Dockerコンテナで開いているポートの調査

昨今では誰もが使用するLinuxコンテナ、中でもDockerは初心者でも知っているほど有名です。しかし、Dockerで開いているポートって実は調べようとすると結構面倒くさいですよね。コンテナにexecでshellを開いてss -ltnしても、コンテナには通常ssnetstatなんて入っていないわけです。

コンテナと言ってもイメージはどうせdebian系列かalpineなんだからとaptやらapkやら叩いてインストールすればssnetstatも使えるでしょう。しかし次にコンテナ起動したときには消えてます。かといってイメージに入れたくもないのです。

ではどうするか?

コンテナで分からないならホストで調べてみます

おさらい

まずは前提知識を統一しましょう。

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見てますが…)。

ちょっとスクリプトを用意するだけでコンテナに影響を与えずにいつでも確認できるようになるのは大きい。

まとめ

コンテナ内のプロセスをホストから追っかけることで、比較的簡単に(?)開いてるポートを確認することが出来た。

未分類docker,linux

Posted by first_user