Dockerとホストのネットワーク on EC2


全然理解していなかったのでちゃんと勉強してみました。

## 試した環境

AMIは Amazon Linux AMI [amzn-ami-hvm-2017.03.1 (ami-3bd3c45c)] を使用しています。
インスタンスタイプは t2.micro です。
ポートは ``:22`` (ssh) だけ開けました。

予め、docker をインストールします。

```shell-session
$ sudo yum install -y docker
$ sudo service docker start
```

ブリッジを操作するコマンド ``brctl`` をインストールします。

```shell-session
$ sudo yum install -y bridge-utils
```

本投稿では ``sudo`` は省略 (alias) しています。

```shell-session
$ vi ~/.bash_profile

# これを追加
alias docker='sudo docker'
alias ip='sudo ip'
alias brctl='sudo brctl'

$ source ~/.bash_profile
```

## コンテナが2つある時のネットワーク

### 図にするとこんな感じです

![default.png](https://qiita-image-store.s3.amazonaws.com/0/60131/6551737c-298f-0845-c8f0-610779e75aa0.png "default.png")

### まずはコンテナ２つを立ち上げる

Container 1, Continer 2 を立ち上げます。実行するプロセスは ``sleep 1000000``

```shell-session
$ docker run -d --name container1 alpine sleep 1000000
$ docker run -d --name container2 alpine sleep 1000000
```

ちゃんと動いてるか確認します。

```shell-session
$ docker ps

CONTAINER ID     IMAGE     COMMAND           CREATED           STATUS          PORTS   NAMES
ac2ece448844     alpine    "sleep 1000000"   5 seconds ago     Up 4 seconds            container2
e1147e5ec006     alpine    "sleep 1000000"   14 seconds ago    Up 13 seconds           container1
```

### ホストのネットワークを見てみる

ホスト側のネットワークを見てみます。↑図と大体同じような感じになっています。

```shell-session
$ ifconfig

# "veth0000001", "veth0000002" のブリッジ
docker0   Link encap:Ethernet  HWaddr 02:42:DE:AF:0E:4C  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:deff:feaf:e4c/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:28 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1744 (1.7 KiB)  TX bytes:1068 (1.0 KiB)

eth0      Link encap:Ethernet  HWaddr 06:AC:37:4A:1A:AD  
          inet addr:172.30.1.211  Bcast:172.30.1.255  Mask:255.255.255.0
          inet6 addr: fe80::4ac:37ff:fe4a:1aad/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:9001  Metric:1
          RX packets:45727 errors:0 dropped:0 overruns:0 frame:0
          TX packets:22649 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:63486801 (60.5 MiB)  TX bytes:1659723 (1.5 MiB)

...

# 図中の "veth0000001" に該当
veth2b8058a Link encap:Ethernet  HWaddr F2:BD:7F:6A:C2:88  
          inet6 addr: fe80::f0bd:7fff:fe6a:c288/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:42 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1068 (1.0 KiB)  TX bytes:3204 (3.1 KiB)

# 図中の "veth0000002" に該当
vethd2804f4 Link encap:Ethernet  HWaddr B6:2C:76:4A:0B:DD  
          inet6 addr: fe80::b42c:76ff:fe4a:bdd/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:28 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1068 (1.0 KiB)  TX bytes:2048 (2.0 KiB)
```

ホストの routing table はこう↓です。

```shell-session
$ route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.30.1.1      0.0.0.0         UG    0      0        0 eth0
169.254.169.254 0.0.0.0         255.255.255.255 UH    0      0        0 eth0    # これはEC2 metadata用.
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.30.1.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0
```

ホストのブリッジ情報を表示してみます。``docker0`` ブリッジにvethが2つ接続されています。それぞれのvethの先に Container 1, Container 2 のネットワーク名前空間があります。

```shell-session
$ brctl show

bridge name   bridge id           STP enabled   interfaces
docker0       8000.0242deaf0e4c   no            veth2b8058a
                                                vethd2804f4
```

### コンテナの名前空間を見てみる

Dockerはプロセスの実行環境をホストOSや他コンテナと分離をするサービスです。Linuxの *ネットワーク名前空間* / *マウント名前空間* / *PID名前空間* / *etc...* 機能を利用して実現しています。

各コンテナの名前空間を探す為に、まずホスト側からコンテナプロセスのPIDを探します。 PID ``3100``, ``3211`` がそれですね。

```shell-session
$ ps auxf

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
...
root      2810  0.0  4.1 435916 42528 pts/0    Sl   03:31   0:01 /usr/bin/dockerd --default-ulimit nofile=1024:4096
root      2816  0.0  0.9 228760  9912 ?        Ssl  03:31   0:00  \_ docker-containerd -l unix:///var/run/docker/libcontainerd/...
root      3084  0.0  0.2 135792  2568 ?        Sl   03:40   0:00      \_ docker-containerd-shim ... /var/run/docker/libcontainerd/...
root      3100  0.0  0.0   1524     4 ?        Ss   03:40   0:00      |   \_ sleep 1000000
root      3188  0.0  0.2 201328  2504 ?        Sl   03:40   0:00      \_ docker-containerd-shim ... /var/run/docker/libcontainerd/...
root      3211  0.0  0.0   1524     4 ?        Ss   03:40   0:00          \_ sleep 1000000
```

この ``PID=3100 sleep 1000000`` プロセスが実行されている名前空間は↓で確認できます。このうち ``net`` がネットワーク名前空間になります。

```shell-session
$ sudo ls -l /proc/3100/ns/

total 0
lrwxrwxrwx 1 root root 0 Jul 23 04:08 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Jul 23 04:08 ipc -> ipc:[4026532159]
lrwxrwxrwx 1 root root 0 Jul 23 04:08 mnt -> mnt:[4026532157]
lrwxrwxrwx 1 root root 0 Jul 23 03:40 net -> net:[4026532162]
lrwxrwxrwx 1 root root 0 Jul 23 04:08 pid -> pid:[4026532160]
lrwxrwxrwx 1 root root 0 Jul 23 04:08 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul 23 04:08 uts -> uts:[4026532158]
```

### コンテナのネットワークを見てみる

次に ``ip`` コマンドを使って、コンテナのネットワーク名前空間上で ``bash`` プロセスを立ち上げてみます。

``ip`` コマンドは ``/var/run/netns/`` 以下のネットワーク名前空間しか扱えないので、まずはシンボリックリンクを貼ってやります。

```shell-session
$ sudo mkdir -p /var/run/netns/
$ sudo ln -s /proc/3100/ns/net /var/run/netns/container1
```

ネットワーク名前空間 ``container1`` 上で ``bash`` プロセスを立ち上げます。

```shell-session
$ ip netns exec container1 bash

root#
```

> "$ ip netns exec {ネットワーク名前空間名} {実行したいコマンド}"

コンテナ側のネットワークを盗み見る事ができました。

```
root# ifconfig

eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:45 errors:0 dropped:0 overruns:0 frame:0
          TX packets:15 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:3414 (3.3 KiB)  TX bytes:1138 (1.1 KiB)

...
```

コンテナの routing table も見てみます。

```shell-session
root# route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```


## コンテナにNICを手動で追加してみる

### 図にするとこんな感じです

![2nic.png](https://qiita-image-store.s3.amazonaws.com/0/60131/130d9aec-5aae-5e2d-428e-15d05773544c.png "2nic.png")

### 準備

まず、 **Container 2 は邪魔なので落としておきます** 。

```shell-session
$ docker stop container2
$ docker rm container2
```

Container 2 のNICへのブリッジも自動で削除されています。

```shell-session
$ brctl show

bridge name   bridge id           STP enabled   interfaces
docker0       8000.0242deaf0e4c   no            veth2b8058a
```

### veth (Virtual Ethernet) を追加する

veth (１ケーブルで接続済の仮想NICのペア) を追加します。ホスト側のブリッジ  ``docker0`` と接続する方を ``veth-host``, コンテナ側を ``veth-con`` とします。

```shell-session
$ ip link add name veth-host type veth peer name veth-con
$ ip link set veth-host up
```

ホスト側NIC ``veth-host`` をブリッジ ``docker0`` に接続します。

```shell-session
$ brctl addif docker0 veth-host
$ brctl show

bridge name   bridge id           STP enabled   interfaces
docker0       8000.0242deaf0e4c   no            veth2b8058a
                                                veth-host
```



この時はまだ ``veth-host``, ``veth-con`` 共にホスト側のネットワーク名前空間に属している為、ホスト側から両方とも見えます。

```shell-session
$ ifconfig veth-host

veth-host Link encap:Ethernet  HWaddr 3A:D3:41:66:58:F8  
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

$ ifconfig veth-con

veth-con Link encap:Ethernet  HWaddr 5E:98:45:07:48:00  
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
```

コンテナ側NIC ``veth-con`` のネットワーク名前空間を ``container1`` に変更します。

```shell-session
$ ip link set veth-con netns container1
```

ホスト側からは見えなくなります。

```shell-session
$ ifconfig veth-con

veth-con: error fetching interface information: Device not found
```

逆にコンテナ側は見える様になります。

```shell-session
$ ip netns exec container1 ifconfig veth-con

veth-con  Link encap:Ethernet  HWaddr B2:45:A8:96:1B:AA  
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
```

### コンテナ側NICの設定

まずはNIC名を ``veth-con`` → ``eth1`` に変えてやります。

```shell-session
$ ip netns exec container1 ip link set veth-con name eth1
```

> "$ ip netns exec container1 ..." → コンテナ側 (netns) でコマンド実行している

NIC ``eth1`` にIP ``172.17.0.99/24`` を割り当てます。

```shell-session
$ ip netns exec container1 ip addr add 172.17.0.99/24 dev eth1
$ ip netns exec container1 ip link set eth1 up
```

コンテナ側からIPが見れる様になります。

```shell-session
$ ip netns exec container1 ifconfig eth1

eth1      Link encap:Ethernet  HWaddr B2:45:A8:96:1B:AA  
          inet addr:172.17.0.99  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::b045:a8ff:fe96:1baa/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:5714 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4448 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:121496019 (115.8 MiB)  TX bytes:309958 (302.6 KiB)
```

### コンテナ側 routing table の変更

せっかく追加したNIC ``eth1`` が全く使われていないので、routing table を変更してやります。

```shell-session
$ ip netns exec container1 ip route delete default
$ ip netns exec container1 ip route add default via 172.17.0.1 dev eth1
```

この通り、外部アクセスが新規追加したNIC ``eth1`` 経由となりました。

```shell-session
$ ip netns exec container1 route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth1
172.17.0.0      0.0.0.0         255.255.255.0   U     0      0        0 eth1
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```

> eth0 で入って来たのが eth1 から出てしまいますが、今回は無視します

### コンテナに侵入して確認してみる

実際にコンテナに侵入して、意図したとおりに動いているか見てみます。

```shell-session
$ docker exec -it container1 sh

/ # ps aux
PID   USER     TIME   COMMAND
    1 root       0:00 sleep 1000000
    5 root       0:00 sh
   10 root       0:00 ps aux
```

元からあった eth0 (172.17.0.2) と、追加した eth1 (172.17.0.99) が見えます。

```shell-session
# ifconfig

eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:2040 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1320 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:48591073 (46.3 MiB)  TX bytes:97477 (95.1 KiB)

eth1      Link encap:Ethernet  HWaddr B2:45:A8:96:1B:AA  
          inet addr:172.17.0.99  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::b045:a8ff:fe96:1baa/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:5714 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4448 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:121496019 (115.8 MiB)  TX bytes:309958 (302.6 KiB)
...
```

routing table も先程設定したとおりです。

```shell-session
# route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth1
172.17.0.0      0.0.0.0         255.255.255.0   U     0      0        0 eth1
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```

curl をインストールしてみます。

> [apk] は [Alpine Linux] のパッケージ管理システム

```shell-session
/ # apk update

fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/community/x86_64/APKINDEX.tar.gz
v3.6.2-40-gf1c202674f [http://dl-cdn.alpinelinux.org/alpine/v3.6/main]
v3.6.2-32-g6f53cfcccd [http://dl-cdn.alpinelinux.org/alpine/v3.6/community]
OK: 8436 distinct packages available

/ # apk add curl

(1/3) Installing libssh2 (1.8.0-r1)
(2/3) Installing libcurl (7.54.0-r0)
(3/3) Installing curl (7.54.0-r0)
Executing busybox-1.26.2-r5.trigger
OK: 231 MiB in 41 packages
```

google.com にアクセスしてみます。

```shell-session
/ # curl google.com

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.jp/?gfe_rd=cr&amp;ei=3UN0WduOBrGL8QfG9JGIBQ">here</A>.
</BODY></HTML>
```


[amzn-ami-hvm-2017.03.1 (ami-3bd3c45c)]:https://aws.amazon.com/jp/amazon-linux-ami/
[Alpine Linux]:https://alpinelinux.org/
[apk]:https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management