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

ここまでの状態は下記の画像のようになる。

インターフェースを各NSに配置

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

ここまでの状態は下記の図のようになる。

NS同士相互通信できる状態

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の場合のネットワーク構成図は下記のようになる。

Dockerのネットワーク構成図

まとめ
Linuxマシン上で一から自分でネットワークを構築してみた。手続き的にたくさんのコマンドを打つ必要があったが、Dockerを使うことでLinuxマシン上に簡単にネットワーク構成を作ることができる。
普段なんとなく使っているものを、こうやって深掘りして調べてみると勉強になることがたくさんあることがわかった。