コンテナからインターネットに抜けられないとか、ネットワーク設定にトラブルがありそうだという時にコンテナに入っていろいろコマンドを試したいが、scratchイメージでbashが入っていないとかログインできないことがあります。そういう時に、どうするかという話です。単的にいうと下記の3行ですが、タラタラと説明してみたいと思います。
pid=$(docker inspect $CONTAINER_ID --format '{{.State.Pid}}')
sudo ln -s /proc/${pid}/ns/net /var/run/netns/tmp-container-ns
sudo ip netns exec tmp-container-ns bash
Docker Containerのネットワークについて
Docker Container(DefaultのrunC container runtime)では、Container環境とHost環境のリソースの隔離化に、Linuxカーネルの機能であるnetwork namespaceを使います。これは1つのLinux OS(カーネル)環境でネットワークの設定を複数独立して持つことを許容してくれます。
例えば
- PID 50のプロセスでは、
- 10.0.0.2のipを使い
- デフォルトゲートウェイに10.0.0.1を設定する
- PID 51のプロセスでは、
- 10.0.0.3のipを使い
- デフォルトゲートウェイに10.0.0.254を設定する
ということが可能になります。
Dockerもこのようにして、各コンテナごとにnetwork namaespaceを分けることで、コンテナごとに独立したネットワーク設定を保持しています。
どうやってnetwork namespaceを作るのか
network namespaceの作成には、「ip」コマンドが利用可能です。
新しく、network namespaceを作成します
$ ip netns add newns
namespace一覧の確認
$ ip netns
newns
作成したnamespaceを指定して、好きなコマンド/プログラムを実行
$ ip netns exec newns ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
上記コマンドでは、単純に新しいnamespace環境でipアドレスの確認をしていますが、loopback interfaceのみが表示されてると思います。
ip netnsでdockerの作ったnamespaceが表示されない?
上記で、ip netnsを使えばデバッグできるのでは?と思うと思うのですが、実はdockerが作るnamespaceは ip netnsコマンドで操作できないのです。。。
ip netnsで既存のnamespaceリストを表示できますが、ここでの情報は厳密には、namespaceではありません。/var/run/netns 配下のファイル一覧を単純に表示しているだけです。
root@vagrant:/var/run/netns# touch /var/run/netns/normalfile
root@vagrant:/var/run/netns# ip netns
RTNETLINK answers: Invalid argument
RTNETLINK answers: Invalid argument
normalfile
エラーは出るので、表示する際に該当のfileがnamespaceの識別子かどうか確認しているようですがご覧の通り、namespaceとして表示されています。
正直なところ、namespaceが内部的にどのように実装されているのかまだわかっていませんが([*1]要追加調査)、スペシャルデバイス上のファイルとして作られるようです。3h/3dが具体的にどういう意味なのかなどは今後の課題とします。
vagrant@vagrant:/var/run/netns$ stat test
File: 'test'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 3h/3d Inode: 4026532349 Links: 1
ここでわかっていただきたいことは、特定のinode番号を持ったファイルがnamespaceの識別子になっており(必ずしもone hopである必要はないnamespaceを示すinodeへのsymbolic linkでも可) 通常 ip netns add
で([*2]要追加調査)追加したnamespace識別子は /var/run/netns
に追加されるということ
ip netns で dockerが作ったnamespaceを表示し、exec できるようにする
ここまでくればやるべきことは単純明解で、
- dockerが作ったnamespace識別子ファイルを探して
- なんとかして /var/run/netns 配下に置く
dockerが作ったnamespace識別子ファイルを探して
/proc/${PID}/ns では、${PID}が利用しているすべての種類のnamespace識別子へのシンボリックリンクが置いてあります。のでまず該当のdocker containerのホスト側でのpidを調べます。
$ docker inspect ${container_id} --format '{{.State.Pid}}'
3456
でnamespace識別子と呼んでいるのはこれです。
$ ls -l /proc/3456/ns/net
lrwxrwxrwx 1 root root 0 Sep 2 08:25 /proc/3456/ns/net -> net:[4026531957]
/proc/3456/ns/net を /var/run/netns 配下に置く
シンボリックリンクでいいので、シンボリックリンクを置いてあげます
$ ln -s /proc/3456/ns/net /var/run/netns/docker-ns-3456
ここまでくると下記のコマンドで、dockerのコンテナのネットワーク情報で好きなコマンドが実行できます。
$ ip netns
docker-ns-3456
$ ip netns exec docker-ns-3456 bash
とここまで、dockerの作ったnetwork namespaceを指定してipコマンドでデバッグする方法を記述しましたが、簡単なbash scriptで、dockerコンテナのnetwork namespaceをすべて、 ip netns
で表示、利用できるようにするbash scriptを書きました。興味がある人は是非。
下記の要調査事項については、これから調べていきたいと思います。
*1 namespaceファイルの仕組み、statのdeviceの意味を明確に
*2 特定のシステムコールの結果、カーネルが追加するのか、ユーザランドプログラムが追加するのかを明確に