DockerとNetwork Namespace
ネットワークを勉強していると、Linuxマシン上で仮想的なネットワークを作成できる、Network Namespaceという機能があることを知った。
色々調べてみると、どうやらこの機能がDockerを構成する技術の1つでもあるらしい。
前々からネットワークやDockerの詳細について深掘りして学習したいと考えていたので、今回はDockerで3つのコンテナを立てたネットワークの状態をNetwork Namespace(以下NS)を使って再現してみようと思う。
今回作成するネットワークの構成図
Ubuntuのホストマシンの中にns1,ns2,ns3,bridgeというNSを作成し、ns1~ns3がbridgeの中の仮想ブリッジと接続され、外部ネットワークと通信ができる状態を目指す。
ブリッジとはデータリンク層でフレームを転送する機器で、1つのブロードキャストドメインに対して複数のネットワーク機器の接続を提供してくれる。
1. 仮想ノードの作成
ns1,ns2,ns3,bridgeを作成する。
$ sudo ip netns add ns1 $ sudo ip netns add ns2 $ sudo ip netns add ns3 $ sudo ip netns add bridge
これでNSが作成される。NSがきちんと作成されているか確認してみると、
$ ip netns list bridge ns3 ns2 ns1
きちんと4つのNSが作成されていた。
2. ノード同士を繋ぐvethインターフェイスを作成
先ほどの構成図では、ns1~ns3がそれぞれbridgeと繋がっていたので、6つのvethインターフェースを作成する必要がある。
$ sudo ip link add ns1-veth0 type veth peer name ns1-br0 $ sudo ip link add ns2-veth0 type veth peer name ns2-br0 $ sudo ip link add ns3-veth0 type veth peer name ns3-br0
作成されているか確認してみる。
$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 02:59:c2:1a:50:f1 brd ff:ff:ff:ff:ff:ff 3: ns1-br0@ns1-veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 72:1b:b5:69:10:c0 brd ff:ff:ff:ff:ff:ff 4: ns1-veth0@ns1-br0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 8a:c9:fa:44:00:6d brd ff:ff:ff:ff:ff:ff 5: ns2-br0@ns2-veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether c6:06:19:58:a3:b5 brd ff:ff:ff:ff:ff:ff 6: ns2-veth0@ns2-br0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 6a:26:33:1a:c8:32 brd ff:ff:ff:ff:ff:ff 7: ns3-br0@ns3-veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 4a:c4:d9:df:f3:b7 brd ff:ff:ff:ff:ff:ff 8: ns3-veth0@ns3-br0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether ba:dd:e3:bf:96:22 brd ff:ff:ff:ff:ff:ff
loとenp0s3はデフォルトで作成されているインターフェースであるので、きちんと作成されていることが確認できる。
3. 作成したvethインターフェースをノードに設定する
現状では全てのインターフェースはホストマシン上に存在し、NSには付与されていない。これをNSに付与する。
$ sudo ip link set ns1-veth0 netns ns1 $ sudo ip link set ns2-veth0 netns ns2 $ sudo ip link set ns3-veth0 netns ns3 $ sudo ip link set ns1-br0 netns bridge $ sudo ip link set ns2-br0 netns bridge $ sudo ip link set ns3-br0 netns bridge
付与できているかどうかを確認する。
$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 02:59:c2:1a:50:f1 brd ff:ff:ff:ff:ff:ff
ホストマシン上のインターフェース一覧からは、先ほどNSに付与したものは消えている。
$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 02:59:c2:1a:50:f1 brd ff:ff:ff:ff:ff:ff
代わりにNS上では、付与したインターフェースが表示されるようになる。
$ sudo ip netns exec ns1 ip link show 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 4: ns1-veth0@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 8a:c9:fa:44:00:6d brd ff:ff:ff:ff:ff:ff link-netns bridge
ここまでの状態は下記の画像のようになる。
4. 作成したvethインターフェースの状態をUPに変更
vethインターフェースはデフォルトの状態ではDOWNであり、UPに変更する必要がある。
$ sudo ip netns exec ns1 ip link show ns1-veth0 | grep state 4: ns1-veth0@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
$ sudo ip netns exec ns1 ip link set ns1-veth0 up $ sudo ip netns exec ns2 ip link set ns2-veth0 up $ sudo ip netns exec ns3 ip link set ns3-veth0 up $ sudo ip netns exec bridge ip link set ns1-br0 up $ sudo ip netns exec bridge ip link set ns2-br0 up $ sudo ip netns exec bridge ip link set ns3-br0 up
もう一度インターフェースの状態を確認すると、UPに変更されている。
$ sudo ip netns exec ns1 ip link show ns1-veth0 | grep state 4: ns1-veth0@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state UP mode DEFAULT group default qlen 1000
5. NS側のvethインターフェースにIPアドレスを付与
各NSのインターフェースにIPアドレスを付与する。
$ sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 $ sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0 $ sudo ip netns exec ns3 ip address add 192.0.2.3/24 dev ns3-veth0
インターフェースにIPが付与されていることが確認できる。
$ sudo ip netns exec ns1 ip addr 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 4: ns1-veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 8a:c9:fa:44:00:6d brd ff:ff:ff:ff:ff:ff link-netns bridge inet 192.0.2.1/24 scope global ns1-veth0 valid_lft forever preferred_lft forever inet6 fe80::88c9:faff:fe44:6d/64 scope link valid_lft forever preferred_lft forever
6. ネットワークブリッジを作成
現状ではNS bridgeには4つのインターフェースが存在する。
$ sudo ip netns exec bridge ip link 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: ns1-br0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 72:1b:b5:69:10:c0 brd ff:ff:ff:ff:ff:ff link-netns ns1 5: ns2-br0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether c6:06:19:58:a3:b5 brd ff:ff:ff:ff:ff:ff link-netns ns2 7: ns3-br0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 4a:c4:d9:df:f3:b7 brd ff:ff:ff:ff:ff:ff link-netns ns3
各NSが外部ネットワークやお互いのNSと相互通信できるようにするために、仮想ネットワークブリッジを作成する。
$ sudo ip netns exec bridge ip link add dev br0 type bridge $ sudo ip netns exec bridge ip link set br0 up
実際にブリッジが作成されていることがわかる。
$ sudo ip netns exec bridge ip link 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/ether 06:93:fb:99:50:41 brd ff:ff:ff:ff:ff:ff 3: ns1-br0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 72:1b:b5:69:10:c0 brd ff:ff:ff:ff:ff:ff link-netns ns1 5: ns2-br0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether c6:06:19:58:a3:b5 brd ff:ff:ff:ff:ff:ff link-netns ns2 7: ns3-br0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 4a:c4:d9:df:f3:b7 brd ff:ff:ff:ff:ff:ff link-netns ns3
7. bridge側のvethインターフェースをブリッジに接続
NS bridgeに配置されているvethインターフェースを仮想ブリッジに接続する
$ sudo ip netns exec bridge ip link set ns1-br0 master br0 $ sudo ip netns exec bridge ip link set ns2-br0 master br0 $ sudo ip netns exec bridge ip link set ns3-br0 master br0
実際に接続されていることを確認する。
$ sudo ip netns exec bridge ip link show master br0 3: ns1-br0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000 link/ether 72:1b:b5:69:10:c0 brd ff:ff:ff:ff:ff:ff link-netns ns1 5: ns2-br0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000 link/ether c6:06:19:58:a3:b5 brd ff:ff:ff:ff:ff:ff link-netns ns2 7: ns3-br0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br0 state UP mode DEFAULT group default qlen 1000 link/ether 4a:c4:d9:df:f3:b7 brd ff:ff:ff:ff:ff:ff link-netns ns3
ここまでの状態は下記の図のようになる。
8. ノードns1からノードns2,ns3へ通信してみる
ns1からns2に通信してみる。
$ sudo ip netns exec ns1 ping -c 3 192.0.2.2 -I 192.0.2.1 PING 192.0.2.2 (192.0.2.2) from 192.0.2.1 : 56(84) bytes of data. 64 bytes from 192.0.2.2: icmp_seq=1 ttl=64 time=0.052 ms 64 bytes from 192.0.2.2: icmp_seq=2 ttl=64 time=0.045 ms 64 bytes from 192.0.2.2: icmp_seq=3 ttl=64 time=0.045 ms --- 192.0.2.2 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2026ms rtt min/avg/max/mdev = 0.045/0.047/0.052/0.003 ms
きちんと通信できている。ns1からns3に通信してみる。
$ sudo ip netns exec ns1 ping -c 3 192.0.2.3 -I 192.0.2.1 PING 192.0.2.3 (192.0.2.3) from 192.0.2.1 : 56(84) bytes of data. 64 bytes from 192.0.2.3: icmp_seq=1 ttl=64 time=0.039 ms 64 bytes from 192.0.2.3: icmp_seq=2 ttl=64 time=0.034 ms 64 bytes from 192.0.2.3: icmp_seq=3 ttl=64 time=0.039 ms --- 192.0.2.3 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2052ms rtt min/avg/max/mdev = 0.034/0.037/0.039/0.002 ms
9. ノードから外部と通信してみる
$ sudo ip netns exec ns1 ping 8.8.8.8 ping: connect: Network is unreachable
ここで外部ネットワークと通信を試みるが、できない。
それもそのはず、外部ネットワークと通信する経路はまだ用意できていない。
10. ノードからブリッジ経由で外部と通信できるようにする
まずはブリッジとホストを繋ぐインターフェースを作成する。
$ sudo ip link add name rt-veth type veth peer name rt-br0 $ sudo ip link set rt-veth up
次に、ホスト側のインターフェースにIPを付与する。
$ sudo ip addr add 192.0.2.100/24 dev rt-veth
NS bridge側のインタフェースを仮想ブリッジに接続する。
$ sudo ip link set rt-br0 netns bridge $ sudo ip netns exec bridge ip link set dev rt-br0 master br0 $ sudo ip netns exec bridge ip link set rt-br0 up
各NSにデフォルトゲートウェイを設定する。
$ sudo ip netns exec ns1 ip route add default via 192.0.2.100 $ sudo ip netns exec ns2 ip route add default via 192.0.2.100 $ sudo ip netns exec ns3 ip route add default via 192.0.2.100
ホスト側でIP転送を有効化する。
$ sudo sysctl net.ipv4.ip_forward=1
ホスト側のインターフェースにNATを設定する。
$ sudo iptables -t nat \ -A POSTROUTING \ -s 192.0.2.0/24 \ -o enp0s3 \ -j MASQUERADE
11. もう一度ノードから外部と通信してみる
$ sudo ip netns exec ns1 ping -c 3 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=4.36 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=4.82 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=61 time=5.09 ms --- 8.8.8.8 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2005ms rtt min/avg/max/mdev = 4.364/4.759/5.089/0.299 ms
外部と通信できた!
これで最初に示した完成図と同じネットワーク構成を作ることができた。
Dockerを使うと
NSを使ってネットワークを構築する場合、上記のようにたくさんのコマンドを叩く必要があったが、Dockerの場合同じようなネットワーク構成を実現するにはコンテナを3つ立ち上げるだけで済む。
1. Dockerが入っていない状態でインターフェースを確認する
現状ではloとenp0s3の2つだけが存在している。
$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 02:59:c2:1a:50:f1 brd ff:ff:ff:ff:ff:ff
2. Dockerをインストールする
Dockerをインストールしただけの状態で、もう一度インターフェースの一覧を見る。
$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 02:59:c2:1a:50:f1 brd ff:ff:ff:ff:ff:ff 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500
docker0というインターフェースが新規で作成されている。
IPアドレスを見てみると、
$ ip address show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 02:59:c2:1a:50:f1 brd ff:ff:ff:ff:ff:ff inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3 valid_lft 85874sec preferred_lft 85874sec inet6 fe80::59:c2ff:fe1a:50f1/64 scope link valid_lft forever preferred_lft forever 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:8e:4a:45:a1 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
172.17.0.1/16というIPが割り振られているのがわかる。
3. コンテナを立てる
先ほどのNSの時と同じ状態に近づけるために、コンテナを3つ立てる。
$ sudo docker run -it -d --name ubuntu01 ubuntu:latest $ sudo docker run -it -d --name ubuntu02 ubuntu:latest $ sudo docker run -it -d --name ubuntu03 ubuntu:latest
コンテナを作ると、コンテナに対応するインターフェースが作成されている。
$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 02:59:c2:1a:50:f1 brd ff:ff:ff:ff:ff:ff 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:8e:4a:45:a1 brd ff:ff:ff:ff:ff:ff 5: veth7203d86@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 72:52:16:04:2f:77 brd ff:ff:ff:ff:ff:ff link-netnsid 0 7: vetha29d336@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether d6:84:ad:10:be:a5 brd ff:ff:ff:ff:ff:ff link-netnsid 1 9: veth0ba49e1@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 1a:aa:47:69:f1:bf brd ff:ff:ff:ff:ff:ff link-netnsid 2
上記のインターフェースはdocker0に接続されていることがわかる。
$ ip link show master docker0 5: veth7203d86@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 72:52:16:04:2f:77 brd ff:ff:ff:ff:ff:ff link-netnsid 0 7: vetha29d336@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether d6:84:ad:10:be:a5 brd ff:ff:ff:ff:ff:ff link-netnsid 1 9: veth0ba49e1@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 1a:aa:47:69:f1:bf brd ff:ff:ff:ff:ff:ff link-netnsid 2
各コンテナのデフォルトゲートウェイは172.17.0.1を向いていることがわかる。
$ sudo docker inspect --format '{{.NetworkSettings.Gateway}}' ubuntu01 172.17.0.1 $ docker inspect --format '{{.NetworkSettings.Gateway}}' ubuntu02 172.17.0.1 $ sudo docker inspect --format '{{.NetworkSettings.Gateway}}' ubuntu03 172.17.0.1
Dockerの場合のネットワーク構成図は下記のようになる。
まとめ
Linuxマシン上で一から自分でネットワークを構築してみた。手続き的にたくさんのコマンドを打つ必要があったが、Dockerを使うことでLinuxマシン上に簡単にネットワーク構成を作ることができる。
普段なんとなく使っているものを、こうやって深掘りして調べてみると勉強になることがたくさんあることがわかった。