Docker マルチホストネットワーキング: swarm, consul, overlay network(VXLAN) を使用した構築手順

# Docker multi host ネットワーク 
今までDocker の`--link` 機能を使うことで、同一ホスト内のコンテナの名前解決をして同一ネットワークのコンテナに対してアクセスすることは可能でしたが、それが複数の異なるDocker ホストになると`--link` 機能では実現することができませんでした。
そこでDocker のmulti ホストネットワーク及びDocker overlay ネットワークの機能を使用することで異なるホスト上にあるコンテナに対して、あたかも同じネットワーク上に存在しているコンテナのように、便利にアクセスすることができるようになります。
この機能を使うことにより今まで以上に大規模なDocker コンテナ環境を構築することができるようになります。

また、今回構築するDocker multi ホストネットワーク環境では、overlay network 及びDocker ホストのディスカバリにkey value ストアを使用するため、Docker ホストが異なるL3 ネットワーク上及び異なる拠点やデータセンタにある場合でも、それぞれのDocker ホスト上のコンテナは同じネットワーク上に存在するものとして容易にアクセスすることができるようになります。

* --link によるコンテナの名前解決
![Docker_MultiHostNetwork0000.png](https://qiita-image-store.s3.amazonaws.com/0/70152/f0b6056c-969c-a92f-f2ba-a9d061fdd221.png)


* Multi host ネットワークによるコンテナの名前解決
![Docker_MultiHostNetwork0001.png](https://qiita-image-store.s3.amazonaws.com/0/70152/5acdf2a5-420c-17aa-0d1d-20e3e525c582.png)


## ネットワークの構成 
今回構築するDocker multi host ネットワーク環境は次のようになります。
今回はそれぞれのDocker ホストが同じネットワーク上(192.168.1.0/24) にありますが、もちろんこれらは異なるネットワーク上に属していても問題ありません。


* Docker マルチホスト構成
<table><tbody>
  <tr>
    <th> ホスト名</th>
    <th> Docker ホスト側のIP</th>
    <th> OS</th>
    <th> Docker ホストのDocker version</th>
    <th> 備考</th>
  </tr>
  <tr>
    <td> client</td>
    <td> 192.168.1.121</td>
    <td> Ubuntu 15.10</td>
    <td> -</td>
    <td> Docker 管理用ワークステーション(兼Swarm マネージャ)</td>
  </tr>
  <tr>
    <td> consul01</td>
    <td> 192.168.1.140</td>
    <td> Debian strech</td>
    <td> 1.10.1</td>
    <td> Docker クラスタの管理情報を扱うkey value ストア</td>
  </tr>
  <tr>
    <td> swmgr01</td>
    <td> 192.168.1.121</td>
    <td> Debian strech</td>
    <td> 1.10.1</td>
    <td> Docker Swarm cluster のマネージャ(兼管理用ワークステーション)</td>
  </tr>
  <tr>
    <td> swagt01</td>
    <td> 192.168.1.141</td>
    <td> Debian strech</td>
    <td> 1.10.1</td>
    <td> Docker Swarm cluster のエージェント1 号機</td>
  </tr>
  <tr>
    <td> swagt02</td>
    <td> 192.168.1.142</td>
    <td> Debian strech</td>
    <td> 1.10.1</td>
    <td> Docker Swarm cluster のエージェント2 号機</td>
  </tr>
  <tr>
    <td> swagt03</td>
    <td> 192.168.1.143</td>
    <td> Debian strech</td>
    <td> 1.10.1</td>
    <td> Docker Swarm cluster のエージェント3 号機</td>
  </tr>
</tbody></table>

## Key-value ストア(consul)の作成 
Docker ホストのメタ情報を格納する格納するために、key-value データストアのconsul を準備します。
このkey-value データストアにはディスカバリ情報、エンドポイント、IP アドレスなどの情報が格納され、クラスタに参加しているDocker ホストが同じクラスタ内の他Docker ホストを検索するのに利用されます。
Docker ではConsul, Etcd, ZooKeeper といったkey-value データストアをサポートしていますが、今回はConsul を使用していきます。

Consul はDocker イメージが幾つか既に用意されているので、その中の一つである`progrium/consul` 利用することにします。


```text:consul01
consul01~# docker run -d -p 8500:8500 --name consul01 -h consul01 progrium/consul -server -bootstrap
```


上記コマンドを実行することで、consul が8500 番ポートでListen するようになります。
`docker ps` コマンドでconsul コンテナが起動していることを確認します。


```text:consul01
consul01~# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                            NAMES
463ad77b47ce        progrium/consul     "/bin/start -server -"   11 minutes ago      Up 11 minutes       53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8301-8302/udp, 0.0.0.0:8500->8500/tcp   consul01
```


## Swarm cluster を構築する 
次に、Docker Swarm manager コンテナをswmgr01 ホストに作成します。
ここではmanager を作成するときにディスカバリサービスとしてconsul を使うように指定します。
consul をディスカバリサービスとして指定するには、swarm コンテナを起動するときに`consul://<consul ip>:8500` と指定するようにします。


```text:swmgr01
$ docker run -d -p 2375:2375 --name swmgr01 swarm manage consul://192.168.1.140:8500/my_cluster

$ docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
ff1fe5e04017        swarm               "/swarm manage consul"   3 seconds ago       Up 2 seconds        0.0.0.0:2375->2375/tcp   swmgr01
```


## Docker Swarm agent のDocker daemon の設定 
各agent のDocker daemon が外部からのDocker コマンドを受け付けるようにするために、daemon 起動時に`-H` オプションで`tcp://0.0.0.0:2375` を指定するようにします。
また`--cluster-store` でconsul を指定し、`--cluster-advertise` でagent のDocker daemon のInterface (もしくはIP) とport を指定します。
起動時のオプションを変更するには`/lib/systemd/system/docker.service` ファイルを編集します。


```text:/lib/systemd/system/docker.serviceatswagt01-03
ExecStart=/usr/bin/docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=consul://192.168.1.140:8500/my_cluster --cluster-advertise=eno16777736:2375
```


systemctl ファイルを書き換えたら、`daemon-reload` を実行して修正を反映し、Docker daemon を再起動させます。

```text:swagt01-03
# systemctl daemon-reload
# systemctl restart docker
```


## Docker Swarm agent をクラスタに参加させる 
Docker Swarm manager を構築したら、次にDocker Swarm agent をクラスタに参加させて行きます。
こちらもDocker Swarm manager コンテナを起動した時と同様にディスカバリサービスとして`consul` を使うように指定します。


```text:swagt01
swagt01~# docker run --name swagt01 -d swarm join --addr=192.168.1.141:2375 consul://192.168.1.140:8500/my_cluster
```



```text:swagt02
swagt02~# docker run --name swagt02 -d swarm join --addr=192.168.1.142:2375 consul://192.168.1.140:8500/my_cluster
```



```text:swagt03
swagt03~# docker run --name swagt03 -d swarm join --addr=192.168.1.143:2375 consul://192.168.1.140:8500/my_cluster
```


### Docker ID が重複することによるエラー 
今回、Swarm agent の各ノードを、VM のコピー/クローンを使って作成した場合、Docker Swarm manager コンテナに次のようなエラーが出ることがあります。


```text:dockerlogsswmgr01
time="2016-02-06T06:00:15Z" level=error msg="ID duplicated. XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXXshared by 192.168.1.141:2375 and 192.168.1.142:2375"
```

Docker daemon は初回起動時にID を作成しますが、そのID が他のDocker daemon のID と重複しまうことによるエラーです。
もしこのようなエラーが出てしまった場合は、それぞれのDocker ID ファイル(~/.docker/key.json もしくは/root/.docker/key.json もしくは/etc/docker/key.json)を削除してからDocker daemon を再起動するようにしてください。


```text:DockerIDが重複した場合の対応
# rm -f /etc/docker/key.json
# systemctl restart docker
# docker info
...
ID: YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY
...
```


## cluster ノードの検索 
各agent をDocker Swarm cluster へ参加させたら、`consul` から各Docker Swarm agent が検索できることを確認してみましょう。


```text:swmgr01
swmgr01~$ docker run --rm swarm list consul://192.168.1.140:8500/my_cluster
192.168.1.141:2375
192.168.1.142:2375
192.168.1.143:2375
```


次にDocker Swarm manager コンテナに`docker info` コマンドを実行してDocker Swarm agent がcluster に参加しているコンテナを確認します。
`docker info` コマンドを実行するときは、`-H` オプションの接続先としてDocker Swarm manager を指定するようにします。


```text:dockerinfo
$ docker -H tcp://192.168.1.121:2375 info
...
Nodes: 3
 swagt01: 192.168.1.141:2375
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 2
  └ Reserved Memory: 0 B / 2.038 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.3.0-1-amd64, operatingsystem=Debian GNU/Linux stretch/sid (containerized), storagedriver=devicemapper
 swagt02: 192.168.1.142:2375
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 2
  └ Reserved Memory: 0 B / 2.038 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.3.0-1-amd64, operatingsystem=Debian GNU/Linux stretch/sid (containerized), storagedriver=devicemapper
 swagt03: 192.168.1.143:2375
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 2
  └ Reserved Memory: 0 B / 2.038 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.3.0-1-amd64, operatingsystem=Debian GNU/Linux stretch/sid (containerized), storagedriver=devicemapper
...
```


上記のように検索できれば成功です。

## overlay network の作成 
`docker network` コマンドを使ってswmgr01 コンテナにoverlay ネットワークを作成します。
overlay ネットワークを作成するコンテナは、cluster に参加しているいずれか1 つののコンテナで構いません。
今回は、manager コンテナにoverlay network を作成します。


```text:dockernetwork
$ docker -H tcp://192.168.1.121:2375 network create --driver overlay --subnet=10.0.9.0/24 my_network
```


Docker overlay network を作成したら、`docker network ls` コマンドを使ってクラスタ上のネットワークドライバの一覧を確認してみましょう。


```text:dockernetworkls
$ docker -H tcp://192.168.1.121:2375 network ls
NETWORK ID          NAME                DRIVER
474350b5f95f        swagt01/bridge      bridge              
f018be332b12        swagt02/none        null                
2279a690f4e6        swagt02/host        host                
4bebe4cbca4a        swagt03/host        host                
c93572ec1940        swagt03/bridge      bridge              
7aa32976023f        swagt01/host        host                
5ec158206a76        swagt01/none        null                
0050af5c7580        my_network          overlay             
139094363b1e        swagt02/bridge      bridge              
d3f2a409061e        swagt03/none        null
```


出力された結果を見ると、全agent 上のネットワークドライバが表示されます。
しかしoverlay network については全cluster 上で1 つのみ存在していることが確認できます。

次にそれぞれのagent に個々に接続して`docker network ls` コマンドを実行してみましょう。


```text:swagt01
$ docker -H tcp://192.168.1.141:2375 network ls
NETWORK ID          NAME                DRIVER
0050af5c7580        my_network          overlay             
9e68da7fa173        bridge              bridge              
97d2a09b9fbb        none                null                
22c27ff26d8b        host                host  
```



```text:swagt02
$ docker -H tcp://192.168.1.142:2375 network ls
NETWORK ID          NAME                DRIVER
0050af5c7580        my_network          overlay             
33276c8c59b6        bridge              bridge              
1d54daad8fce        none                null                
90768ae10d86        host                host 
```



```text:swagt03
$ docker -H tcp://192.168.1.143:2375 network ls
NETWORK ID          NAME                DRIVER
0050af5c7580        my_network          overlay             
2e5fff12a678        host                host                
80caae61efdd        bridge              bridge              
6e5b95a64480        none                null 
```


そうするとそれぞれのagent にoverlay network が同じID 表示されることが確認できるはずです
このような状態になっていればoverlay network の作成は成功です。
以上でDocker multi host networking 環境の構築は完了です。

### --subnet でサブネットを指定する必要性について 
`--subnet` オプションでoverlay network のネットワークアドレスを指定していますが、この指定を無くすとDocker daemon が自動的にネットワークアドレスを割り当ててくれます。
しかし、これは場合によってはDocker によって管理されていないネットワークアドレスをoverlay network に割り当てることもあるので、特に理由がない限りは明示的に安全なネットワークアドレスを指定するようにしてください。

## 動作確認 
では実際にcluster 上にコンテナを作成し、異なるDocker host 上のコンテナにコンテナ名を指定することでアクセスできることを確認してみましょう。
コンテナを起動するときは、`--net` オプションでoverlay network ドライバ`my_network` を指定する点に注意してください。
まずは1 つ目のコンテナを作成してみます。


```text:1つ目のコンテナの作成
$ docker -H tcp://192.168.1.121:2375 run --name=busybox01 --net=my_network -d -ti busybox /bin/sh
```


1 つ目のコンテナを作成したらどのDocker ホスト上で作成されたかを確認してみましょう。

```text:dockerps
$ docker -H tcp://192.168.1.121:2375 ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
be748478527d        busybox             "/bin/sh"           4 seconds ago       Up 2 seconds                            swagt02/busybox01
```


1 つ目のコンテナは`swagt02` 上に作成されました。
それでは2 つ目のコンテナをcluster 上に作成してみましょう。


```text:2つ目のコンテナの作成
$ docker -H tcp://192.168.1.121:2375 run --name=busybox02 --net=my_network -d -ti busybox /bin/sh

$ docker -H tcp://192.168.1.121:2375 ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
03c2ff579a90        busybox             "/bin/sh"           3 seconds ago       Up 2 seconds                            swagt03/busybox02
be748478527d        busybox             "/bin/sh"           52 seconds ago      Up 50 seconds                           swagt02/busybox01
```


2 つ目のコンテナは`swagt03` 上に作成されました。
次に`busybox02(on swagt03)` にattach して`busybox01(on swagt02)` を名前解決できてアクセスできることを確認してみましょう。


```text:dockerattach
$ docker -H tcp://192.168.1.121:2375 attach busybox02

/ # ping busybox01
PING busybox01 (10.0.9.2): 56 data bytes
64 bytes from 10.0.9.2: seq=0 ttl=64 time=0.526 ms
64 bytes from 10.0.9.2: seq=1 ttl=64 time=0.792 ms
64 bytes from 10.0.9.2: seq=2 ttl=64 time=0.801 ms
^C
--- busybox01 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.526/0.706/0.801 ms
```


ping コマンドを実行すると`busybox01` が名前解決されて異なるDocker ホスト上にあるコンテナから応答があることがわかります。

# 参考 
<dl>
  <dt> Get started with multi-host networking</dt>
  <dd> https://docs.docker.com/engine/userguide/networking/get-started-overlay/</dd>
</dl>

<dl>
  <dt> マルチホストDockerネットワーキング（native overlay）</dt>
  <dd> http://qiita.com/nmatsui/items/3ed2f21f7ee61d0827ea</dd>
</dl>

<dl>
  <dt> Virtual Ethernet Tunnel (veth) の組を知りたい</dt>
  <dd> http://thatsdone-j.blogspot.jp/2013/05/linux-veth-pairs.html</dd>
</dl>

<dl>
  <dt> Linux Switching – Interconnecting Namespaces</dt>
  <dd> http://www.opencloudblog.com/?p=66</dd>
</dl>

<dl>
  <dt> Docker Swarm Discovery</dt>
  <dd> https://docs.docker.com/swarm/discovery/</dd>
</dl>

<dl>
  <dt> Build a Swarm cluster for production</dt>
  <dd> https://docs.docker.com/swarm/install-manual/</dd>
</dl>

<dl>
  <dt> Swarm Manager errors seen #380</dt>
  <dd> https://github.com/docker/swarm/issues/380</dd>
</dl>

<dl>
  <dt> Failed to create the network with overlay driver type #17647</dt>
  <dd> https://github.com/docker/docker/issues/17647</dd>
</dl>