Dockerのネットワークの仕組み

# はじめに

今回は、Dockerのネットワークの仕組みについて解説します。対象読者はL2, L3の知識がある方となっております。

## Dockerのネットワークの仕組み

先に結論を出すと、以下のような構成になっています。(公式ドキュメントより)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/a867e562-fba8-cd0a-d2d6-66df1cb0f9f2.png)

Ref: https://docs.docker.com/engine/tutorials/networkingcontainers

こんなのはググればそこら辺に転がっていますが、今回は実際にコマンドを叩いて確認します。

## Docker起動

今回はUbuntu上にDockerを起動させました。

https://matsuand.github.io/docs.docker.jp.onthefly/engine/install/ubuntu

Dockerは起動時に以下のことを行います。実際に見ていきましょう。

- ルーティングを作成
- 仮想ブリッジであるdocker0を作成
- コンテナのIPからeth0のIPに変換するのNAPTルールを作成

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

新しいルーティングが追加されていますね。

```console:
$ ip route

default via 172.31.32.1 dev eth0 proto dhcp src 172.31.33.239 metric 100 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.31.0.2 via 172.31.32.1 dev eth0 proto dhcp src 172.31.33.239 metric 100 
172.31.32.0/20 dev eth0 proto kernel scope link src 172.31.33.239 metric 100 
172.31.32.1 dev eth0 proto dhcp scope link src 172.31.33.239 metric 100
```

送信元IPが172.17.0.1で送信先IPが172.17.0.0/16の場合はdocker0にパケットを送信します。

### 仮想ブリッジのdocker0を確認

仮想ブリッジ [^1] であるdocker0が作成されていることが分かります。

```console
$ ip address show

3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:e1:36:00:e8 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
```

L2スイッチの役割を果たすのでMACアドレステーブルもあるということです。brctlコマンドをインストールします。

```console:
$ sudo apt-get install bridge-utils -y
```

まだコンテナが立ち上がってないのでMACアドレステーブルには何も表示はされていないです。
```console:
$ brctl showmacs docker0
port no	mac addr		is local?	ageing timer
```

### NATテーブルを確認

NATテーブルを確認することでルールが追加されているのが確認できます。

```console:
$ sudo iptables -t nat -nL

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0  
```

送信元IPが172.17.0.0/16（Dockerのネットワーク）かつ送信先IPが0.0.0.0/0なら、送信元IPを物理NIC（eth0）のIPに変換し、外部のネットワークにアクセスできるようになります。

## コンテナ起動

Nginxのコンテナを立ち上げてみます。
```
docker run --name nginx -d -p 80:80 nginx
```

コンテナが起動すると以下のことを行います。実際に見ていきましょう。

- コンテナ専用のNetwork Namespaceとdocker0をveth[^2]にて接続
- 外部からのポートフォワードルールの作成

### Virtual Ethernet Pairの確認

コンテナを立ち上げると、新しいインターフェースが作成されています。vethは2つのインターフェースがペアで作成され、インターフェース間で通信が行うことができます。つまり、トンネリングです。

```console
$ ip address show

7: veth6c4af0c@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether aa:72:12:12:e8:c7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::a872:12ff:fe12:e8c7/64 scope link 
       valid_lft forever preferred_lft forever
```

```console
$ brctl show docker0

bridge name	bridge id		STP enabled	interfaces
docker0		8000.0242e13600e8	no		veth6c4af0c
```

コンテナ側のvethも確認していきましょう。Nginxコンテナに入ります。
```
docker exec -it nginx bash
```

ipコマンドが入っていなかったので以下のコマンドでインストールします。
```
apt-get update && apt-get install iproute2 -y
```

コンテナは自動的にIPアドレスが割り当てられており、コンテナからはeth0として見えていることが分かります。
```console
$ ip address show

10: eth0@if11: <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
```

### NATテーブルを確認

NATテーブルを確認することでルールが追加されているのが確認できます。

```console
$ sudo iptables -t nat -nL

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:80

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.2:80
```

PREROUTINGにてDOCKERチェーンが呼ばれており、ホストの80番の通信をDNATしていることが分かります。

## さいごに

L2スイッチを触ったことがあるものの、仮想ネットワークに関してはあまり詳しくなかったので勉強になりました。これからDockerについてさらに深ぼって行く予定です。

## 参考文献

https://christina04.hatenablog.com/entry/2016/07/22/193000

https://qiita.com/hana_shin/items/118319fea15d327dac14

https://tech-blog.rakus.co.jp/entry/20220301/iptables

https://christina04.hatenablog.com/entry/iptables-outline

[^1]: 仮想ブリッジとはLinux上に仮想的なL2スイッチを構成する機能です。
[^2]: Virtual Ethernet Deviceの略。つまり、仮想ネットワークインターフェースのことです。 vethは必ずペアで作成され、ペア間で通信が可能です。