最近会社で、聞かれていないけれど答えることになるかもしれないことをまとめてみました。
"docker run -p" でポート公開しているホスト側のポート番号が、複数のコンテナ間で被ったらどうなるか、です。
この程度のことなら、実験するのが手っ取り早いです。
実験環境
ホストには、VirtualBox上の "Lubuntu 18.04.4" を用いています。この環境に "Docker 19.03.6" をパッケージインストールし、これを実験環境としています。
Dockerのインストール方法には "Ubuntuにdockerをインストールする" の "リポジトリからインストール" を使用しました。
実験
まずは実験用コンテナのベースイメージを取得します。
今回はCentOSを使用しました。
$ docker pull centos
:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
:
centos latest 470671670cac 4 weeks ago 237MB
:
linuser@linuser:~$
上記のイメージをベースにコンテナAを起動します。
起動時のオプション指定で、コンテナのポート22をホストのポート10022に割り当てます。
$ docker run -it --rm -p 10022:22 --name test_a centos
CentOSの起動直後の状態です。
コンテナ内でsshdを実行していません。
これは、コンテナ内のポート使用状況を見れば分かります。
[root@4bd739598429 /]# ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
[root@4bd739598429 /]#
コンテナAが稼働中の状態で、コンテナ名だけ変更した同じコマンドを用いて、コンテナBを実行します。
$ docker run -it --rm -p 10022:22 --name test_b centos
docker: Error response from daemon: driver failed programming external connectivity on endpoint test_b (7ad7dfd88f361777e6147b65d38e7fd585abce640b70ce180ba8b7406ea30709): Bind for 0.0.0.0:10022 failed: port is already allocated.
ERRO[0000] error waiting for container: context canceled
$
まあ予想通りです。
ポート10022への "bind" の失敗により、コンテナBの起動に失敗しました。
ホストの状態
このとき、ホストはどのようになっているでしょうか?
もう少し、ポートの競合の状況を見ていきましょう。
まず、LISTENしているポートはこのような感じです。但し、Dockerに関係のない部分は省いて表示しています。
# ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
:
LISTEN 0 128 *:10022 *:* users:(("docker-proxy",pid=3171,fd=4))
:
#
このように、コンテナAのサービスが稼働していなくても、ポート転送オプションに伴うホスト上でのLISTENが行われています。
LISTENしているプロセスは、docker-proxyと呼ばれるプロセスのようです。
同じことはコンテナBでも起こります。
ホスト上で同一ポート10022でのLISTEN(bind)を試みることになるため、競合が起こるわけです。
そのため、コンテナの起動は行われません。
これらの事情は、コンテナの外で発生しています。
つまり、コンテナ内のsshサーバープロセスの稼働停止に依存しません。
もう一つ見ておきましょう。iptablesの設定状況です。
# iptables -t nat -L -n --line-numbers
:
Chain DOCKER (2 references)
num target prot opt source destination
1 RETURN all -- 0.0.0.0/0 0.0.0.0/0
2 DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:10022 to:172.17.0.2:22
#
最後のエントリが、コンテナAへのポート転送に伴うエントリです。
ポート番号10022に到着したパケットが、コンテナA(172.17.0.2)のポート番号22に送られるのが分かります。
このあたりのことは、公式ドキュメントにも少し記載されています。
まとめ
- コンテナ内のサーバープロセスの稼働停止にかかわらず、ホスト側のポートが被るようなコンテナ複数を起動することはできない。
- Dockerコンテナのポートの外部公開は、コンテナ内のサーバープロセスの稼働停止に依存しない。
- Dockerコンテナのポートの外部公開は、プロセス"docker-proxy"とiptablesが協調して行っている。
- プロセス"docker-proxy"は、ホスト側のポート待ち受けを担っている。