Dockerコンテナで開いているポートの調査
昨今では誰もが使用するLinuxコンテナ、中でもDockerは初心者でも知っているほど有名です。しかし、Dockerで開いているポートって実は調べようとすると結構面倒くさいですよね。コンテナにexec
でshellを開いてss -ltn
しても、コンテナには通常ss
やnetstat
なんて入っていないわけです。
コンテナと言ってもイメージはどうせdebian系列かalpineなんだからとapt
やらapk
やら叩いてインストールすればss
やnetstat
も使えるでしょう。しかし次にコンテナ起動したときには消えてます。かといってイメージに入れたくもないのです。
ではどうするか?
コンテナで分からないならホストで調べてみます
おさらい
まずは前提知識を統一しましょう。
docker ps -a
dockerコンテナで開いているポートを一番分かりやすく見るのはdocker ps -a
( docker container ls
-a)です。例えばmysqlの公式イメージを実行して見てみましょう。
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d --rm mysql:8
92fc40d0c4f6df595ac104d6d4f1b9a17df965d520db85a356fd099183b8c9e4
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
92fc40d0c4f6 mysql:8 "docker-entrypoint.s…" 39 seconds ago Up 36 seconds 3306/tcp, 33060/tcp some-mysql
$
なんかよく見ると3306/tcp, 33060/tcpとかが見えてるので、多分3306と33060が見えてるのかな?と思えます。しかしホスト上でss -ltn
しても3306も33060も開いていません。これらが表示しているのはあくまでコンテナ上でlistenしているポートなので、デフォルトのbridgeネットワークに繋がっている他のコンテナからしか(デフォルトでは)アクセスできません。しかも現実に開いているかどうかではなく(このケースでは開いていますが)、実行したイメージの設定を表示しているだけです。
一旦起動したmysqlコンテナを停止し
$ docker stop some-mysql
some-mysql
$
同じイメージでbashだけ起動してみます。
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d --rm mysql:8 bash -c 'sleep 60'
0a5f8b3ebfbbc2deeef6134200dede4f7536492fbd4e336f5a6b7c3ed8e78e78
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0a5f8b3ebfbb mysql:8 "docker-entrypoint.s…" 12 seconds ago Up 11 seconds 3306/tcp, 33060/tcp some-mysql
$
ポートなんて開いてないのに(execでコンテナ内を調べれば分かります)、同じ表示になってますね。なお60秒経過後に勝手に終了します。
docker ps -a
ではイメージ作成者が意図したポートは分かりますが、実際に開いているかどうかの確認には使えないことが分かりました。
docker exec
docker run/startで開始したコンテナの中に、docker execで追加のプロセスを起動します。追加するプロセスをbashなどのshellにすることにより、コンテナの中を通常のLinux同様に操作し、開いているポートを確認する方法です。
コンテナ内でssコマンドをインストールする
例によってmysqlを起動して、docker execを使ってポートを調べてみます。
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d --rm mysql:8
2a674ebed1c39fe2031978e9f9a8f389d990da07879f083aac5c8f27b8f1dedb
$ docker exec -it some-mysql bash
bash-4.4# microdnf install iproute
...
bash-4.4# ss -ltn
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 151 *:3306 *:*
LISTEN 0 70 *:33060 *:*
bash-4.4 # exit
exit
$ docker stop some-mysql
some-mysql
$
3306と33060が開いているのが分かりました。途中で使用しているmicrodnf
はOracle Linux slim用のパッケージ管理コマンドです。これを毎回ネットワーク経由で入れないといけないのが何かモヤっとするわけです。
procfsを見る
ssコマンドに頼れないとなると、次はprocfsの出番です。ps/topコマンド調査時もお世話になったあれですね。procfsはLinuxカーネルが提供してる機能なので、特定のパッケージは不要ですが、そんなに親切設計ではありません。とりあえずやってみます。
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d --rm mysql:8
2a674ebed1c39fe2031978e9f9a8f389d990da07879f083aac5c8f27b8f1dedb
$ docker exec -it some-mysql bash
bash-4.4# for f in /proc/[0-9]*/cmdline;do echo "[$f]";cat $f;echo;done
[/proc/1/cmdline]
mysqld
[/proc/118/cmdline]
bash
bash-4.4# ls -lAF /proc/1/fd/*
ls: cannot read symbolic link '/proc/1/fd/0': Permission denied
ls: cannot read symbolic link '/proc/1/fd/1': Permission denied
...
lrwx------ 1 mysql mysql 64 Jan 16 20:20 /proc/1/fd/0
l-wx------ 1 mysql mysql 64 Jan 16 20:20 /proc/1/fd/1
...
まずはps代わりにmysqldを探します。すぐpid=1がmysqldなことが分かるので、そのfdを読むのですが、Permission denied
で怒られます。そこでユーザーをmysqlにして再度execし直して再開します。
bash-4.4# exit
$ docker exec -it -u mysql:mysql some-mysql bash
bash-4.4# ls -lAF /proc/1/fd/*
...
lrwx------ 1 mysql mysql 64 Jan 16 20:20 /proc/1/fd/20 -> 'socket:[1922171]'
lrwx------ 1 mysql mysql 64 Jan 16 20:20 /proc/1/fd/21 -> 'socket:[1923100]'
lrwx------ 1 mysql mysql 64 Jan 16 20:20 /proc/1/fd/22 -> 'socket:[1923101]'
...
lrwx------ 1 mysql mysql 64 Jan 16 20:20 /proc/1/fd/3 -> 'socket:[1922173]'
...
bash-4.4$ cat /proc/1/net/tcp
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
bash-4.4$ cat /proc/1/net/tcp6
sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000000000000000000000000000:8124 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 999 0 1922171 1 0000000000000000 100 0 0 10 0
1: 00000000000000000000000000000000:0CEA 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 999 0 1922173 1 0000000000000000 100 0 0 10 0
bash-4.4$ cat /proc/1/net/unix
Num RefCount Protocol Flags Type St Inode Path
0000000000000000: 00000002 00000000 00010000 0001 01 1923101 /var/run/mysqld/mysqld.sock
0000000000000000: 00000002 00000000 00010000 0001 01 1923100 /var/run/mysqld/mysqlx.sock
bash-4.4$ exit
exit
$ docker stop some-mysql
some-mysql
$
今度は無事fdが読めてsocketが4つあることが分かりました。次にnetのtcp, tcp6, unixを調べてsocketをinode番号から特定し、unixドメインソケットが2つとipv6のtcpのポートが2つ使用されていることが分かります。tcp6の方には0x8124と0x0CEAのポートが開かれています。これらはそれぞれ33060と3306なので、これでssの結果と一致します。
目的
もっと手軽にコンテナ内で開いているポートを確認する方法を見つける
調査
コンテナのプロセスは実はホストからも普通に見えている
$ ps -ef | grep mysqld
vboxadd 133015 132996 1 05:49 ? 00:00:01 mysqld
user 133288 19312 0 05:50 pts/4 00:00:00 grep --color=auto mysqld
$
実はpidも簡単に分かる(これはコンテナ内でpid=1のルートプロセス)
$ docker inspect some-mysql | awk '/"Pid":.*/{print $2;}' | sed 's/,$//'
133015
$
コンテナ内の全プロセスも簡単に分かる
$ pstree -pT 133015
mysqld(133015)
$
つまりlsofとかすれば…
$ sudo lsof -p 133015 | grep protocol
mysqld 133015 vboxadd 20u sock 0,8 0t0 1939040 protocol: UNIX-STREAM
mysqld 133015 vboxadd 21u sock 0,8 0t0 1939039 protocol: TCPv6
mysqld 133015 vboxadd 22u sock 0,8 0t0 1938148 protocol: TCPv6
mysqld 133015 vboxadd 24u sock 0,8 0t0 1938149 protocol: UNIX-STREAM
$ sudo cat /proc/133015/net/tcp6
sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000000000000000000000000000:8124 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 999 0 1939039 1 ffff8e378664cc00 100 0 0 10 0
1: 00000000000000000000000000000000:0CEA 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 999 0 1938148 1 ffff8e37f4717200 100 0 0 10 0
$
コンテナに入らずに確認出来る(ただしlsofがコンテナまでは対応してなくて結局procfs見てますが…)。
ちょっとスクリプトを用意するだけでコンテナに影響を与えずにいつでも確認できるようになるのは大きい。
まとめ
コンテナ内のプロセスをホストから追っかけることで、比較的簡単に(?)開いてるポートを確認することが出来た。
ディスカッション
コメント一覧
まだ、コメントがありません