Carpe Diem

備忘録

Dockerのネットワークを理解するために覚えたことまとめ

概要

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 Minutes Networking No.17


ルータ

第3層でIPで判別して転送

3 Minutes Networking No.28


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で指定しないときのコンテナが所属するネットワーク

f:id:quoll00:20160722142617j:plain ref: How to capture packets on a local network with Pcap4J container | To Be Decided

コンテナから外部ネットワークのアクセス

docker0はブリッジなのでL2です。MACアドレスで判別して〜というやつです。
ではdocker0のネットワーク172.17.0.0/16からeth0192.168.1.0/24へはどうすれば良いのでしょうか?

これはホスト側のiptablesIPマスカレード機能により行っています。

$  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

流れとしては以下です。

  1. google.comにアクセスしたい
  2. ルーティングテーブルに無いので172.17.0.0/16デフォルトゲートウェイ172.17.0.1へ飛ばす
  3. IPマスカレードして送信元IPがDockerホストのeth0のIPに変換される
  4. Dockerホストのルーティングテーブルに無いので192.168.33.0/24デフォルトゲートウェイ192.168.33.1へ飛ばす(ルーター
  5. ルーターで送信元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します。

# 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

ソース