こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。
この記事ではdockerコンテナとその裏側で使用されているNetwork Namespaceについて、個人的に色々考えて/調べたので備忘録としてまとめておこうと思います。
dockerコンテナから他のdockerコンテナのプロセスは見れないってはなし
dockerのコンテナは、docker環境上で複数立ち上げることができるのですが、前提としてコンテナのプロセスは他コンテナからは見れないという特性があります。
プロセスというと難しく聞こえるかもしれません。例えば、PCでいうとタスクマネージャを開くと色々出てくると思うのですが、それとイコールだと考えればすんなり入ってくると思います。
実際に確認してみましょう。
docker環境で以下のように3つのコンテナを作成しています。
具体的にはnginx,apache,nextcloudコンテナをそれぞれ1つずつデプロイしています。
root@docker:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
83f27d094780 nextcloud "/entrypoint.sh apac…" About a minute ago Up About a minute 80/tcp sad_tu
6c498e8d2477 httpd "httpd-foreground" 6 minutes ago Up 6 minutes 80/tcp flamboyant_bhaskara
55ef2eae198a nginx "/docker-entrypoint.…" 7 minutes ago Up 7 minutes 80/tcp busy_poincare
この状態でnginxコンテナにアクセスし、psコマンドを叩いてみましょう。
コンテナにアクセスするためにはdocker execコマンドを使用します。
またコンテナにはデフォルトでpsコマンドが入っておりませんので、インストールして使用できるようにします。
root@docker:~# docker exec -it 55ef2eae198a /bin/bash
root@55ef2eae198a:/# apt update
root@55ef2eae198a:/# apt upgrade
root@55ef2eae198a:/# apt install procps
psコマンドを叩いた結果は以下となります。
nginxに関連するプロセスしかありません。
root@55ef2eae198a:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 06:25 ? 00:00:00 nginx: master process nginx -g daemon off;
nginx 29 1 0 06:25 ? 00:00:00 nginx: worker process
nginx 30 1 0 06:25 ? 00:00:00 nginx: worker process
nginx 31 1 0 06:25 ? 00:00:00 nginx: worker process
nginx 32 1 0 06:25 ? 00:00:00 nginx: worker process
root 33 0 0 07:41 pts/0 00:00:00 /bin/bash
root 457 33 0 07:48 pts/0 00:00:00 ps -ef
同じことをapacheコンテナにもやってみます。
apache関連のプロセスは出力されますが、先ほどnginxコンテナ上で出ていたnginx関連のプロセスは出ていないことがわかると思います。
root@docker:~# docker exec -it 6c498e8d2477 /bin/bash
root@6c498e8d2477:/usr/local/apache2# apt update
root@6c498e8d2477:/usr/local/apache2# apt upgrade
root@6c498e8d2477:/usr/local/apache2# apt install procps
root@6c498e8d2477:/usr/local/apache2# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 06:25 ? 00:00:01 httpd -DFOREGROUND
www-data 8 1 0 06:25 ? 00:00:00 httpd -DFOREGROUND
www-data 9 1 0 06:25 ? 00:00:00 httpd -DFOREGROUND
www-data 10 1 0 06:25 ? 00:00:00 httpd -DFOREGROUND
root 99 0 0 09:28 pts/0 00:00:00 /bin/bash
root 434 99 0 09:33 pts/0 00:00:00 ps -ef
一方でこれらのプロセスはホスト側(dockerが稼働しているサーバ。私の場合ubuntu22.04)では確認することができます。
ホスト側で同じコマンドを実行してみます。
root@docker:~# ps -ef
中略
root 26271 26249 0 15:25 ? 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 26321 26271 0 15:25 ? 00:00:00 nginx: worker process
systemd+ 26322 26271 0 15:25 ? 00:00:00 nginx: worker process
systemd+ 26323 26271 0 15:25 ? 00:00:00 nginx: worker process
systemd+ 26324 26271 0 15:25 ? 00:00:00 nginx: worker process
中略
root 26491 26469 0 15:25 ? 00:00:01 httpd -DFOREGROUND
www-data 26523 26491 0 15:25 ? 00:00:00 httpd -DFOREGROUND
www-data 26524 26491 0 15:25 ? 00:00:00 httpd -DFOREGROUND
www-data 26525 26491 0 15:25 ? 00:00:00 httpd -DFOREGROUND
この状態をイメージ図に起こしてみると以下のようになるかと思います。
コンテナどうしてプロセスを見れない一方で、ホスト側からは各コンテナのプロセスを確認することができます。
Namespaceについて
Network Namespace(以降"ns"と省略)はこのdockerコンテナを稼働させるために使用している技術であります。
なので、本当であれば各ns間のプロセスは他のnsからは見えないはずだと想定することができます。
なぜならばdockerコンテナ≒nsと考えることができるからです。
しかし、以前nsを触っているときにプロセスについて確認したところ、その想定はあっさり裏切られました。私が以前作成した記事の【未解決】部分についてがそれに該当します。
この後に本docker環境のnsについても同様に確認してみます。(が準備に少し手間がかかります。)
dockerで作成したnsがデフォルトで見れない件
dockerkコンテナをデプロイする際、nsを使用していることは前述のとおりです。nsの存在を確認するときは基本的に"ip netns list"コマンドを叩くかと思います。
そこでdocker環境下でも叩いてみます。想定であれば本環境の場合3つ(dockerコンテナが3つデプロイされているので)nsが出力されるはずです。
しかしここでも期待を裏切られます。結果は1つとして出力されないのです。
root@docker:~# ip netns list
root@docker:~#
これについて調べてみると、どうも上記"ip netns list"コマンドはサーバ内のnsを直接認識している?というよりは/var/run/netns/配下にnsに関連するものがあるかどうかで判断しているみたいです。
以下のQiita記事に次のように書かれています。
実はdockerが作るnamespaceは ip netnsコマンドで操作できないのです。。。
ip netnsで既存のnamespaceリストを表示できますが、ここでの情報は厳密には、namespaceではありません。/var/run/netns/ 配下のファイル一覧を単純に表示しているだけです。
実際に私のdocker環境において/var/run/netns/配下には何もありませんでした。ここに何もないから、ip netns listコマンドは何も吐かないということになります。
root@docker:~# ls -ltr /var/run/netns/
total 0
では、dockerコンテナ(≒ns)の情報はどこにあるのか?ということですが、それは以下の記事に書かれておりました。
どうも/proc/配下にあるようです。
上記記事を参考に私の環境でも確認してみます。
まずdockerコンテナのプロセスを確認してみます。
nginx,apache,nextcloudそれぞれPIDが26271,26491,26936であることがわかりました。
root@docker:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
83f27d094780 nextcloud "/entrypoint.sh apac…" 15 minutes ago Up 15 minutes 80/tcp sad_tu
6c498e8d2477 httpd "httpd-foreground" 20 minutes ago Up 20 minutes 80/tcp flamboyant_bhaskara
55ef2eae198a nginx "/docker-entrypoint.…" 21 minutes ago Up 21 minutes 80/tcp busy_poincare
root@docker:~# docker inspect 83f27d094780 --format '{{.State.Pid}}'
26936
root@docker:~# docker inspect 6c498e8d2477 --format '{{.State.Pid}}'
26491
root@docker:~# docker inspect 55ef2eae198a --format '{{.State.Pid}}'
26271
このプロセス番号が/proc/配下にディレクトリとしてあるようです。
確認してみると確かにあるっぽいです。これがdockerコンテナ専用のnsなんですかね?
root@docker:~# ls -l /proc/26936/ns/
total 0
lrwxrwxrwx 1 root root 0 Apr 15 15:41 cgroup -> 'cgroup:[4026532458]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 ipc -> 'ipc:[4026532400]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 mnt -> 'mnt:[4026532398]'
lrwxrwxrwx 1 root root 0 Apr 15 15:31 net -> 'net:[4026532402]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 pid -> 'pid:[4026532401]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 pid_for_children -> 'pid:[4026532401]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 15 15:41 uts -> 'uts:[4026532399]'
root@docker:~# ls -l /proc/26491/ns/
total 0
lrwxrwxrwx 1 root root 0 Apr 15 15:54 cgroup -> 'cgroup:[4026532395]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 ipc -> 'ipc:[4026532337]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 mnt -> 'mnt:[4026532335]'
lrwxrwxrwx 1 root root 0 Apr 15 15:25 net -> 'net:[4026532339]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 pid -> 'pid:[4026532338]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 pid_for_children -> 'pid:[4026532338]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 15 15:54 uts -> 'uts:[4026532336]'
root@docker:~# ls -l /proc/26271/ns/
total 0
lrwxrwxrwx 1 root root 0 Apr 15 15:55 cgroup -> 'cgroup:[4026532332]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 ipc -> 'ipc:[4026532272]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 mnt -> 'mnt:[4026532270]'
lrwxrwxrwx 1 root root 0 Apr 15 15:25 net -> 'net:[4026532274]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 pid -> 'pid:[4026532273]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 pid_for_children -> 'pid:[4026532273]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 15 15:55 uts -> 'uts:[4026532271]'
dockerのnsをip netns listで出力させるためにはこの/proc/"コンテナのPID"/ns/配下にあるnetを/var/run/netns/配下にシンボリックさせればいいようです。
以下のように実行してみます。
root@docker:~# ln -s /proc/26936/ns/net /var/run/netns/docker-ns-26936
root@docker:~# ln -s /proc/26491/ns/net /var/run/netns/docker-ns-26491
root@docker:~# ln -s /proc/26271/ns/net /var/run/netns/docker-ns-26271
root@docker:~# ls -ltr /var/run/netns/
total 0
lrwxrwxrwx 1 root root 18 Apr 15 16:26 docker-ns-26936 -> /proc/26936/ns/net
lrwxrwxrwx 1 root root 18 Apr 15 16:26 docker-ns-26491 -> /proc/26491/ns/net
lrwxrwxrwx 1 root root 18 Apr 15 16:27 docker-ns-26271 -> /proc/26271/ns/net
この状態でip netns listコマンドを実行してみると、先ほどはdockerに関するnsは出てきませんでしたが、今度は出てきました。
root@docker:~# ip netns list
docker-ns-26271 (id: 0)
docker-ns-26491 (id: 1)
docker-ns-26936 (id: 2)
これでip netnsコマンドでnsに入る方法とdocker execコマンドでdockerコンテナ(=ns)に入る方法が確立したことになります。
イメージで表すと以下のようになるでしょうか?
dockerコンテナに入り操作する方法として以下の2つの方法ができたということです。
①docker exec コマンドを使う
②ip netns exec コマンドを使う
※図は必ずも正しいとは思っておりません。docker execコマンドを叩いたときにdocker0のBridgeを通るとも思っておりませんし、ip netns execコマンドを叩いたときにNetwork Namespaceなんて未知の何かを通るとも思っていません。あくまでグラフィカルに表示させるために描いただけです。
先ほどシンボリックリンクの設定をしたのは、ディスクでいうマウントをしたようなイメージを持てると少しイメージしやすくなると思います。
ディスクもマウントしないと中身見れないと思います。シンボリックリンクを設定したことによってNetwork Namespace側にもマウントされて中身が見れるようになったということなのかなと。。。
ip netns execでns(コンテナ)に接続し、プロセスを確認してみる
では、dockerコンテナに対してip netns execコマンドでアクセスし、psコマンドを実行しプロセスを見てみます。
docker execで入りpsコマンドを叩いたところ、自分以外のコンテナのプロセスは見えておりませんでしたね。
入っている場所は同じになるので先ほどと同じ結果になるはずだと推測することができます。
実際にアクセスしてみます。下記アクセスしたnsはnginxコンテナになります。
root@docker:~# ip netns exec docker-ns-26271 bash
root@docker:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,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 link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
nginxコンテナであることの証拠も併せて提示します。
IPアドレスが同じですね。
root@docker:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
83f27d094780 nextcloud "/entrypoint.sh apac…" About an hour ago Up About an hour 80/tcp sad_tu
6c498e8d2477 httpd "httpd-foreground" About an hour ago Up About an hour 80/tcp flamboyant_bhaskara
55ef2eae198a nginx "/docker-entrypoint.…" About an hour ago Up About an hour 80/tcp busy_poincare
root@docker:~# docker inspect 55ef2eae198a | grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
nginxコンテナであることを確認したため、psコマンドを実際に叩きプロセスを確認します。
以下に結果を示しますが、nginxからapacheのプロセスが見えているので、docker execで入った時と出力が違うように見えますね。。。
root@docker:~# ps -ef
UID PID PPID C STIME TTY TIME CMD
省略
root 26271 26249 0 15:25 ? 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 26321 26271 0 15:25 ? 00:00:00 nginx: worker process
systemd+ 26322 26271 0 15:25 ? 00:00:00 nginx: worker process
systemd+ 26323 26271 0 15:25 ? 00:00:00 nginx: worker process
systemd+ 26324 26271 0 15:25 ? 00:00:00 nginx: worker process
root 26469 1 0 15:25 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 6c498e8d24774cf7250df99fd945344a3e68c7200400ade3a756dd7bb6af2f93 -address /run/containerd/co
root 26491 26469 0 15:25 ? 00:00:00 httpd -DFOREGROUND
www-data 26523 26491 0 15:25 ? 00:00:00 httpd -DFOREGROUND
www-data 26524 26491 0 15:25 ? 00:00:00 httpd -DFOREGROUND
www-data 26525 26491 0 15:25 ? 00:00:00 httpd -DFOREGROUND
root 26915 1 0 15:31 ? 00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 83f27d094780b7dbab4b0b36f4c44afbfc8f4c82ddd01a3da0ba929fe74f34e1 -address /run/containerd/co
root 26936 26915 0 15:31 ? 00:00:00 apache2 -DFOREGROUND
www-data 27026 26936 0 15:31 ? 00:00:00 apache2 -DFOREGROUND
www-data 27027 26936 0 15:31 ? 00:00:00 apache2 -DFOREGROUND
www-data 27028 26936 0 15:31 ? 00:00:00 apache2 -DFOREGROUND
www-data 27029 26936 0 15:31 ? 00:00:00 apache2 -DFOREGROUND
www-data 27030 26936 0 15:31 ? 00:00:00 apache2 -DFOREGROUND
root 27559 2 0 16:05 ? 00:00:00 [kworker/2:1-events]
root 27573 2 0 16:05 ? 00:00:00 [kworker/u8:3-events_unbound]
root 27750 2 0 16:05 ? 00:00:00 [kworker/1:2-events]
root 27769 2 0 16:05 ? 00:00:00 [kworker/0:0-events]
root 28077 2 0 16:09 ? 00:00:00 [kworker/3:2-events]
root 28419 2 0 16:22 ? 00:00:00 [kworker/3:1-events]
root 28426 2 0 16:22 ? 00:00:00 [kworker/2:2]
root 28439 2 0 16:23 ? 00:00:00 [kworker/0:1-events]
root 28449 2 0 16:23 ? 00:00:00 [kworker/1:0-mm_percpu_wq]
root 28555 23578 0 16:25 ? 00:00:00 sshd: test [priv]
test 28655 28555 0 16:25 ? 00:00:00 sshd: test@pts/2
test 28657 28655 0 16:25 pts/2 00:00:00 -bash
root 28672 28657 0 16:25 pts/2 00:00:00 sudo su -
root 28674 28672 0 16:25 pts/3 00:00:00 sudo su -
root 28675 28674 0 16:25 pts/3 00:00:00 su -
root 28676 28675 0 16:25 pts/3 00:00:00 -bash
root 28710 23578 0 16:32 ? 00:00:00 sshd: test [priv]
test 28768 28710 0 16:32 ? 00:00:00 sshd: test@pts/4
test 28769 28768 0 16:32 pts/4 00:00:00 -bash
root 28778 28769 0 16:32 pts/4 00:00:00 sudo su -
root 28779 2 0 16:32 ? 00:00:00 [kworker/u8:0-events_unbound]
root 28780 28778 0 16:32 pts/5 00:00:00 sudo su -
root 28781 28780 0 16:32 pts/5 00:00:00 su -
root 28782 28781 0 16:32 pts/5 00:00:00 -bash
root 29046 2 0 16:38 ? 00:00:00 [kworker/u8:1-events_unbound]
root 29070 10277 0 16:38 pts/1 00:00:00 bash
root 29078 29070 0 16:38 pts/1 00:00:00 ps -ef
docker execでコンテナに入るときとip netns execでnsに入るときの違い
同じdockerコンテナ(Network Namespaceから見たときはns)に入ったはずなのにpsコマンドの出力の違いが出てしまうのはなぜなのでしょうか?
この疑問について以下の記事を読むと精度は高くありませんが、イメージをつけることができました。
Dockerのネットワーク周りのトラシューをする時、
docker execでコンテナ内に入ってしまうとデバッグ用のコマンドが使えないことがあって不便なものです。
そんな時はNetwork Namespaceを切り替えることで、ホスト側のコマンド全て使える状態で対象のDockerコンテナのネットワーク環境で作業できて便利です。
つまり、docker側でコマンドを叩いているのか、ホスト側でコマンドを叩いているのかが、そもそも違っていたということなのかと思います。
簡潔に書くと以下の違いがあるということになるかと思います。
- docker execでコンテナに入る場合、コンテナ側でコマンドが実行。
- ip netns execでns(コンテナと同義)に入ると、ホスト側でコマンドが実行。
図に起こしてみます。
再三になりますが、dockerコンテナにdocker execでアクセスしていると、コンテナ上でコマンドを実行していることになるようす。
これはコンテナを使わずゲストOSを使うパターンのものとほぼ同じといえるでしょう。
例えばUbuntuにKVMをインストールして仮想化基盤を構築するパターン。
あるいはベアメタルサーバに直接インストールして使用するような仮想化基盤なども同様です。
一方でNamespaceは、「1つのOS空間をいくつかに分離する"だけ"」であるので、イメージとしては以下のようになると思います。
あくまでも空間を切っているだけなので、どのnsでpsコマンドを叩いても出力結果は同じだし、一部のnsに追加アプリをインストールしようとすると、それはホストでインストールをするのと同じであるということなのかと。。。
まとめ
docker及びNetwork Namespaceについて調査の現状をまとめておきます。
① docker runでも ip netns addでもコンテナ(≒ns)を作成できるが、nsが作成される場所が違う
docker run:/proc/配下に作成される
ip netns add:/var/run/netns/配下に作成される
② dockerコンテナをnsとして操作するためには/var/run/netns/に対してシンボリックリンクを設定する必要がある
これを実行しておくと、ホスト側のコマンドをコンテナ内で使用することが出来るので、デバックが便利(らしい?)
③ docker execでコンテナに入っているとコンテナ側でコマンドが実行され、ip netns execでns(≒コンテナ)に入っていると、ホスト側でコマンドが実行される
ホスト環境を汚したくないなら素直にdocker execで入るほうが吉?
今後調べたいこと
Network Namespaceごとにプロセスが分離していると、確認する方法。
dockerコンテナでプロセスが分離出来ているのに、そのおおもとの技術となるNetwork Namespaceでそれが実現できないのはおかしい気がしています。
おそらく、Network Namespaceだけでは名前空間を完全には分離できず、unshareコマンドなどを使用することで
出来るようになるのかな?と思っています。
今後要検証ですね。。。汗
以下を参考にしていきたい・・・!