こんにちは、LinuCエバンジェリストこと、鯨井貴博@opensourcetechです。
今回は、Dockerコンテナのネットワークについて調べてみました。
Dockerの導入については、こちらを見てください。
docker 〜Dockerfile を使用した、コンテナの作成・起動〜 - Opensourcetechブログ
Dockerコンテナですが、以下の構成にあるバックエンドサーバ nginx 3台を使っています。
※以下の記事で作った構成です。
NGINX Plusによるリバースプロキシ構築 - Opensourcetechブログ
まず、起動しているコンテナの情報を確認します。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
911f5d0e2499 nginx "nginx -g 'daemon of…" 43 hours ago Up 2 hours 0.0.0.0:8083->80/tcp laughing_liskov ・・・コンテナ1
0e9d799bd46a nginx "nginx -g 'daemon of…" 43 hours ago Up 2 hours 0.0.0.0:8082->80/tcp practical_boyd・・・コンテナ2
d39f6d85d65f nginx "nginx -g 'daemon of…" 43 hours ago Up 2 hours 0.0.0.0:8081->80/tcp pensive_cohen・・・コンテナ3
では、この状態でコンテナのネットワークはどうなっているか、
ip addr 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 pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:96:f6:c0 brd ff:ff:ff:ff:ff:ff
inet 192.168.13.3/24 brd 192.168.13.255 scope global noprefixroute dynamic enp0s3
valid_lft 22215sec preferred_lft 22215sec
inet6 fe80::e830:c2da:441:a2d7/64 scope link tentative dadfailed
valid_lft forever preferred_lft forever
inet6 fe80::e77f:5380:24e5:f200/64 scope link tentative dadfailed
valid_lft forever preferred_lft forever
inet6 fe80::bba:f6af:15f8:fdcb/64 scope link tentative dadfailed
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:e9:8e:65 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:acff:fee9:8e65/64 scope link
valid_lft forever preferred_lft forever
5: veth6e13a71@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 2a:74:10:58:17:3d brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::2874:10ff:fe58:173d/64 scope link
valid_lft forever preferred_lft forever
7: veth3e496ad@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 0e:a2:b7:51:16:f0 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::ca2:b7ff:fe51:16f0/64 scope link
valid_lft forever preferred_lft forever
9: veth4f2f0d6@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 22:fc:05:1f:75:32 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::20fc:5ff:fe1f:7532/64 scope link
valid_lft forever preferred_lft forever
上記の中の、「3: docker0」がDocker用に作られたインターフェイス、
「vethXXXXXXXX`XXX」とあるのが各コンテナに使用されるインターフェイスとなります。
図にすると、以下のようになります。
各インターフェイスの意味は、以下の通りです。
docker0:Dockerコンテナ全体で使用される中心的なインターフェイス
veth:各コンテナのインターフェイスとペアとなるインターフェイス
そして、上記インターフェイスの関係を作成する為に必要な情報の確認方法を見ていきます。
情報の確認には、docker inspectコマンドを使用します。
docker inspect | Docker Documentation
各コンテナ インターフェイスのIPアドレス確認
172.17.0.4
[root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 0e9d799bd46a
172.17.0.3
[root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 911f5d0e2499
172.17.0.2
各コンテナ インターフェイスのMACアドレス確認
02:42:ac:11:00:04
[root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{.MacAddress}}{{end}}' 0e9d799bd46a
02:42:ac:11:00:03
[root@localhost ~]# docker inspect --format='{{range .NetworkSettings.Networks}}{{.MacAddress}}{{end}}' 911f5d0e2499
02:42:ac:11:00:02
各コンテナとHostPCのポートバインド情報確認
以下だと、コンテナのTCP80番ポートと、HostPCの8081 / 8082 / 8083がバインとされていることが確認できます。
8081
[root@localhost ~]# docker inspect --format='{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostPort}}' 0e9d799bd46a
8082
[root@localhost ~]# docker inspect --format='{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostPort}}' 911f5d0e2499
8083
同様のものは、以下でも確認できます。
80/tcp -> 8081
[root@localhost ~]# docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 0e9d799bd46a
80/tcp -> 8082
[root@localhost ~]# docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 911f5d0e2499
80/tcp -> 8083
brctl(bridge-utilsパッケージに含まれる)で、docker0とvethの関連が確認できます。
bridge name bridge id STP enabled interfaces
docker0 8000.0242ace98e65 no veth3e496ad
veth4f2f0d6
vethc50278d
この操作で、各コンテナのネットワークに関する情報は分かりましたが、
vethと各コンテナ インターフェイスの関連性(どのvethが、どのコンテナに対応するかどうか)については確認ができません。
ここで重要なのが、Dockerコンテナが動作している裏には、
Linux Kernelのシステムリソース分離機能である cgroups(namespaces:名前空間 と呼んでもいいかもしれませんが)を知ること。
https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
以下のDocker overviewにも書いてあります。
Docker overview | Docker Documentation
そして、Dockerコンテナで使われている名前空間のうち、ネットワーク名前空間を見ていくと「vethと各コンテナインターフェイスの関連性」が確認できます。
方法としては、以下のGitHubにある「docker-netns.sh」というスクリプトを使用しました。
GitHub - pujoheadsoft/docker-netns
gitが必要なので、入っていない場合は「yum install git」などでインストールします。
Cloning into 'docker-netns'...
remote: Enumerating objects: 12, done.
remote: Total 12 (delta 0), reused 0 (delta 0), pack-reused 12
Unpacking objects: 100% (12/12), done.
[root@localhost ~]# ls
anaconda-ks.cfg docker-netns lldptool.man
[root@localhost ~]# cd docker-netns/
[root@localhost docker-netns]# ls -l
合計 8
-rw-r--r--. 1 root root 280 11月 20 20:55 README.md
-rw-r--r--. 1 root root 3972 11月 20 20:55 docker-netns.sh
[root@localhost docker-netns]# chmod +x docker-netns.sh
[root@localhost docker-netns]# ls -l
合計 8
-rw-r--r--. 1 root root 280 11月 20 20:55 README.md
-rwxr-xr-x. 1 root root 3972 11月 20 20:55 docker-netns.sh
[root@localhost docker-netns]# ./docker-netns.sh visible
container's network namespace to visible, you can show container's nemespace by [ip netns] command.
[root@localhost docker-netns]# ip netns
d39f6d85d65f (id: 2)
0e9d799bd46a (id: 1)
911f5d0e2499 (id: 0)
[root@localhost docker-netns]# ./docker-netns.sh showveth ・・・これで各コンテナが使用しているvethの確認ができます!
VETH CONTAINER ID NAMES
vethc50278d 911f5d0e2499 /laughing_liskov
veth3e496ad 0e9d799bd46a /practical_boyd
veth4f2f0d6 d39f6d85d65f /pensive_cohen
[root@localhost docker-netns]# ./docker-netns.sh showip
CONTAINER ID IP NAMES
911f5d0e2499 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 /laughing_liskov
0e9d799bd46a inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 /practical_boyd
d39f6d85d65f inet 172.17.0.4/16 brd 172.17.255.255 scope global eth0 /pensive_cohen
上記のスクリプトの中で行われていることは、
以下のソースコードを見れば分かりますが、
/proc/コンテナプロセスのPID/ns/net を/var/run/netns/コンテナのID としてシンボリックリンクを設定し 通常では見れない ネットワーク名前空間の情報を確認しています。
docker-netns/docker-netns.sh at master · pujoheadsoft/docker-netns · GitHub
ん〜、Dockerコンテナ ネットワーク 、Linux kernel(cgoupsやら名前空間)んどいろいろ勉強になりますね。