はじめに
上記の記事では、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を使って実現します。
構成は図に表すと、以下のようになります。
まずは、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ファイルの設定でコンテナを立ち上げます。
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"
}
}
]
参考