Docker containerのnetwork namespaceでdebugしたい

コンテナからインターネットに抜けられないとか、ネットワーク設定にトラブルがありそうだという時にコンテナに入っていろいろコマンドを試したいが、scratchイメージでbashが入っていないとかログインできないことがあります。そういう時に、どうするかという話です。単的にいうと下記の３行ですが、タラタラと説明してみたいと思います。

```bash
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を使います。これは１つの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を作成します

```bash
$ ip netns add newns
```
namespace一覧の確認

```bash
$ ip netns
newns
```

作成したnamespaceを指定して、好きなコマンド/プログラムを実行

```bash
$ 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 配下のファイル一覧を単純に表示しているだけです。

```bash
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が具体的にどういう意味なのかなどは今後の課題とします。

```bash
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 できるようにする

ここまでくればやるべきことは単純明解で、

1. dockerが作ったnamespace識別子ファイルを探して
2. なんとかして /var/run/netns 配下に置く

### dockerが作ったnamespace識別子ファイルを探して

/proc/\${PID}/ns では、${PID}が利用しているすべての種類のnamespace識別子へのシンボリックリンクが置いてあります。のでまず該当のdocker containerのホスト側でのpidを調べます。

```bash
$ docker inspect ${container_id} --format '{{.State.Pid}}'
3456
```

でnamespace識別子と呼んでいるのはこれです。

```bash
$ 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 配下に置く

シンボリックリンクでいいので、シンボリックリンクを置いてあげます

```bash
$ ln -s /proc/3456/ns/net /var/run/netns/docker-ns-3456
```

ここまでくると下記のコマンドで、dockerのコンテナのネットワーク情報で好きなコマンドが実行できます。

```bash
$ 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を書きました。興味がある人は是非。

https://gist.githubusercontent.com/ukinau/e5fd0712742f467b932f11a827b04a3b/raw/70ad49c692d7bd620de75ae98c252dfe71804d0e/ip_docker



下記の要調査事項については、これから調べていきたいと思います。
*1  namespaceファイルの仕組み、statのdeviceの意味を明確に
*2  特定のシステムコールの結果、カーネルが追加するのか、ユーザランドプログラムが追加するのかを明確に