Docker : マルチホスト間での仮想ネットワーク

# 概要
Docker 1.9 で正式版になったマルチホストネットワーク機能を使ってみたのでまとめます。

この機能では、複数のホストにまたがる仮想ネットワークを構築でき、コンテナはどこのホスト上で動いているかに関わらず、仮想ネットワークを経由して他のコンテナと通信することができます。

今回は下図のように、2台のホスト上のコンテナが仮想ネットワークを通じて通信できるようにしました。
![DockerNetwork.png](https://qiita-image-store.s3.amazonaws.com/0/79316/47ffeee1-0166-aed4-8808-306acc093aa5.png)

**マルチホストネットワークの要件**

* マルチホストネットワークを使うには、複数のホストにまたがる Key/Value ストアが必要になりますが(図の Consul がそれに相当)、これについては後述します
* ホストの Linux Kernel は 3.16 かそれ以降が必要になります

**今回の検証環境**

* Host: Ubuntu 15.10 (Kernel 4.2.0)
* Docker Version: 1.9.0

# 手順

## Docker 1.9 のインストール

2台のホスト上で、それぞれ以下を実行します。

```bash:Host1&Host2
# Docker Engine の最新版をインストール
$ curl -sSL https://test.docker.com/ | sh

# Docker デーモンが動いていたら停止しておく。後で、デフォルトとは異なるパラメータを付けて開始するため。
$ sudo service docker stop

# docker コマンドを実行するたびに sudo を打つのが面倒なので、以下のコマンドを実行して再ログインする。
$ sudo usermod -aG docker <username>
```


## Consul のインストールと起動
複数のホストにまたがる仮想ネットワークを作成する場合は、ホスト間でネットワークの情報を共有するために 分散Key/Value ストアのクラスターが必要になります。

Docker 1.9 では Consul, etcd, ZooKeeper が利用できるようですが、今回は Consul を使いました。Consul は、Vagrant や Packer を開発している HashiCorp が提供している Key/Value ストアです。

まずは、両方のホストで consul をダウンロード。

```bash:Host1&Host2
# ダウンロード
$ wget https://releases.hashicorp.com/consul/0.5.2/consul_0.5.2_linux_amd64.zip

# もし unzip コマンドが入ってなければインストールしておく
$ sudo apt-get install unzip

# consul を解凍してコピー
$ unzip consul_0.5.2_linux_amd64.zip
$ sudo cp consul /usr/local/bin/
```

Host1 側で consul をサーバーとして起動。

```bash:Host1
consul agent -server -bootstrap -data-dir=/tmp/consul -bind=100.74.14.78
```

Host2 側の consul をクラスターに参加させる。

```bash:Host2
consul agent -data-dir=/tmp/consul -bind=100.74.120.62 -join=100.74.14.78
```

別のターミナルから両ホストにSSH接続して、以下のコマンドを実行。クラスタが形成されたか確認しておきます。

```bash:Host1&Host2
$ consul members
Node     Address             Status  Type    Build  Protocol  DC
Ubuntu1  100.74.14.78:8301   alive   server  0.5.2  2         dc1
Ubuntu2  100.74.120.62:8301  alive   client  0.5.2  2         dc1
```

# Docker デーモンの起動
Key/Value ストアのクラスターが構成できたので、それを指定して Docker デーモンを起動します。引数は以下になります。

* --cluster-store ： Key/Value のクラスタ
* --cluster-advertise ： 自分自身の IP/Port を指定します。


```bash:Host1
$ sudo docker daemon --cluster-store=consul://localhost:8500 --cluster-advertise=100.74.14.78:2376
```

```bash:Host2
$ sudo docker daemon --cluster-store=consul://localhost:8500 --cluster-advertise=100.74.120.62:2376
```

# ネットワークの作成

また新しいターミナルを起動して、まずはデフォルトで作成されているネットワークを確認します。どちらも、bridge, null, host の３つのネットワークがあることが分かります。

```bash:Host1
$ docker network ls
NETWORK ID          NAME                DRIVER
3db3635076e3        bridge              bridge
1f616c6c3c34        none                null
e459697f7cb8        host                host
```

```bash:Host2
$ docker network ls
NETWORK ID          NAME                DRIVER
a629ccb5fbba        bridge              bridge
2802d9ae3067        none                null
42b1f2f3d2bd        host                host
```

ここで、**Host1 側だけで** 新しいネットワークを作成します。

```bash:Host1
$ docker network create -d overlay TestNetwork
60231ea6adf2c36cafe68ddc5ee0548fbae034a812b988c3e018b41b1cc66278
```

すると、Host1 と Host2 の両方で、今作成した TestNetwork というネットワークが見えます。

```bash:Host1
$ docker network ls
NETWORK ID          NAME                DRIVER
60231ea6adf2        TestNetwork         overlay
3db3635076e3        bridge              bridge
1f616c6c3c34        none                null
e459697f7cb8        host                host
```

```bash:Host2
$ docker network ls
NETWORK ID          NAME                DRIVER
60231ea6adf2        TestNetwork         overlay
a629ccb5fbba        bridge              bridge
2802d9ae3067        none                null
42b1f2f3d2bd        host                host
```

# コンテナの作成とコンテナ間の通信
仮想ネットワークが作成できたので、いよいよネットワーク上にコンテナを作成します。

```bash:Host1
# コンテナを起動
$ docker run -it --net=TestNetwork --name=Container1 ubuntu /bin/bash

# コンテナの中で ifconfig を実行してIPアドレスを確認
root@27f699b0d97c:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:0a:00:00:02
          inet addr:10.0.0.2  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::42:aff:fe00:2/64 Scope:Link
          (中略)

eth1      Link encap:Ethernet  HWaddr 02:42:ac:12:00:02
          inet addr:172.18.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link
          (中略)
```

```bash:Host2
# コンテナを起動
$ docker run -it --net=TestNetwork --name=Container2 ubuntu /bin/bash

# コンテナの中で ifconfig を実行してIPアドレスを確認
root@6b4baee6e4d0:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:0a:00:00:03
          inet addr:10.0.0.3  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::42:aff:fe00:3/64 Scope:Link
          (中略)

eth1      Link encap:Ethernet  HWaddr 02:42:ac:12:00:02
          inet addr:172.18.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link
          (中略)
```

どちらのコンテナにも 172.18.0.2 という IP のほかに、10.0.0.x という IP が割り当たっています。この 10.0.0.x が、仮想ネットワークに接続されている IP アドレスになります。

## ping での通信
では、Container1(10.0.0.2) から Container2(10.0.0.3) に通信してみます。IP でもコンテナ名でも Ping が送れることが分かります。

```bash:Container1
# IP で ping を送る
root@27f699b0d97c:/# ping 10.0.0.3
PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data.
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=1.11 ms
64 bytes from 10.0.0.3: icmp_seq=2 ttl=64 time=0.806 ms
・・・

# コンテナ名で ping を送る
root@27f699b0d97c:/# ping Container2
PING Container2 (10.0.0.3) 56(84) bytes of data.
64 bytes from Container2 (10.0.0.3): icmp_seq=1 ttl=64 time=0.661 ms
64 bytes from Container2 (10.0.0.3): icmp_seq=2 ttl=64 time=1.61 ms
・・・

# コンテナ名.ネットワーク名 で ping を送る
root@27f699b0d97c:/# ping Container2.TestNetwork
PING Container2.TestNetwork (10.0.0.3) 56(84) bytes of data.
64 bytes from Container2 (10.0.0.3): icmp_seq=1 ttl=64 time=1.00 ms
64 bytes from Container2 (10.0.0.3): icmp_seq=2 ttl=64 time=0.767 ms
・・・
```

## HTTP での通信

次に HTTP で通信してみます。
まず、Container1 内に HTTP サーバーを入れます。

```bash:Container1
# apache のインストールと開始
root@27f699b0d97c:/# apt-get install apache2
root@27f699b0d97c:/# service apache2 start

# HTML ファイルの作成しておく
root@27f699b0d97c:/# echo "Hello, I'm Container1." > /var/www/html/test.html
```

Container2 から HTTP サーバーにアクセスすると、ちゃんと応答が返ってきます。

```bash:Container2
# コンテナ名で HTTP リクエストを送る
root@6b4baee6e4d0:/# curl http://Container1/test.html
Hello, I'm Container1.
```

マルチホストネットワーク機能を使わない場合は、docker run コマンドの -p, -P オプションでポートの公開をする必要がありましたが、マルチホストネットワークではそれも不要のようです。

# 参考サイト

* [Docker container networking](https://github.com/mavenugo/docker/blob/ux/docs/userguide/dockernetworks.md)
* [Overlay Driver](https://github.com/docker/libnetwork/blob/master/docs/overlay.md)
* [Get started with multi-host networking](https://docs.docker.com/engine/userguide/networking/get-started-overlay/)
* [Consulを使ってみた](http://pocketstudio.jp/log3/2014/04/18/what_is_consul/)