はじめに
前回は、Docker/Kubernetesを扱う上で必要なネットワークの基礎知識ということで、
について解説しました。
リンクは以下です。
上記の項目を理解しておくことで、Dockerのネットワークの仕組みをスムーズに理解できると思います。特にiptablesとnetwork namespaceの理解は必須です。
本記事では、上記の内容を元に、Dockerのネットワークの仕組みを紐解いていきます。
Dockerのネットワークの概要
Dockerではコンテナが通常のホストマシンと同じようにネットワーク通信ができるように、vethとnamespace、iptablesを使って仮想的なネットワークを1つのホストマシン内部に構築しています。
具体的には、コンテナ1つに対してvethペアを作成し、片方をホストのブリッジとリンクさせ、もう片方をコンテナのnamespaceのインターフェースとすることで、ブリッジを介してコンテナ同士やホストと通信します。
では、具体的にDockerのネットワーク構成を確認していきます。
デフォルト設定
Dockerをインストールした直後は、デフォルトで3種類(bridge, host, none)のネットワークが作成されています。何も指定せずにコンテナを起動すると、自動的にbridgeのネットワークが利用されるようになっています。ネットワーク一覧を表示することで確認できます。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
5a5c97732d37 bridge bridge local
fe0ef46114b8 host host local
477421ac0904 none null local
上記のようにdocker network
コマンドを使うことで、Dockerのネットワーク情報を表示したり、作成したりといった操作ができます。
$ docker network --help Usage: docker network COMMAND Manage networks Commands: connect Connect a container to a network create Create a network disconnect Disconnect a container from a network inspect Display detailed information on one or more networks ls List networks prune Remove all unused networks rm Remove one or more networks Run 'docker network COMMAND --help' for more information on a command.
bridgeネットワークの詳細
例えばbridgeネットワークの詳細は以下のようになっています。
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "5a5c97732d3723d6d8d2a69073d5d7f7f03b67554459a08e708c79db517b2046",
"Created": "2019-12-17T09:14:08.464805258Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"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": {}
}
]
この詳細情報から、サブネットが172.17.0.0/16
であることや、オプションとしてIPマスカレードやMTUの設定が適用されていること、ブリッジ名がdocker0
として設定されていることがわかります。
ではブリッジdocker0
の詳細をifconfig
コマンドおよびip
コマンドで確認してみます。
$ ifconfig docker0
docker0 Link encap:Ethernet HWaddr 02:42:89:90:7e:b1
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
docker0に172.17.0.1
のIPアドレスが割り当てられていることが分かります。
$ ip link show docker0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:89:90:7e:b1 brd ff:ff:ff:ff:ff:ff
$ ip address show docker0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:89:90:7e:b1 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
ip
コマンドでは、割り当てられているIPアドレスに加え、そのインターフェースのステータスがDOWNになっていることも分かります。
ここまでのイメージ図は下記です。
コンテナ起動時のネットワーク構成
コンテナを起動してホストのネットワーク構成の変化を確認する
では、ここでサンプルでコンテナを起動させます。(test1という名前をつけています。また今回はコンテナの中身は関係ないため軽量なalpineイメージを使っています。)
$ sudo docker run -d --name test1 alpine sleep 6000
1004a5da26cec98fc3c861cb18db921cc7d806efcde9880bbca4169d4cc7a3d2
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1004a5da26ce alpine "sleep 6000" 3 seconds ago Up 2 seconds test1
すると、docker0のステータスがUPに変わり、vethのインターフェースが作成されます。
$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 0e:52:22:18:99:ca 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:89:90:7e:b1 brd ff:ff:ff:ff:ff:ff
33: veth3d3f000@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 8e:35:fe:12:13:a9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
vethの設定の中にはmaster docker0
という記載もあるため、docker0とリンクされていることも分かります。このリンクされている情報は、ブリッジの情報を出力してくれるbrctl
コマンドからも確認できます。
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024289907eb1 no veth3d3f000
interfacesの項目にveth名が記載されています。
なお、ifconfig
コマンドでもvethインターフェースを確認できます。
$ ifconfig veth3d3f000
veth3d3f000 Link encap:Ethernet HWaddr 8e:35:fe:12:13:a9
inet6 addr: fe80::8c35:feff:fe12:13a9/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:828 (828.0 B)
ここまでで分かっている部分のイメージ図です。
さて最初の概要にて、
「コンテナ1つに対してvethペアを作成し、片方をホストのブリッジとリンクさせ、もう片方をコンテナのnamespaceのインターフェースとすることで、ブリッジを介してコンテナ同士やホストと通信します。」
と書きました。
つまり、もう1つのvethインターフェースはコンテナのnamespaceにて確認できるはずです。
しかし、network namespaceを確認できるip netns
コマンドを実行しても、何も表示されません。
$ ip netns
(何も表示されない)
これは、デフォルトではip
コマンドで見えないところでコンテナのnetns情報が管理されているためです。
ip
コマンドの管理下にコンテナのnetns情報をリンクすることで、扱えるようになります。
コンテナのnetnsをipコマンドの管理下にリンクする
まずは、test1のプロセスIDを取得します。
$ docker inspect test1 --format '{{.State.Pid}}'
15329
※環境によってプロセスIDは異なります。
確認したプロセスIDのディレクトリからネットワーク識別子を確認します。
$ sudo ls -la /proc/15329/ns/net
lrwxrwxrwx 1 root root 0 Dec 17 12:29 /proc/15329/ns/net -> net:[4026532283]
このファイルをip
コマンドの支配下である/var/run/netns
にリンクさせることで、ip
コマンドで扱えるようになります。
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/15329/ns/net /var/run/netns/test1-ns
再度ip
コマンドで確認すると表示されます。
$ ip netns
test1-ns
これでtest1コンテナのnetwork namespaceを確認できるようになりました。
コンテナ内部のネットワーク構成を確認する
実際に確認してみます。
確認するためのコマンドは ip net ns exec <netns名> <実行したいipコマンド>
です。
$ sudo ip netns exec test1-ns ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ sudo ip netns exec test1-ns ip addr show eth0
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
eth0がインターフェースとして存在し、172.17.0.2/16
が割り当てられていることが分かります。
また、下記のコマンドから、コンテナのデフォルトゲートウェイが172.17.0.1
に設定されていることも分かります。
$ sudo ip netns exec test1-ns ip route
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
ここまでで、コンテナを起動した時に、bridgeネットワークを使ってどのようにネットワークが構築されるのかが分かりました。
コンテナ同士の通信を確認する
続いてはコンテナをもう1つ立ち上げ、コンテナ同士で通信ができるかどうかを確認します。
test2のコンテナを起動します。
$ sudo docker run -d --name test2 alpine sleep 6000
e5981078e932e6a038d7b0ecde7dcc972a4f93aefd967b745f9bd2036c6142d1
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e5981078e932 alpine "sleep 6000" 6 seconds ago Up 5 seconds test2
1004a5da26ce alpine "sleep 6000" About a minute ago Up About a minute test1
それぞれのコンテナのIPアドレスを確認します。
$ sudo docker inspect --format "{{.NetworkSettings.Networks.bridge.IPAddress}}" test1
172.17.0.2
$ sudo docker inspect --format "{{.NetworkSettings.Networks.bridge.IPAddress}}" test2
172.17.0.3
上記で確認したtest2のIPアドレスを使って、test1のコンテナからtest2に対して、pingが通るか確認します。
$ sudo docker exec -ti test1 ping -c 3 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.120 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.079 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.077 ms
--- 172.17.0.3 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.077/0.092/0.120 ms
0%のパケットロスで疎通できており、正常に通信できることが分かりました。
ここまでで分かったことをまとめると下図のようになります。
外部と通信する時のネットワーク構成
ではホストとコンテナをポートフォワードし、外部から接続できるようにしてみます。
$ sudo docker run -d --name test3 -p 12345:8888 alpine sleep 6000
8d3b2bb5b4f87c6fd4d02c237f219f0ed8765411679972dd93188a593c20d056
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d3b2bb5b4f8 alpine "sleep 6000" 11 seconds ago Up 9 seconds 0.0.0.0:12345->8888/tcp test3
e5981078e932 alpine "sleep 6000" 12 minutes ago Up 12 minutes test2
1004a5da26ce alpine "sleep 6000" 14 minutes ago Up 14 minutes test1
このポートフォワードの設定は、iptablesのNAT変換およびパケットフィルタリングにて実現されています。
ということで、iptablesの設定を確認します。iptalblesは最初にnatの設定が適用され、その後filter(パケットフィルタリング)の設定が適用されます。
まずはnatテーブルを確認します。
$ sudo iptables -t nat -L DOCKER
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- anywhere anywhere
DNAT tcp -- anywhere anywhere tcp dpt:12345 to:172.17.0.4:8888
設定通り、ホストの12345ポートに届いた通信は、DNATとして 172.17.0.4:8888
への通信に変換されることが分かります。
次にfilterテーブルです。
$ sudo iptables -L DOCKER
Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- anywhere 172.17.0.4 tcp dpt:8888
こちらも設定通り、172.17.0.4
のポート8888に対する通信がACCEPTになっています。
ここまでの構成を改めてまとめると下図のようになります。
これでDockerのデフォルトのネットワーク構成に対する理解が深まったかと思います。
独自ネットワークを作成
次は、独自のネットワークを作成する方法を見ていきます。 デフォルトのdocker0のネットワークは明示的にIPアドレスを指定できず、Docker側で自動的に決められてしまいます。もしIPアドレスを指定したい場合は、自分でネットワークを作成する必要があります。
docker network
コマンドでサブネットと名前を指定して作成できます。
ここではtestnet1
という名前をつけて作成しています。
$ docker network create --subnet 172.21.0.0/16 --attachable testnet1
--attachable
のオプションをつけることで、後から作成したコンテナに紐付けることができるようになります。細かい使い方は公式ページにまとまっています。
docker network create | Docker Documentation
では作成されたかどうかを確認します。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
659876731c81 bridge bridge local
01c446104fae host host local
41c4e357056e none null local
cae34612a700 testnet1 bridge local
testnet1
というネットワークが追加されていることがわかりました。
このネットワークを使うには、コンテナ起動時に--network
と--ip
のオプションを付ける必要があります。
$ docker run -d --network testnet1 --ip 172.21.0.2 --name test alpine sleep 6000
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c888cb905dcf alpine "sleep 6000" About a minute ago Up About a minute test
$ docker exec -ti test hostname -i
172.21.0.2
ただし、作成したネットワークのコンテナが、他のネットワークやマシンの外のネットワークと通信をするためには、bridgeネットワークのdocker0のようにブリッジなどの設定が必要になります。
マルチホストによるネットワーク構築
コンテナを起動するときに-p
オプションを使ってポートフォワードの設定をすることで、iptablesが設定され、外部と通信できることを確認しました。
ただ、毎回コンテナを起動するときにポートフォワードを設定することは、数多くのコンテナを立ち上げたり、自動でコンテナを起動させたい場合などに、設定や管理が煩雑になります。
そこで、サードパーティ製のネットワーク構築OSSを利用することで、ポート管理をせずに、自動でネットワークの作成削除など管理をしてくれます。
サードパーティ製のネットワーク構築OSSは数多く存在しており、flannelやcalico、weave netが有名です。例えば、flannelでは、VXLANを利用してオーバレイネットワークを実現し、etcd(KVS)を使ってネットワーク設定を紐付けられたホスト間で共有することで、マルチホスト間でも重複しないネットワーク設定を使って通信を可能にしています。
自動でネットワークを管理できることは、Kubernetesのネットワークの肝になります。Kubernetesを使っていくのであれば、しっかりと理解しておく必要があります。そのため、詳しい実装に関しては別途まとめていきます。
おわりに
今回はDockerのネットワークの仕組みについてまとめました。まだまだ奥深い部分はあるとは思いますが、ブリッジがあって、vethペアがあって、iptablesがあって、といった基本的な仕組みは把握できたかと思います。
最後の方にも書きましたが、次はKubernetesのネットワークの仕組みを理解してまとめたいと思っています。
参考文献
- 作者:Adrian Mouat
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/08/17
- メディア: 単行本(ソフトカバー)