概要
Dockerのネットワーク周りを勉強していると、
- docker0
- 仮想ブリッジ
- VXLAN
- link機能
など色んな要素が出てくるのですが、ちゃんと理解していないとすぐ忘れるため一度しっかり学んでみました。
今回はその時に疑問に思ったことをまとめてみました。
環境
- docker 1.11.2
構成
マシン | IP | 役割 |
---|---|---|
ホスト | 192.168.33.10 | Dockerホスト |
docker0 | 172.17.0.1 | 仮想ブリッジ |
nginx1 | 172.17.0.2 | コンテナ1 |
nginx2 | 172.17.0.3 | コンテナ2 |
事前知識
以下の知識があると学ぶ上で非常に助かります。
ブリッジ
第2層でMACアドレスで判別して転送
ルータ
第3層でIPで判別して転送
NAT、NAPT、IPマスカレード
用語 | 意味 |
---|---|
NAT | IPの1対1変換(GIP1個なら1つの内部マシンしか見せられない) |
NAPT | IP+portでの変換(GIP1個でも複数の内部マシンにアクセスできる) |
IPマスカレード | NAPTと同じ |
ARPテーブルとルーティングテーブル
用語 | 意味 |
---|---|
ARPテーブル | IPとMACアドレスの対応表 |
ルーティングテーブル | subnetとgwが書かれた表 |
arpの確認
# ip neigh 172.17.0.3 dev eth0 lladdr 02:42:ac:11:00:03 STALE 172.17.0.1 dev eth0 lladdr 02:42:1e:81:5f:dc STALE
routeの確認
# 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
NATとルータの違いは?
用語 | 意味 |
---|---|
NAT | アドレス変換する機能一般 |
ルータ | ルーティングテーブル持つ。IP見て転送先判断。NAPT機能持つので外部ネットワークとも接続できる。 |
ゲートウェイとデフォルトゲートウェイ
用語 | 意味 |
---|---|
ゲートウェイ | パケットの送り先がルーティングテーブルに記述されている他のネットワークへ行く場合の、パケットを飛ばす先。 |
デフォルトゲートウェイ | パケットの送り先がルーティングテーブルに記述されていない場合(要は残り物)の、飛ばす先。 |
VXLAN
ルーターを介したL3ネットワークの上に、仮想的なL2スイッチで直結されたネットワークを構築する技術
docker0
- 仮想ブリッジ
--net
で指定しないときのコンテナが所属するネットワーク
ref: How to capture packets on a local network with Pcap4J container | To Be Decided
コンテナから外部ネットワークのアクセス
docker0はブリッジなのでL2です。MACアドレスで判別して〜というやつです。
ではdocker0のネットワーク172.17.0.0/16
からeth0
の192.168.1.0/24
へはどうすれば良いのでしょうか?
これはホスト側のiptables
のIPマスカレード機能により行っています。
$ sudo iptables -t nat -L -n Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0 Chain DOCKER (2 references) target prot opt source destination RETURN all -- 0.0.0.0/0 0.0.0.0/0
下記の行がポイントで、
Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
これは送信元IPが172.17.0.0/16
(つまりdocker network)の範囲で、宛先IPが0.0.0.0/0
(つまり全ての宛先)の場合、送信元IPをNIC(eth0)のIPに変換する、という意味です。
これによってdocker networkからのパケットをNICの外に渡すことができます。
google.comへはどう繋がるの?
nginx1
コンテナからgoogle.comにpingすると以下のように繋がります。
root@1fc29982699f:/# ping google.com PING google.com (216.58.197.238): 56 data bytes 64 bytes from 216.58.197.238: icmp_seq=0 ttl=61 time=3.911 ms
tracerouteで調べるとこうなります。
# traceroute google.com traceroute to google.com (216.58.197.238), 30 hops max, 60 byte packets 1 172.17.0.1 (172.17.0.1) 0.035 ms 0.009 ms 0.007 ms 2 192.168.33.1 (192.168.33.1) 0.151 ms 0.067 ms 0.158 ms ...... 12 nrt13s49-in-f14.1e100.net (216.58.197.238) 3.663 ms 3.532 ms 3.565 ms
流れとしては以下です。
- google.comにアクセスしたい
- ルーティングテーブルに無いので
172.17.0.0/16
のデフォルトゲートウェイ172.17.0.1
へ飛ばす - IPマスカレードして送信元IPがDockerホストのeth0のIPに変換される
- Dockerホストのルーティングテーブルに無いので
192.168.33.0/24
のデフォルトゲートウェイ192.168.33.1
へ飛ばす(ルーター) - ルーターで送信元IPがIPマスカレードされてルータの持つグローバルIPに書き換わり、外のインターネットに繋がる
コンテナからコンテナへのアクセス
docker0はブリッジなので、基本的にコンテナ間は疎通できます。
nginx1コンテナからnginx2コンテナにpingしてみます。
# ping 172.17.0.3 PING 172.17.0.3 (172.17.0.3): 56 data bytes 64 bytes from 172.17.0.3: icmp_seq=0 ttl=64 time=0.069 ms 64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.076 ms
しかしIPなどは自動で割り振られるため、毎回コンテナの中に入ってIPを取得、他のコンテナに設定を教えてあげる、だと非常に手間です。
そこで使えるのがlink機能
です。
--link=コンテナ名:エイリアス
とすると、linkしたいコンテナの環境変数を保持できます。またエイリアスは/etc/hosts
に書き込まれるので、IPを知らなくてもエイリアスでドメイン名のように扱えます。
具体的にlinkさせてみる
nginx2
というコンテナを起動する際に、nginx1
に対してlinkしてあげます。
$ docker run --link=nginx1:nginx1 --name nginx2 nginx
コンテナの中に入って/etc/hosts
を見てみます。
# cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.17.0.2 nginx1 1fc29982699f 172.17.0.3 ac03febe0a9f
# ping -c2 nginx1 PING nginx1 (172.17.0.2): 56 data bytes 64 bytes from 172.17.0.2: icmp_seq=0 ttl=64 time=0.097 ms 64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.085 ms
ちなみにnginx1側は/etc/hosts
に変更はありません。
# cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.17.0.2 1fc29982699f
docker hostからコンテナへのアクセス
hostの場合docker0のbridgeに繋がっているので、先程の「コンテナからコンテナへのアクセス」と同じく疎通可能です。
外部からコンテナへのアクセス
コンテナから外部へのIPマスカレードはありますが、外部からコンテナへはありません。
これを解決するのが-p
オプションです。
$ docker run -p 80:80 --name nginx nginx
のようにして起動すると
$ sudo iptables -t nat -L -n Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0 MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80 Chain DOCKER (2 references) target prot opt source destination RETURN all -- 0.0.0.0/0 0.0.0.0/0 DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
マスカレードルールが追加され、Dockerホストではない192.168.33.0/24系のマシンからアクセス可能になります。
Macからvagrant(dockerホスト)のコンテナにアクセスしてみる
MacのIPです。
$ ifconfig vboxnet0 vboxnet0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500 ether 0a:00:27:00:00:00 inet 192.168.33.1 netmask 0xffffff00 broadcast 192.168.33.255
dockerホストのIPに対して、設定した80ポートにアクセスしてみます。
$ curl 192.168.33.10 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
ちゃんとnginxのページが見れました。
docker network
default networkでなく、ユーザ定義したネットワークだと別のメリットが有ります。
先ほどlink
機能を説明したように、default networkだと名前解決はされずIP指定でしかできませんが、ユーザが作成したネットワークだと内蔵 DNS サーバが作成されるので名前解決してくれます。
$ docker network create dns
これに紐付けてコンテナを起動します。
$ docker run --net dns --name web1 -it ubuntu:trusty root@7cda447e6b28:/#
疎通確認用にもう一つ
$ docker run -it --net dns --name web2 ubuntu:trusty root@01a1732d5577:/#
web2からweb1へpingしてみます。
root@01a1732d5577:/# ping web1 PING web1 (172.22.0.2) 56(84) bytes of data. 64 bytes from web1.dns (172.22.0.2): icmp_seq=1 ttl=64 time=0.161 ms 64 bytes from web1.dns (172.22.0.2): icmp_seq=2 ttl=64 time=0.182 ms
ちゃんと名前解決してくれます。
/etc/resove.conf
には内部DNSサーバとして127.0.0.11
が用意されています。
root@01a1732d5577:/# cat /etc/resolv.conf search local nameserver 127.0.0.11 options ndots:0