こんにちは。SPEEDA開発チームの鈴木です。
調べてみるとなかなか興味深い技術であるマルチホストでのDocker Conainer間通信。
これをどのように実現しているのか説明したいと思います。
が、その前に今回の投稿では、まず基礎知識的な話としてDockerのネットワークについて順を追って説明をします。
Dockerのネットワーク
docker0
Dockerをインストールしたあと、ifconfig
やip addr show
するとdocker0
なるものが表示されるようになるので、気になっていた人もいるかと思います。
これは一体何者なのでしょうか。
[kenji@arch ~]$ ifconfig docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0 ether 02:42:fb:28:ee:2d txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
このdocker0
はDockerによって自動的に作成された仮想ブリッジで、Docker Container同士をつなぐためのものです。
docker0に接続されているネットワーク・インタフェース
何かしらDocker Containerを起動した状態で、ブリッジを管理するbrctl
コマンドを実行してみると次のようにdocker0
に接続されているネットワーク・インタフェースが表示されます。
(brctl
はbridge-utils
パッケージに含まれています)
[kenji@arch ~]$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242fb28ee2d no veth269de4e veth87e3222 vethbcff718
そしてこれがインタフェースをgrep veth
してみた結果です。たしかにこのネットワーク・インタフェースは存在するようですね。
[kenji@arch ~]$ ip addr show | grep veth 9: vethbcff718@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 11: veth269de4e@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 15: veth87e3222@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
ちなみにDocker Containerをすべて落としてみるとこうなります。docker0
には何も接続されていないようですね。
[kenji@arch ~]$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242fb28ee2d no
さきほど存在したネットワーク・インタフェースもいなくなっています。
[kenji@arch ~]$ ifconfig | grep veth [kenji@arch ~]$
なるほど。じゃあこのvethXXXがDocker Containerのネットワーク・インタフェースなのか、というとちょっと違うのですが、正確なところを説明する前にそもそもvethって何なのかを説明します。
veth
vethは virtualethernet device、つまり仮想的なネットワーク・インタフェースです。
vethは常にペアで作成され、このペア間で通信が行えます。
(vethペアの片割れをveth peerといいます)
vethペアは次のようなコマンドで作成できます。
[kenji@arch ~]$ sudo ip link add name veth1 type veth peer name veth1-peer
ip link show
で確認してみると、vethのペアが作成されていることが分かります。
[kenji@arch ~]$ ip link show | grep veth 18: veth1-peer@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 19: veth1@veth1-peer: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
さて、ここで一つの疑問が生じます。
「Docker Containerを起動したときに作成されるvethって一つじゃね?」
Containerの起動後にネットワーク・インタフェースを表示してみると、たしかにそう見えます。
[kenji@arch ~]$ docker ps --format "table {{.ID}} {{.Names}}" CONTAINER ID NAMES b71e096208c2 kong 7add408c9775 kong-database # Containerが2つなので4つvethが表示されるはず? [kenji@arch ~]$ ip link show | grep veth 17: vethb96b887@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 21: veth9c7e462@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
この疑問に答えるキーワードがネットワーク名前空間です。
ネットワーク名前空間
Dockerのことを勉強すると必ずといっていいほど説明されていますが、Linuxはカーネルが扱う様々なリソースをある単位でまとめて分離する仕組みを持っています。 この仕組みを名前空間といいます。 分離されたリソースはその名前空間に属するプロセス以外からは直接見えなくなります。
例えばunshareコマンドでUTS名前空間を新たに作成した場合、その名前空間を作成した側と別のホスト名を持つことができます。
# 1. 名前空間のエントリを表示 kenji@test1:~$ sudo ls -l /proc/*/ns | grep uts | awk '{print $11}' | sort | uniq uts:[4026531838] # 2. UTS名前空間を新たに作成し、その名前空間でbashを実行 kenji@test1:~$ sudo unshare -u /bin/bash root@test1:~# # 1,2 とは別のシェルで名前空間のエントリを表示すると一つ増えてることが分かる kenji@test1:~$ sudo ls -l /proc/*/ns | grep uts | awk '{print $11}' | sort | uniq uts:[4026531838] uts:[4026532203] # 1,2 のシェルでホスト名を変更 root@test1:~# hostname updated-host root@test1:~# hostname updated-host # 1,2 とは別のシェルでホスト名を表示すると、こちらには上記の変更が反映されないことが分かる kenji@test1:~$ hostname test1
この名前空間には色々種類があるのですが、ネットワークに対する名前空間がネットワーク名前空間です。
そして前述のvethペアの片割れは、作成したネットワーク名前空間に移動させることができるのです。
つまりvethペアによってネットワーク名前空間同士を仮想的なケーブルでつなぐことができます。
移動させたvethは移動元からは見えなくなります。これがDocker Containerのvethの片割れが見えなかった理由です。
ここまでを踏まえるとDockerのネットワークが次のようになっていることが理解できるでしょう。
更に付け加えると、veth(peer)はDocker Container内ではeth0として見えるようになっています。 またDockerによりveth(peer)にIPアドレスが割り当てられます。
Docker Containerのネットワーク名前空間を参照する
ネットワーク名前空間の一覧はip netns
コマンドで参照でき、ip netns exec ネットワーク名前空間名
コマンドを使うと指定したネットワーク名前空間で任意のコマンドが実行できるのですが、Docker Containerのネットワーク名前空間はDockerにより隠蔽されています。
しかし以下2つの情報を知っていれば、強引に参照することができます。
- Docker Containerのネットワーク名前空間は
/proc/Docker ContainerのPID/ns/net
に割り当てられる。 ip netns
コマンドで表示されるネットワーク名前空間は、/var/run/netns
ディレクトリ配下のものである。
どういうことかというと、Docker ContainerのPIDを調べてネットワーク名前空間を見つけて、/var/run/netnsへのリンクを貼るのです。
試してみたいけど面倒くさいという方のためにシェルを用意してあります(github)。
プログラムの性質上、実行にroot権限が必要になるので、気になるようでしたら内容をご確認ください。
git clone https://github.com/pujoheadsoft/docker-netns.git cd docker-netns chmod +x docker-netns.sh # 実行例 ~/docker-netns$ sudo ./docker-netns.sh visible container's network namespace to visible, you can show container's nemespace by [ip netns] command. kenji@master:~/docker-netns$ ip netns bfe34b8a7972 2ec174520fda # vethとvethに対応するContainerを表示 kenji@test1:~/docker-netns$ sudo ./docker-netns.sh showveth VETH CONTAINER ID NAMES veth2753b2f 51f82f5e9712 /mywordpress_wordpress_1 vetha334043 4939c68f1740 /mywordpress_db_1 # ContainerのIPを表示(リソースはcontainer側のものですが、コマンドはホスト側のものを使えるので、IPを表示するコマンドがないContainerでもIPを表示できます) kenji@test1:~/docker-netns$ sudo ./docker-netns.sh showip CONTAINER ID IP NAMES 51f82f5e9712 inet 172.18.0.3/16 scope global eth0 /mywordpress_wordpress_1 4939c68f1740 inet 172.18.0.2/16 scope global eth0 /mywordpress_db_1
ルーティング
Dockerにより、Docker Containerから外部のネットワークに接続できるようIPマスカレードが行われるようなルーティングになっています。
これはIPテーブルを参照してみると分かります。
[kenji@arch ~]$ ip addr show | grep docker 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default inet 172.17.0.1/16 scope global docker0 # iptablesの情報を参照 [kenji@arch ~]$ sudo iptables-save # Generated by iptables-save v1.6.1 on Mon Jul 10 01:15:10 2017 *nat # --中略-- -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER # これがIPマスカレードの部分。Dockerのネットワーク(172.17.0.0/16)かつ、 # docker0以外のインタフェースからパケットが出力された場合IPマスカレードする -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE -A DOCKER -i docker0 -j RETURN COMMIT # --中略--
これまでの図に加えると、このような感じになりますね。
以上がDockerのネットワークの説明になります。
Docker Composeを使った場合、bridgeやルーティングはまたこれとは少し違ったものになるのですが、そこは割愛します。
次回は、いよいよマルチホストでのDocker Container間通信の話をします。
株式会社ユーザベースでは、DockerやDocker関連技術に興味のあるエンジニアを大募集中です!
シリーズ
第1回: マルチホストでのDocker Container間通信 第1回: Dockerネットワークの基礎 (当記事)
第2回: マルチホストでのDocker Container間通信 第2回: Port Forwarding と Overlay Network
第3回: マルチホストでのDocker Container間通信 第3回: Kubernetesのネットワーク(CNI, kube-proxy, kube-dns)