RHEL7RC+EPEL版Dockerの前提で解説します。RHEL7RCを最小構成で入れて、次の手順でDockerを導入します。
# yum -y install bridge-utils net-tools # yum -y install http://download.fedoraproject.org/pub/epel/beta/7/x86_64/epel-release-7-0.1.noarch.rpm # yum -y install docker-io # systemctl enable docker.service
Dockerが設定するiptablesの内容を見るために(見やすくするために)、firewalldを停止した上でdockerサービスを起動します。
# systemctl stop firewalld.service # systemctl mask firewalld.service # systemctl restart docker.service
まずは普通の説明
dockerサービスを起動すると、コンテナ接続用の仮想ブリッジ「docker0」が用意されます。
# brctl show bridge name bridge id STP enabled interfaces docker0 8000.56847afe9799 no # ifconfig docker0 docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500 inet 172.17.42.1 netmask 255.255.0.0 broadcast 0.0.0.0 inet6 fe80::5484:7aff:fefe:9799 prefixlen 64 scopeid 0x20<link> ether 56:84:7a:fe:97:99 txqueuelen 0 (Ethernet) RX packets 10956 bytes 600362 (586.2 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 19642 bytes 27720858 (26.4 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
このブリッジは、物理NICとは接続していないので、外部ネットワークとコンテナが直接に通信するためのものではありません。iptablesを見ると次のようになっています。若干、変なルール設定ですが、少なくとも、docker0に接続したコンテナから外部ネットワークへは、IPマスカレードで出ていけるようになっています。
# iptables-save | grep -v "^#" *nat :PREROUTING ACCEPT [1:100] :INPUT ACCEPT [1:100] :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] :DOCKER - [0:0] -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER -A POSTROUTING -s 172.17.0.0/16 ! -d 172.17.0.0/16 -j MASQUERADE COMMIT *filter :INPUT ACCEPT [133:10308] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [72:9600] -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i docker0 ! -o docker0 -j ACCEPT -A FORWARD -i docker0 -o docker0 -j ACCEPT COMMIT
CentOS6のイメージをダウンロードして、コンテナを起動してみます。
# docker pull centos # docker run -it centos /bin/bash
コンテナ内部にはeth0があって、後でみるように、これはブリッジ「docker0」に接続されています。docker0と同じサブネットのIPが割り当てられており、docker0にpingが届きます。IPマスカレードでインターネットまで出て行くこともできます。
bash-4.1# ifconfig eth0 eth0 Link encap:Ethernet HWaddr 26:B7:4A:7F:01:BC inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::24b7:4aff:fe7f:1bc/64 Scope:Link UP BROADCAST RUNNING MTU:1500 Metric:1 RX packets:7 errors:0 dropped:2 overruns:0 frame:0 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:558 (558.0 b) TX bytes:648 (648.0 b) bash-4.1# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 172.17.42.1 0.0.0.0 UG 0 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 bash-4.1# yum -y install iputils traceroute bash-4.1# ping -c1 172.17.42.1 PING 172.17.42.1 (172.17.42.1) 56(84) bytes of data. 64 bytes from 172.17.42.1: icmp_seq=1 ttl=64 time=0.104 ms --- 172.17.42.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.104/0.104/0.104/0.000 ms bash-4.1# traceroute -I 8.8.8.8 traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets 1 172.17.42.1 (172.17.42.1) 0.096 ms 0.018 ms 0.012 ms 2 192.168.122.1 (192.168.122.1) 0.234 ms 0.212 ms 0.204 ms ...(中略)... 15 209.85.243.156 (209.85.243.156) 72.423 ms 71.965 ms 71.918 ms 16 209.85.244.25 (209.85.244.25) 90.231 ms 89.823 ms 89.788 ms 17 * * * 18 google-public-dns-a.google.com (8.8.8.8) 70.944 ms 70.928 ms 71.182 ms
コンテナを起動したまま、別端末からホストLinuxに再ログインして、ブリッジdocker0を見るとコンテナ内のeth0の片割れのvethが接続されています。
# brctl show bridge name bridge id STP enabled interfaces docker0 8000.56847afe9799 no veth452d # ifconfig veth452d veth452d: flags=67<UP,BROADCAST,RUNNING> mtu 1500 inet6 fe80::874:87ff:fe84:6aa2 prefixlen 64 scopeid 0x20<link> ether 0a:74:87:84:6a:a2 txqueuelen 1000 (Ethernet) RX packets 4479 bytes 310641 (303.3 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 7727 bytes 10625737 (10.1 MiB) TX errors 0 dropped 1 overruns 0 carrier 0 collisions 0
veth(Virtual Ethernet)は、クロスケーブルで直結された仮想NICのペアを作るLinuxの機能で、ポンチ絵にするとこんな状態になります。
ちなみに、コンテナ内のIPアドレスがどのように設定されているか気になるかも知れません。
「DHCPじゃないの?」
と思ったあなた、
残念。。。。。。OpenStackの勉強のし過ぎです。。。。。。。
コンテナの中で動いているのは、bashだけです。どこにもDHCPクライアントはありません。
bash-4.1# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 05:45 ? 00:00:00 /bin/bash root 79 1 0 06:13 ? 00:00:00 ps -ef
実は、このアドレスは、Dockerがコンテナを作成した際にコンテナの中に入り込んで、独自にIPアドレスを設定しています。もう少し正確にいうと、このコンテナの「ネットワークネームスペース(netns)」に入り込んで設定しています。
コンテナのnetnsに潜り込む
それでは、ちょっとしたお遊びで、ホストLinuxからコンテナ内のnetnsに潜り込んでみましょう。コンテナは、プロセスの実行環境を分離する機能ですが、「ネットワーク環境の分離=netns」「ファイルシステムの分離=mntns」「プロセステーブルの分離=pidns」のように分離するリソースごとに別々のネームスペース機能で分離しています。したがって、ホストLinuxからコンテナ内のnetnsに潜り込むと、ファイルシステムやプロセステーブルはコンテナの外側(ホストLinux)なのに、ネットワーク設定だけコンテナの中が見える、という面白い状態になります。
netnsを操作する定番ツールはipコマンドですが、このコンテナのnetnsをipコマンドで管理できるようにちょっとした仕込みをします。まず、コンテナ内で起動しているbashのホストLinux上でのPIDを調べます。dockerデーモンの子プロセスのbashを探せばOKです。
# ps -ef | grep "docker -[d]" root 9498 1 0 13:17 ? 00:00:02 /usr/bin/docker -d # ps -ef | grep bash | grep 9498 root 10170 9498 0 18:00 pts/2 00:00:00 /bin/bash
このプロセスの/proc情報の中に、このプロセスが属するnetnsを操作するFDへのリンクがあります。
# ls -l /proc/10170/ns/net lrwxrwxrwx. 1 root root 0 4月 24 18:01 /proc/10170/ns/net -> net:[4026532160]
/var/run/netns/以下からシンボリックリンクを張ると、ipコマンド管理下のnetnsとして認識されます。
# ln -s /proc/10170/ns/net /var/run/netns/hoge # ip netns hoge
ここまでくれば、Neutronのデバッグで鍛えたip netnsを駆使してやり放題ですね。このnetns内部でbashを起動します。
# ip netns exec hoge bash
次のようにコンテナ内のネットワーク環境が丸見えです。
# ifconfig eth0 eth0: flags=67<UP,BROADCAST,RUNNING> mtu 1500 inet 172.17.0.2 netmask 255.255.0.0 broadcast 0.0.0.0 inet6 fe80::8874:a6ff:fe1c:1b2a prefixlen 64 scopeid 0x20<link> ether 8a:74:a6:1c:1b:2a txqueuelen 1000 (Ethernet) RX packets 8 bytes 648 (648.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 8 bytes 648 (648.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 # route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 172.17.42.1 0.0.0.0 UG 0 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
あくまで、netnsだけを切り替えているので、ネットワーク以外の環境(ファイルシステムやプロセステーブル)は、ホストLinuxと同じ状態である点に注意してください。bashを終了すると、ホストLinuxのネットワーク環境に戻ります。
# exit
Dockerがコンテナを作るタイミングで、ホストLinux側から次のような操作をして、コンテナ内のIPアドレスを設定しているものと考えられます。
・ホストLinux側でvethペアを作成する。
・その片割れをコンテナのnetnsに突っ込んで、コンテナからeth0として見えるようにする。
・コンテナのnetnsに入り込んで、コンテナ内のeth0にIPアドレスをセットする。
コンテナにNICを追加する
前述の手法を応用すると、起動中のコンテナに対して、ホストLinux上でvethペアを作成して、後からコンテナにNICを追加することができてしまう気もします。論より証拠で、実際にやってみましょう。
目標はこんな感じです。ブリッジ「br0」が宙に浮いていますが、これを物理NICに接続すれば、コンテナと外部ネットワークを直結することが可能になります。
まずブリッジ「br0」を作成します。
# brctl addbr br0 # ip link set br0 up # ip addr add 192.168.200.1/24 dev br0
続いて、vethのペアを作成します。ここでは、[veth-host]----[veth-guest]という名前で作成した上で、ホスト側(veth-host)をbr0に接続します。
# ip link add name veth-host type veth peer name veth-guest # ip link set veth-host up # brctl addif br0 veth-host # brctl show br0 bridge name bridge id STP enabled interfaces br0 8000.b638185b3372 no veth-host # ifconfig veth-host veth-host: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 ether b6:38:18:5b:33:72 txqueuelen 1000 (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 # ifconfig veth-guest veth-guest: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 ether d6:52:68:0a:1d:0e txqueuelen 1000 (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
この時点ではゲスト側のveth-guestも見えていますが、次のコマンドでコンテナのnetnsに突っ込むと、ホストからは見えなくなります。
# ip link set veth-guest netns hoge # ifconfig veth-guest veth-guest: error fetching interface information: Device not found
逆にコンテナ側で、veth-guestが見えるようになっています。
bash-4.1# ifconfig veth-guest veth-guest Link encap:Ethernet HWaddr D6:52:68:0A:1D:0E 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:1000 RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
続いて、コンテナ内のNICの設定を行います。これも、先ほどの「ip netns exec」を駆使してホスト側で行います。まず、コンテナ内でのデバイス名を「eth1」に変更します。
# ip netns exec hoge ip link set veth-guest name eth1
「eth1」に対するIPの設定を行います。ルーティングテーブルも変更して、eth1側をデフォルトゲートウェイにします。
# ip netns exec hoge ip addr add 192.168.200.101/24 dev eth1 # ip netns exec hoge ip link set eth1 up # ip netns exec hoge ip route delete default # ip netns exec hoge ip route add default via 192.168.200.1
これで完成です。ホストからpingも通ります。
# ping -c1 192.168.200.101 PING 192.168.200.101 (192.168.200.101) 56(84) bytes of data. 64 bytes from 192.168.200.101: icmp_seq=1 ttl=64 time=0.082 ms --- 192.168.200.101 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.082/0.082/0.082/0.000 ms
コンテナ内では次のように設定されています。
bash-4.1# ifconfig eth1 eth1 Link encap:Ethernet HWaddr D6:52:68:0A:1D:0E inet addr:192.168.200.101 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::d452:68ff:fe0a:1d0e/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 b) TX bytes:648 (648.0 b) bash-4.1# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.200.1 0.0.0.0 UG 0 0 0 eth1 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 192.168.200.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
すばらしい。。。。。
ちなみにコンテナを停止すると、後から追加したvethペアもうまく消してくれるようです。ただし、/var/run/netns以下のシンボリックリンクが残るので、これは手で削除する必要があります。これは、「ip netns」で操作するためだけに必要なので、上記の設定が終わったタイミングですぐに削除しても構わないでしょう。
で。。。。
実は、上記の作業を全部やってくれるシェルスクリプト(pipework)がすでに公開されてます。興味のある方は、スクリプトの中身を読み解いてみてください。