マルチホストDockerネットワーキング(1)
みなさんDockerを利用しているでしょうか。Dockerを使ったことのある方は、そのネットワーク周りに不便を感じたことはないでしょうか。今回はDockerのネットワーク周りを概観し、Dockerをコンテナ型仮想化エンジンとして見た場合のネットワーク周りの問題点について解説します。また次回以降の記事で、その問題点を解決する既存の取り組みやツールについて触れていきます。
Dockerとは
そもそもDockerとは、当時のdotCloud社(現Docker社)が自社のパブリックPaaSを実現するために、アプリケーションの実行環境をポータブルにしていつでも簡単に立ち上げられるように開発した技術が根幹となっています。様々なLinuxコンテナ技術を用いたリソース隔離も、コピーオンライトで差分管理を行うファイルシステムも、またOSやミドルウェアを自動構成してデプロイする仕組みも、もともとはPaaSを形づくるために開発されたものなのです。
一方で今日のDockerは、LXCやOpenVZのような、「コンピューティングリソースをオンデマンドに提供してくれるコンテナ型仮想化エンジン」としても注目されています。しかしDockerが提供するコンテナを、いわゆるHypervisorが提供する「仮想マシン」と同列に扱おうとした場合、様々な問題が表出します。ホストOSのカーネルを共有することに起因するコンテナ技術の本質的な問題から、Dockerという若いプロダクトが内包するセキュリティの問題、あるいはOVFのようなコンテナイメージの標準仕様がない問題などいろいろとありますが、今回はDockerのネットワーク周りの問題について記します。
検証した環境
以降の検証は、Ubuntu 14.04.2上に最新版のgolangとdockerをインストールして実施しています。
| Distribution | Ubuntu 14.04.2 LTS |
|---|---|
| Kernel | 3.13.0-45-generic |
| golang | 1.4.2 |
| docker Client & Server | 1.5.0 |
単一ホストでのDockerネットワーク
単一ホストでDockerコンテナを立ち上げると、次のようなネットワークが構成されます。
Dockerデーモンを立ち上げると、ホスト内部にdocker0という仮想ブリッジが作成され、docker0から外部に接続できるように、iptablesにルールが追加されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
root@dn01:~# ip addr show docker0 5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff inet 172.17.42.1/16 scope global docker0 valid_lft forever preferred_lft forever root@dn01:~# brctl show docker0 bridge name bridge id STP enabled interfaces docker0 8000.56847afe9799 no root@dn01:~# iptables-save *nat ... :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 ! -o docker0 -j MASQUERADE ... *filter ... :DOCKER - [0:0] -A FORWARD -o docker0 -j DOCKER -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 ... |
コンテナを立ち上げるとDockerがvethペアを作成し、コンテナのnetwork namespaceとdocker0に接続します。これにより、ホストOSからコンテナに通信できるようになります。またコンテナを立ち上げる際に -p オプションが指定されていれば、Dockerはポート変換ルールもiptablesに追加します。これにより、ホストの当該ポートに通信が届けば、コンテナの適切なポートに転送されるようになります。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# コンテナ1 root@dn01:~# docker run -i -t ubuntu:latest /bin/bash root@79407a760475:/# ip addr show eth0 6: eth0: <BROADCAST,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 inet 172.17.0.2/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe11:2/64 scope link valid_lft forever preferred_lft forever root@79407a760475:/# ip route default via 172.17.42.1 dev eth0 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# コンテナ2 root@dn01:~# docker run -i -t -p 80 ubuntu:latest /bin/bash root@1fa34d17239a:/# ip addr show eth0 8: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff inet 172.17.0.3/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe11:3/64 scope link valid_lft forever preferred_lft forever root@1fa34d17239a:/# ip route default via 172.17.42.1 dev eth0 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
root@dn01:~# brctl show docker0 bridge name bridge id STP enabled interfaces docker0 8000.56847afe9799 no veth833abc8 vethd67df1d root@dn01:~# iptables-save *nat ... :DOCKER - [0:0] ... -A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 80 -j MASQUERADE -A DOCKER ! -i docker0 -p tcp -m tcp --dport 49153 -j DNAT --to-destination 172.17.0.3:80 ... *filter ... :DOCKER - [0:0] ... -A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT ... |
この状態でコンテナ1⇔コンテナ2は問題無く通信できますし、ホストOSからコンテナ2の80ポートへアクセスすることもできます。
|
1 2 3 4 5 |
root@dn01:~# curl 172.17.0.3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... |
Dockerをコンテナ型仮想化エンジンとして見た場合の問題点
Dockerコンテナをワンボックスの開発環境として使うのであればこれで十分ですが、「仮想マシン」のように単独で動作するサーバとして利用したい場合、次のような問題があります。
- コンテナに与えられるIPアドレスを制御できない
- docker0に与えるIPアドレスは、dockerデーモン起動時のオプションで指定できる。しかしコンテナのIPアドレスは、docker0が所属するアドレス空間から使っていないIPアドレスをdockerが自動採番する。
- コンテナを再作成した際に、以前のIPアドレスを再利用するように指定することはできない。
- ホストOSの外部からコンテナにアクセスするのが面倒
- コンテナに与えられたIPアドレスは、ホストOSの外部からはアクセスできない。
- コンテナ内のプロセスにアクセスするためには、-pオプションを指定してホストOSへポートフォワードしておく必要がある。
これらの問題に対応するために、Dockerは「コンテナ間を接続してコンテナ名でアクセスする」仕組み( --linkオプション)を提供しています。
単一ホストでのコンテナ間接続
他のコンテナから接続されるコンテナを起動する際に、--name オプションでコンテナ名を定め、かつ --expose オプションを用いて他のコンテナに公開するポートを指定しておきます。
|
1 2 3 |
root@dn01:~# docker run -i -t --name apache --expose 80 ubuntu:latest /bin/bash root@b860b1033696:/# apt-get install apache2 -y root@b860b1033696:/# apachectl start |
このコンテナに接続するコンテナを立ち上げる際には、 --link <コンテナ名>:<エイリアス> オプションを用いて接続したいコンテナの名前を指定します。この「エイリアス」として指定したキーワードをホスト名として、立ち上げたコンテナの/etc/hostsに接続先コンテナが設定されます。また公開されているポート番号なども、立ち上げたコンテナの環境変数として設定されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
root@dn01:~# docker run -i -t --link apache:httpd ubuntu:latest /bin/bash root@058d49d85f7b:/# printenv | grep HTTPD HTTPD_PORT_80_TCP=tcp://172.17.0.4:80 HTTPD_PORT_80_TCP_PORT=80 HTTPD_PORT_80_TCP_ADDR=172.17.0.4 HTTPD_PORT=tcp://172.17.0.4:80 HTTPD_NAME=/angry_blackwell/httpd HTTPD_PORT_80_TCP_PROTO=tcp root@058d49d85f7b:/# cat /etc/hosts 172.17.0.11 058d49d85f7b 127.0.0.1 localhost 172.17.0.4 httpd |
これにより、接続先コンテナのIPアドレスがわからなくても、指定したエイリアスを用いて公開ポートへアクセスすることができます。
|
1 2 3 4 5 6 7 8 9 10 11 |
root@058d49d85f7b:/# curl httpd <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... root@058d49d85f7b:/# curl $HTTPD_PORT_80_TCP_ADDR:$HTTPD_PORT_80_TCP_PORT <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... |
異なるホストのコンテナへの接続
上記のlink機能は、単一ホストのDockerコンテナ間では上手く動作します。では、異なるホスト上のコンテナへ接続したい場合はどうすれば良いのでしょうか。この場合、Ambassador Patternを用いると良いとDockerは言っています。異なるホストを仲立ちするコンテナ(ambassador:大使)を介して、コンテナを接続するのです。
まずは単一ホストの場合と同様に、ホスト1上で接続されるコンテナを立ち上げます。
|
1 2 3 |
root@dn01:~# docker run -i -t --name apache --expose 80 ubuntu:latest /bin/bash root@3bfc90ad3621:/# apt-get install apache2 -y root@3bfc90ad3621:/# apachectl start |
次にホスト1上で、ambassadorコンテナを立ち上げます。この際、接続されるコンテナが公開しているポートは、自動的にホスト1へポートフォワードされます。
|
1 |
root@dn01:~# docker run -d --link apache:httpd --name httpd_ambasssador svendowideit/ambassador |
今度はホスト2上で、ambassadorコンテナを立ち上げます。この際、ホスト1のIPアドレスとフォワードされているポートを環境変数として与えます。
|
1 |
root@dn02:~# docker run -d --name httpd_ambassador --expose 80 -e HTTPD_PORT_80_TCP=tcp://10.142.51.196:80 svendowideit/ambassador |
最後に、ホスト1のコンテナに接続するDockerコンテナをホスト2上で立ち上げます。この際、link先としてambassadorコンテナを指定します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
root@dn02:~# docker run -i -t --link httpd_ambassador:httpd ubuntu:latest /bin/bash root@03d050986faa:/# printenv | grep HTTPD HTTPD_PORT_80_TCP=tcp://172.17.0.6:80 HTTPD_ENV_HTTPD_PORT_80_TCP=tcp://10.142.51.196:80 HTTPD_PORT_80_TCP_PORT=80 HTTPD_PORT_80_TCP_ADDR=172.17.0.6 HTTPD_PORT=tcp://172.17.0.6:80 HTTPD_NAME=/drunk_darwin/httpd HTTPD_PORT_80_TCP_PROTO=tcp root@03d050986faa:/# cat /etc/hosts 172.17.0.7 03d050986faa 127.0.0.1 localhost 172.17.0.6 httpd |
これにより、異なるホストのコンテナへも、指定したエイリアスを用いて公開ポートへアクセスすることができます。
|
1 2 3 4 5 6 7 8 9 10 11 |
root@03d050986faa:/# curl httpd <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... root@03d050986faa:/# curl $HTTPD_PORT_80_TCP_ADDR:$HTTPD_PORT_80_TCP_PORT <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... |
Docker link機能の問題点
これで万事問題がなければ幸せなのですが、そうもいきません。link機能とambassadorを用いて全てのコンテナの接続情報を管理する場合、コンテナ数が多くなると設定が非常に煩雑になるため、そのままではおそらく破綻します。 progrium/ambassadordのような、分散KVSを用いた動的Ambassadorの実装を検討したほうが良いでしょう。
ただし動的Ambassadorを実現できたとしても、コンテナに付与されたIPアドレスがコンテナが起動しているホストに閉じており、外部のネットワークからアクセスできないという問題は依然として残ったままです。この問題を解決するためには、何らかの手段でDockerのネットワークを拡張しなければなりません。
Dockerネットワーク拡張ツール
このようなDockerのネットワークの諸問題に困っている人はたくさんいるようで、様々なDockerネットワーク拡張ツールが公開されています。jpetazzo/pipework、coreos/flannel、 weaveworks、rancher、socketplane・・・
次回以降の記事では、これらのDockerネットワーク拡張ツールについて触れていきたいと思います。
次回は
ということで次回は、GREとjpetazzo/pipeworkを用いたマルチホストでのDockerネットワーク拡張について解説します。