0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

上記の記事では、2つの異なるNetwork Namespace間を直接繋ぐような仮想ネットワークを作成しました。
次は3つ以上のNetwork Namespace間を繋ぐような仮想ネットワークを作成します。

2つの異なるNetwork Namespace間の通信は、それぞれにvethをアタッチして直接リンクする形で実現することができましたが、3つ以上のNetwork Namespace間の通信はこの方法では実現できません。
そこでLinux bridgeを使います。

Linux bridgeとは

仮想的なL2スイッチです。
なので、このLinux bridgeを使うことによってどのポートにどのMACアドレスの機器がつながっているのかを管理することができます。

DockerのネットワークでもこのLinux bridgeが使われています。

Linux bridgeを使って複数Network Namespace間で疎通する

3つのNetwork Namespace間での通信をLinux bridgeを使って実現します。
構成は図に表すと、以下のようになります。

スクリーンショット 2024-12-22 11.39.02.png

まずは、3つのNetwork Namespaceを作成します。

$ sudo ip netns add ns1
$ sudo ip netns add ns2
$ sudo ip netns add ns3

次に、bridgeを作成します。
https://man7.org/linux/man-pages/man8/ip-link.8.html

$ sudo ip link add br0 type bridge

両端がNetwork Interfaceになっている仮想的なケーブルを作成し、一方はNetwork Namespaceにアタッチし、もう一方はbridgeにアタッチします。
(マシンのポートと、L2スイッチのポートにイーサーネットケーブルを挿し込む操作を仮想的に再現していることになります。)

# 両端がNetwork Interfaceになっている仮想的なケーブルの作成
$ sudo ip link add ns1-veth0 type veth peer ns1_br0-veth0
$ sudo ip link add ns2-veth0 type veth peer ns2_br0-veth0
$ sudo ip link add ns3-veth0 type veth peer ns3_br0-veth0

# Network Namespaceとbridgeにアタッチ
$ sudo ip link set ns1-veth0 netns ns1
$ sudo ip link set ns1_br0-veth0 master br0

$ sudo ip link set ns2-veth0 netns ns2
$ sudo ip link set ns2_br0-veth0 master br0

$ sudo ip link set ns3-veth0 netns ns3
$ sudo ip link set ns3_br0-veth0 master br0

加えて、初期状態ではNetwork InterfaceはDOWNになっていて通信ができない状態にあるため、UPに変更しておきます。

# Network InterfaceをDOWNからUPに
$ sudo ip link set dev br0 up
$ sudo ip netns exec ns1 ip link set ns1-veth0 up
$ sudo ip link set ns1_br0-veth0 up
$ sudo ip netns exec ns2 ip link set ns2-veth0 up
$ sudo ip link set ns2_br0-veth0 up
$ sudo ip netns exec ns3 ip link set ns3-veth0 up
$ sudo ip link set ns3_br0-veth0 up

ping(L3)で疎通確認をするために各Network NamespaceのNetwork Interfaceに対してIPアドレスを割り振ります。

$ sudo ip netns exec ns1 ip address add 192.168.0.1/24 dev ns1-veth0
$ sudo ip netns exec ns2 ip address add 192.168.0.2/24 dev ns2-veth0
$ sudo ip netns exec ns3 ip address add 192.168.0.3/24 dev ns3-veth0

これで3つのNetwork Namespace間で、Linux bridgeを使って通信をする準備ができたのでpingしてみます。

# ns1 <-> ns2
$ sudo ip netns exec ns1 bash
$ ping -c 3 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.127 ms
64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.090 ms
64 bytes from 192.168.0.2: icmp_seq=3 ttl=64 time=0.089 ms

# ns1 <-> ns3
$ ping -c 3 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.144 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.085 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.075 ms

# ns2 <-> ns3
$ sudo ip netns exec ns1 bash
$ ping -c 3 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.126 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.093 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.081 ms

3つ以上のNetwork Namespace間で疎通を確認できました。

L2スイッチとしてのLinux bridgeの挙動

L2スイッチは、自身のどのポートにどのMACアドレス機器がつながっているのかをテーブルとして管理しています。(forwardingテーブル)
これは今回のLinux bridgeも同様なので、そちらの確認もします。

確認しやすくするために、各Network Namespaceに割り当てたNetwork InterfaceのMACアドレスを変更しておきます。

$ sudo ip netns exec ns1 ip link set dev ns1-veth0 address 00:00:00:00:00:01
$ sudo ip netns exec ns2 ip link set dev ns2-veth0 address 00:00:00:00:00:02
$ sudo ip netns exec ns3 ip link set dev ns3-veth0 address 00:00:00:00:00:03

初期状態だと、forwardingテーブルにこれらのMACアドレスは登録されていません。

$ brctl showmacs br0
port no mac addr                is local?       ageing timer

L2スイッチは、forwaringテーブルにすでにエントリが存在する場合は、それに従ってパケットをforwardingし、存在しなかった場合は送信元以外のすべてのインターフェースにforwardingすることでforwaringテーブルを更新します。
この宛先が不明な場合にすべてのインターフェースにfowardingすることをフラッディングと言います。

初回の疎通確認時にはフラッディングが発生しており、ns1 <-> ns3の通信パケットがns2にも到達しています。
ここで初めてforwardingテーブルの更新が発生し、エントリが追加されます。

$ sudo ip netns exec ns1 ping -c 3 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.167 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.076 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.071 ms

$ brctl showmacs br0
port no mac addr                is local?       ageing timer
  1     00:00:00:00:00:01       no                15.14
  3     00:00:00:00:00:03       no                15.14

bridgeのforwardingテーブルが更新されていることも確認できました。

Dockerとbridge

Dockerのネットワーキングは、このLinux bridgeを使って実現されています。

Docker をインストールした全ての環境には、 docker0 と表示されるブリッジ( bridge )ネットワークが現れます。

$ ip addr show docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:e4:91:b1:8b brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:e4ff:fe92:b14b/64 scope link 
       valid_lft forever preferred_lft forever

# macOSだとhostからdocker0を見ることはできません

Dockerでコンテナを立ち上げると、各コンテナはデフォルトのdocker0ブリッジに接続されます。
これは、先ほど検証したNetwork Namespaceとbridgeの構造と同じです。

$ docker run -d --name nginx nginx:latest
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "a8c55753238929f05deac5b15c3dae68c5474027d514752c6f3579072fe2c90e",
        "Created": "2024-12-21T15:07:09.274191183+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "ac64a77d8bb3ebea67f2f5e8a99111430602491986ec34478d499656d88200d2": {
                "Name": "nginx",
                "EndpointID": "ca1f41043ad45c0c234486b7ca91c4e1e90ac0cf5940b9802496345555c450a3",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

docker composeとbridge

docker composeは特に何も指定しなかった場合、docker0とは別にbridgeが新規で作成され、コンテナがそのbridgeを通して通信をすることができます。

hogeというディレクトリで、以下のようなcomposeファイルの設定でコンテナを立ち上げます。

compose.yaml
services:
  host-1:
    image: ubuntu:latest
    container_name: host-1
    tty: true
    cap_add:
      - NET_ADMIN
  host-2:
    image: ubuntu:latest
    container_name: host-2
    tty: true
    cap_add:
      - NET_ADMIN

新規でhoge_defaultという名前のbridge networkが作成され、対象のコンテナがそのbridge越しにつながっていることを確認できます。

$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
a8c557532389   bridge         bridge    local
9478816f6d2a   hoge_default   bridge    local
cab48c3a05f6   host           host      local
5e62a3cdff2a   none           null      local
$ docker network inspect hoge_default
[
    {
        "Name": "hoge_default",
        "Id": "9478816f6d2a318d5f13b2e6f687fb622c423f821722f164bbd671ba77e01435",
        "Created": "2024-12-22T19:13:40.442523611+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "15a7e657e980f50dfd359368d609304cf831582bc42d01bd8216c99f02d638aa": {
                "Name": "host-2",
                "EndpointID": "7f9a7b2653ca3cd9c86105e766255d2d8b265beccf850407ef1e0aac86b4cb69",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "ba33992848379546d38f11fd123ae2a6f754fd38dc7d03d026600a4f04647bfe": {
                "Name": "host-1",
                "EndpointID": "e2abc2fc0abcb6b7bf5157f7eb896f1a0bb7e6bd8f230d3e4f2ab6b2c50e148d",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "hoge",
            "com.docker.compose.version": "2.29.7"
        }
    }
]

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?