Ubuntu 16.04でDockerコンテナに複数NICをブリッジする

こんにちは、[@hico_horiuchi](https://twitter.com/hico_horiuchi)です。
DockerコンテナにDockerホストのNIC 2つをブリッジ接続する機会があったので、その方法をまとめておきます。
複数セグメントに疎通が必要な場合や、DHCPdなどL2接続が必要な場合に役に立つと思います。

## 最終的な構成

最終的にDockerコンテナ〜Dockerホスト〜ネットワークの構成は下記のようになります。
Dockerのネットワークについては [Docker の基本学習 ~ Docker のネットワーク - Qiita](http://qiita.com/Arturias/items/b538e6bbf05dd3364397) を参照。

![4372a266-0575-43c7-ac0d-760c110e0924.png](https://qiita-image-store.s3.amazonaws.com/0/58742/4b59941e-da25-4989-edc2-572134c38d06.png)

## Dockerホストのセットアップ

### ネットワークの設定

`eth0` と `eth1` の2つのNICを持つVMを用意し、Ubuntu Server 16.04をインストールしました。
NICの命名が `ensXX` になっている場合は、下記を参考に `/etc/default/grub` を修正します。

- [networking - Network interface name changes after update to 15.10 - udev changes - Ask Ubuntu](https://askubuntu.com/questions/689070/network-interface-name-changes-after-update-to-15-10-udev-changes)

```
$ sudo sed -i 's/^GRUB_CMDLINE_LINUX=""$/GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0"/g' /etc/default/grub 
$ sudo update-grub
$ sudo reboot
```

### ブリッジの作成

`br0` は `eth0` に、`br1` は `eth1` にブリッジするように `/etc/network/interfaces` を修正します。
(ネットワークの変更を適用するために再起動が必要です、注意してください。)

```
auto eth0
iface eth0 inet manual

auto eth1
iface eth1 inet manual
 
auto br0
iface br0 inet static
address 10.1.1.2
netmask 255.255.255.0
up route add default gw 10.1.1.1
bridge_ports eth0
bridge_maxwait 0
bridge_fd 0
bridge_stp off
 
auto br1
iface br1 inet static
address 172.16.1.2
netmask 255.255.255.0
bridge_ports eth1
bridge_maxwait 0
bridge_fd 0
bridge_stp off
```

ブリッジの状態確認などで `brctl` コマンドを使うので、`bridge-utils` を入れておくと便利です、

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

### Dockerのインストール

Dockerは個人用のAnsible Playbookを使って最新版をインストールしています。
手動でインストールする場合は [Get Docker CE for Ubuntu | Docker Documentation](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/) を参照。

- [hiconyan-com-ansible/roles/docker at master · hico-horiuchi/hiconyan-com-ansible](https://github.com/hico-horiuchi/hiconyan-com-ansible/tree/master/roles/docker)

```
$ git clone git@github.com:hico-horiuchi/hiconyan-com-ansible.git
$ cd hiconyan-com-ansible
$ virtualenv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ source venv/bin/activate
$ ansible-playbook -i hosts -t common_apt,common_ssh,docker site.yml
```
```
[conoha]
10.1.1.2 ansible_python_interpreter=/usr/bin/python3
```

## Dockerネットワークの作成

`br0` に接続する `br0_network` と、`br1` に接続する `br1_network` という2つのDockerネットワークを作成します。

- `gateway` : `br0` など、ブリッジのIPアドレスを指定します。
- `com.docker.network.bridge.enable_ip_masquerade` : NAPTさせないように `false` を指定します。
- `om.docker.network.bridge.name` : `br0` など、使用するブリッジ名を指定します。

その他のオプションについては [network create — Docker-docs-ja 1.13.RC ドキュメント](http://docs.docker.jp/engine/reference/commandline/network_create.html) を参照。

```
$ docker network create \
         -d bridge \
         --subnet 10.1.1.0/24 \
         --gateway 10.1.1.2 \
         -o 'com.docker.network.bridge.enable_icc=true' \
         -o 'com.docker.network.bridge.enable_ip_masquerade=false' \
         -o 'com.docker.network.bridge.host_binding_ipv4=0.0.0.0' \
         -o 'com.docker.network.bridge.name=br0' \
         -o 'com.docker.network.driver.mtu=1500' \
         br0_network
$ docker network create \
         -d bridge \
         --subnet 172.16.1.0/24 \
         --gateway 172.16.1.2 \
         -o 'com.docker.network.bridge.enable_icc=true' \
         -o 'com.docker.network.bridge.enable_ip_masquerade=false' \
         -o 'com.docker.network.bridge.host_binding_ipv4=0.0.0.0' \
         -o 'com.docker.network.bridge.name=br1' \
         -o 'com.docker.network.driver.mtu=1500' \
         br1_network
```

## Dockerコンテナへのネットワーク接続

`docker run` の  `--network` オプションでは接続するネットワークを1つしか指定できません。
そこで、一度 `create` コンテナを作成し、ネットワークを2つ接続してから、`start` で起動します。

```
$ docker create -t --name ubuntu ubuntu:16.04 /bin/bash
$ docker network connect --ip 10.1.1.3 br0_network ubuntu
$ docker network connect --ip 172.16.1.3 br1_network ubuntu
$ docker start -a ubuntu
```

## 注意事項

### プロミスキャスモードについて

スイッチ側で[ARPスプーフィング](https://ja.wikipedia.org/wiki/ARPスプーフィング)の対策として、コンテナからのパケットがドロップされることがあります。
(コンテナのNICと、ブリッジしたNICのMACアドレスが異なるため、フィルタリングされる。)

今回はESXi上にVMを構築したのですが、「無差別モード」と「偽装転送」が拒否されておりハマってました…。
(いわゆるプロミスキャスモードと呼ばれる機能が有効になっている必要があるようです。)

- [vSphere 標準スイッチまたは標準ポート グループのセキュリティ ポリシーの構成](https://docs.vmware.com/jp/VMware-vSphere/6.0/com.vmware.vsphere.networking.doc/GUID-891147DD-3E2E-45A1-9B50-7717C3443DD7.html)

### docker0ネットワークについて

イメージビルド時にはdocker0が使われますが、パッケージダウンロードなどでインターネット疎通が必要ですよね。
しかし、`br0_network` を作成すると、Dockerのネットワーク分離機能により、`docker0` と `br0` は疎通できなくなります。
`br1` についても同様で、その結果インターネットに抜けられなくなってしまうので、注意してください。
(イメージビルド時にはbr0_networkとbr1_networkを作成しない、ビルドサーバを分ける、など。)

```
$ sudo iptables -vL | grep -A4 'Chain DOCKER-ISOLATION'
Chain DOCKER-ISOLATION (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  docker0 br0     anywhere             anywhere            
    0     0 DROP       all  --  br0    docker0  anywhere             anywhere            
    0     0 RETURN     all  --  any    any     anywhere             anywhere
```

## 参考文献

- [Dockerコンテナに複数のNICを付与する方法 (CentOS 7) - Qiita](http://qiita.com/osmito/items/ecd66a654f5dd773799a)
- [Docker - Create a Bridge and Shared Network (Example)](https://coderwall.com/p/2rpbba/docker-create-a-bridge-and-shared-network)
- [Linux: Dockerの使い方 - Narrow Escape](https://www.hiroom2.com/2017/02/14/linux-docker%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9/#sec-5-2)