Dockerで経路制御によりブリッジネットワーク間で通信してみる！

Dockerのブリッジネットワークを使えばコンテナ同士をネットワーク経由で接続させる事ができます。ブリッジネットワークはユーザーが任意に作成することができ、異なるブリッジネットワークに接続しているコンテナは通信できない仕組みになっています。が、ブリッジネットワーク間にコンテナを作成し、経路制御して異なる２つのブリッジネットワークに接続しているコンテナ間で通信させてみようと思います。

やりたいことはこんな感じ。

![スクリーンショット 2020-06-19 13.44.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115251/934a8278-d784-ce77-bf20-07699f516dfd.png)

centos-net1 コンテナと centos-net2 コンテナ が通信できるようにします。図の通り、２つのコンテナは異なるブリッジネットワークに接続されているので centos-router コンテナがパケットのフォワーディングを担当します。

# ルーティング（経路制御）ってなんだっけ

Wikipedia先生によるとこう定義されています。

>ルーティング（英: routing）あるいは経路制御（けいろせいぎょ）とは、データを目的地まで送信するために、コンピュータネットワーク上のデータ配送経路を決定する制御の事である。

簡単に言ってしまえばパケットをサブネットを超えて送信したいときの制御のことですね。同じをサブネット内であればIPアドレスを元に直接宛先のホストへパケットの送信要求を投げることができます。しかし自分が所属していないネットワークに存在するホスト宛にパケットを送信したい場合、ルーティングが必要になります。

ルーティングテーブルとはルーティングをするときに、パケットをどのホストに対して投げるかを決定する際に用いる表のことです。例えばこんな感じ。

``` bash
# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth0
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```

![スクリーンショット 2020-06-19 13.46.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115251/c28a3ff1-057f-3b07-f7ef-1b4429a2307d.png)


ルーティングテーブルは以下のように使用されます。

1. 宛先IPアドレスと一致するエントリがあるか調べる。あるのであれば、Gatewayに記載されているIPアドレスに向けてパケットを投げる。このとき、Gatewayが <code>0.0.0.0</code> に設定されているものは自身の所属するサブネットのためルーティングは不要になる（ここは明記されているドキュメントを見つけられなかったので要調査です）。
2. 宛先IPアドレスと一致するエントリがない場合、デフォルトルートのGatewayに向けてパケットを投げる。デフォルトルートはDestinationが <code>0.0.0.0</code> になっているエントリ。

# ネットワークとコンテナの作成

ではではDockerでルーティングテーブルをいじくり、２つのブリッジネットワークをつなげてみましょう。

ブリッジネットワークを２つ作成します。図と同じようにするため、サブネットアドレスを指定しています。

``` bash
$ docker network create net1 --subnet=172.19.0.0/16
d40bf931a362d32c2b1f37470fa24247d290e017f74ceb4fea3b38c947d87fb1
$ docker network create net2 --subnet=172.20.0.0/16
863b0f24ad6f99207733ba13e28f6dc16d265693112186c0521fd4cb8e49f972
```

コンテナをそれぞれ立ち上げ、作成したブリッジネットワークにつなげます。コンテナの起動オプションとして <code>privileged</code> を指定していますが、これはルーティングテーブルを変更するために必要な権限周りのオプションです。

``` bash
$ docker run -dit --name centos-net1 --privileged --net="net1" centos:centos7
2f08362d149303f33a49c4a718063437ad842228d83850a005e937db0b89f69f
$ docker run -dit --name centos-net2 --privileged --net="net2" centos:centos7
997e496125cc5b5f29e2bd7c48850b1bfa001aea334d6e5f8afea3906d4c0d9a
$ docker run -dit --name centos-router --net="net1" centos:centos7
a9f0e3a4d0bdb9d8d378a317f8cfce31ca3504d13897a9f6ad9909bcdbd4ce4d
$ docker network connect net2 centos-router
```

それぞれのコンテナに接続してルーティングテーブルとパケット通信を監視するソフトウェアをインストールします。

``` bash
# yum -y install net-tools tcpdump traceroute
```

# ルーティングテーブルの確認

centos-net1 のルーティングテーブルを確認してみます。ルーティングテーブルは <code>route</code>コマンドで表示できます。

``` bash
# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth0
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```

Destinationが <code>0.0.0.0</code> となっているエントリはデフォルトルートですね。デフォルトルートはどのエントリにも一致しない場合にどのIPアドレス宛にパケットを送り出すかを表すエントリです。上記の場合は自身の所属するサブネット以外への通信はすべて <code>172.19.0.1</code> にパケットを送り出す設定になっています。なので、pingを centos-net2 コンテナに向けて打っても <code>172.19.0.1</code> に向けてパケットが飛ばされるので届きません。試しに打ってみましょう。

centos-net2 コンテナのIPアドレスは <code>172.20.0.2</code>ですね。

``` bash
$ docker network inspect net2
〜一部を抜粋〜
"Containers": {
            "997e496125cc5b5f29e2bd7c48850b1bfa001aea334d6e5f8afea3906d4c0d9a": {
                "Name": "centos-net2",
                "EndpointID": "cc7611fcb5ae71c7a8a79fb658a374b98c15d95b4f42848b7eb70fac05317208",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/16",
                "IPv6Address": ""
            },
            "a9f0e3a4d0bdb9d8d378a317f8cfce31ca3504d13897a9f6ad9909bcdbd4ce4d": {
                "Name": "centos-router",
                "EndpointID": "6a8f1aa134751cd3232a214e08599ac0f00cf37ad06cb714a99a8d3fa92b37c1",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/16",
                "IPv6Address": ""
            }
        },
〜一部を抜粋〜
```

centos-net1 コンテナから centos-net2 コンテナに向けてpingを打ちます。

``` bash
# ping 172.20.0.2
PING 172.20.0.2 (172.20.0.2) 56(84) bytes of data.
^C
--- 172.20.0.2 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2006ms
```

100% packet loss と表示されて通信できていないことがわかりますね。デフォルトルートである <code>172.19.0.1</code> にパケットが送出されているかをネットワークの経路を表示できる <code>traceroute</code> コマンドで確認してみます。

``` bash
# traceroute 172.20.0.2 -n
traceroute to 172.20.0.2 (172.20.0.2), 30 hops max, 60 byte packets
 1  172.19.0.1  2.570 ms  2.281 ms  2.052 ms
```

# ルーティングテーブルの変更

ルーティングテーブルを変更して centos-net1 と centos-net2 コンテナが通信できるようにします。centos-net1 のルーティングテーブルで <code>172.20.0.0/16</code> のネットワークに向けたパケットを centos-router コンテナに向けて投げるようにし、centos-router コンテナがパケットをフォワーディング（転送）するようにします。

``` bash
# route add -net 172.20.0.0 netmask 255.255.0.0 gw 172.19.0.3 eth0
# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth0
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
172.20.0.0      172.19.0.3      255.255.0.0     UG    0      0        0 eth0
```

centos-net2 でも応答ができるよう同じ要領でルーティングテーブルに変更を加えます。

``` bash
# route add -net 172.19.0.0 netmask 255.255.0.0 gw 172.20.0.3 eth0
# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.20.0.1      0.0.0.0         UG    0      0        0 eth0
172.19.0.0      172.20.0.3      255.255.0.0     UG    0      0        0 eth0
172.20.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
```

centos-router ではIPフォワーディングが有効になっているかを確認します。

``` bash
# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
```

![スクリーンショット 2020-06-19 13.47.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115251/fb0cf8f6-6db9-f058-010e-d1fc757b16d3.png)


ここまで設定できたら centos-net1 から centos-net2 に向けてpingを打ってみましょう。

``` bash
# ping 172.20.0.2
PING 172.20.0.2 (172.20.0.2) 56(84) bytes of data.
64 bytes from 172.20.0.2: icmp_seq=1 ttl=63 time=2.63 ms
64 bytes from 172.20.0.2: icmp_seq=2 ttl=63 time=0.318 ms
64 bytes from 172.20.0.2: icmp_seq=3 ttl=63 time=0.177 ms
64 bytes from 172.20.0.2: icmp_seq=4 ttl=63 time=0.164 ms
64 bytes from 172.20.0.2: icmp_seq=5 ttl=63 time=0.191 ms
^C
--- 172.20.0.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4075ms
rtt min/avg/max/mdev = 0.164/0.696/2.634/0.970 ms
```

疎通できましたね。

パケットが centos-router を経由しているかも確認してみましょう。

``` bash
# traceroute 172.20.0.2 -n
traceroute to 172.20.0.2 (172.20.0.2), 30 hops max, 60 byte packets
 1  172.19.0.3  0.258 ms  0.114 ms  0.340 ms
 2  172.20.0.2  0.461 ms  0.422 ms  0.128 ms
```

centos-router （<code>172.19.0.3</code>）を経由しているのがわかりますね。

centos-router のTCPパケットをキャプチャしてみるとIPフォワーディングの様子を見ることができます。

``` bash
# tcpdump -i eth0 -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
01:48:39.190754 IP 172.19.0.2 > 172.20.0.2: ICMP echo request, id 22, seq 53, length 64
01:48:39.190981 IP 172.20.0.2 > 172.19.0.2: ICMP echo reply, id 22, seq 53, length 64
01:48:40.213036 IP 172.19.0.2 > 172.20.0.2: ICMP echo request, id 22, seq 54, length 64
# tcpdump -i eth1 -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
01:49:06.738801 IP 172.19.0.2 > 172.20.0.2: ICMP echo request, id 22, seq 80, length 64
01:49:06.738874 IP 172.20.0.2 > 172.19.0.2: ICMP echo reply, id 22, seq 80, length 64
01:49:07.763733 IP 172.19.0.2 > 172.20.0.2: ICMP echo request, id 22, seq 81, length 64
```

<code>172.19.0.0/16</code> に所属するeth0インターフェイスと <code>172.20.0.0/16</code> に所属するeth1インターネットそれぞれでパケットをフォワーディングしている様子が見れました。

# 使用したアイコン

Icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon"> www.flaticon.com</a>