Dockerのoverlayネットワークでコンテナを分散実行

# 概要

Docker 1.9 からマルチホスト・ネットワーク機能が利用可能となった。動作検証として、DigitalOcean の環境上に、Docker Machine で Swarm クラスタを作り、オーバレイ・ネットワーク上を作成。最終的に、複数ホスト上にウェブとデータベースを分散した WordPress の環境を作成する。ここでは Docker Compose を使用し、オーバレイ・ネットワークの自動構築も試みる。

![docker-overlay network.png](https://qiita-image-store.s3.amazonaws.com/0/13002/a2f922a4-0b5e-bd21-3157-a1dfdf76f8e6.png)


Docker 1.9 のネットワーク機能については、以下 URL を参照のこと。

* [【参考訳】 Docker 1.9 発表：Swarm とマルチホスト・ネットワーキングのプロダクション対応](http://pocketstudio.jp/log3/2015/11/04/docker-1-9-production-ready-ja/)
* [【参考訳】 プロダクションに対応するマルチホスト Docker ネットワーク機能](http://pocketstudio.jp/log3/2015/11/04/docker-multi-host-networking-ga/)
* [参考訳：マルチホスト・ネットワーキングを始めよう - Qiita](
http://qiita.com/zembutsu/items/36032ee78e82278b3c6a)
* [参考訳：Docker コンテナ・ネットワークの理解 - Qiita](
http://qiita.com/zembutsu/items/1da05cb6a60e1c5acc25)

# 前提条件と環境について

* Docker 1.9～
* Linux Kernel 3.16～
* DigitalOcean

[Dockerのドキュメント](
http://qiita.com/zembutsu/items/1da05cb6a60e1c5acc25)はVirtualBox上だったが、実際に近い環境で試したかったので、DigitalOcean上に先の図のような環境構築を試みる。検証として、WordPressの`web`と`db`コンテナを、複数のホスト上でありながら、同一のネットワーク上に配置する。

※偶然にも眼前の環境が DigitalOcean だっただけで、他の環境でも、同様に動くはず。

# 環境構築手順

３台のサーバ（KVS、Swarmマスタ、Swarmノード）を準備する。

## KVS ( Consul ) 用マシンの作成

クラスタ全体の制御は Docker Swarm を通して行う。そのため、Docker Swarm クラスタが使用する KVS として Consul 用の環境を Docker Machine で作成する。

事前に DigitalOcean の API トークンを取得し、環境変数として定義する。

```
$ DO_TOKEN=3f0f0xxxxf65f
```

次に Docker Machine でマシン `swarm-kvs` を作成する。

```
$ docker-machine create \
    --driver digitalocean \
    --digitalocean-access-token $DO_TOKEN \
    --digitalocean-region sgp1 \
   swarm-kvs
```

正常に作成できると、`docker-machine ls` コマンドで次のように表示されている。

```
$ docker-machine ls
NAME        ACTIVE   DRIVER         STATE     URL                         SWARM
swarm-kvs   -        digitalocean   Running   tcp://188.166.225.62:2376
```

それから、作成したマシン内に `consul` をサーバ・モードで実行する。

```
$ docker $(docker-machine config swarm-kvs) run -d \
    -p "8500:8500" \
    -h "consul" \
    progrium/consul -server -bootstrap
```

## Swarm マスタ用マシンの作成

Docker Machine を使い、`swarm-node0`マシンの作成と、Swarm マスタ用（クラスタ管理）の自動構築も行う。

```
docker-machine create \
    --driver digitalocean \
    --digitalocean-access-token $DO_TOKEN \
    --digitalocean-region sgp1 \
    --swarm \
    --swarm-image="swarm:1.0.0" \
    --swarm-master \
    --swarm-discovery="consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-store=consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-advertise=eth0:2376" \
    swarm-node0
```

正常に実行されると、`docker-machine ls`では次のように2台のマシンが表示される。

```
$ docker-machine ls
NAME          ACTIVE   DRIVER         STATE     URL                         SWARM
swarm-kvs     -        digitalocean   Running   tcp://188.166.225.62:2376
swarm-node0   -        digitalocean   Running   tcp://188.166.225.83:2376   swarm-node0 (master)
```

次に、DigitalOcean の Ubuntu 14.04 LTS3 は Kernel バージョンが古く、オーバレイ・ネットワーク機能が利用出来ない。そのため、サーバにログインし、手動で Kernel のバージョンアップを行う。

1. DigitalOcean の GUI にアクセス
2. `Dropets` から対象のドロップレット(サーバ) をクリック
3. `Settings` -> `Kernel` -> `Ubuntu 14.10 x64 vmlinuz-3.16.0-41-generic` を選択し、`Change` をクリック
4. `docker-machine ssh swarm-node0` でこの環境に SSH ログイン
5. `/sbin/poweroff` コマンドでインスタンスを停止（rebootでは反映されない）
6.  GUI からドロップレットの情報を開き、`Power On`
7. 再度 `docker-machine ssh swarm-node0` でこの環境に SSH ログイン
8. kernel が更新された事を確認

```
root@swarm-node0:~# uname -a
Linux swarm-node0 3.16.0-41-generic #57-Ubuntu SMP Thu Jun 18 08:44:16 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
```

　9. `apt-get install -y -q linux-image-extra-3.16.0-41-generic` を実行
　10. `/sbin/reboot` で再起動


動作確認は、docker クライアントの環境変数を切り替えて行う。環境変数を指定すると、`docker` コマンドはローカルではなく、ここで指定したリモート環境に対する操作を行う。単なる Docker デーモンのポートだけでなく、Swarm のポートを指定すると、クラスタ全体を操作できる。

```
$ eval $(docker-machine env --swarm swarm-node0)
```

`docker ps` や `docker info` の情報が、Docker デーモンの情報ではなく、クラスタに関する情報に切り替わっていることを確認する。


## Swarm ノード用マシンの作成

`swarm-node1`という名称のマシンを作成する。

```
docker-machine create \
    --driver digitalocean \
    --digitalocean-access-token $DO_TOKEN \
    --digitalocean-region sgp1 \
    --swarm \
    --swarm-image="swarm:1.0.0" \
    --swarm-discovery="consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-store=consul://$(docker-machine ip swarm-kvs):8500" \
    --engine-opt="cluster-advertise=eth0:2376" \
    swarm-node1
```

作成後は先ほど同様、kernel のバージョンアップ作業を行う。

この時点で3台の環境が準備できた。

```
$ docker-machine ls
NAME          ACTIVE   DRIVER         STATE     URL                          SWARM
swarm-kvs     -        digitalocean   Running   tcp://188.166.225.62:2376
swarm-node0   *        digitalocean   Running   tcp://188.166.225.83:2376    swarm-node0 (master)
swarm-node1   -        digitalocean   Running   tcp://128.199.242.128:2376   swarm-node0
```

動作確認は `docker info` コマンド実行時、リソース情報で2台分（Swarm マスタとノード）表示されていることを確認。

```
$ eval $(docker-machine env --swarm swarm-node0)
$ docker info
Containers: 3
Images: 2
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 swarm-node0: 188.166.225.83:2376
  m Containers: 2
  m Reserved CPUs: 0 / 1
  m Reserved Memory: 0 B / 514 MiB
  m Labels: executiondriver=native-0.2, kernelversion=3.16.0-41-generic, operatingsystem=Ubuntu 14.04.3 LTS, provider=digitalocean, storagedriver=aufs
 swarm-node1: 128.199.242.128:2376
  m Containers: 1
  m Reserved CPUs: 0 / 1
  m Reserved Memory: 0 B / 514.5 MiB
  m Labels: executiondriver=native-0.2, kernelversion=3.13.0-57-generic, operatingsystem=Ubuntu 14.04.3 LTS, provider=digitalocean, storagedriver=aufs
CPUs: 2
Total Memory: 1.004 GiB
Name: 6bbe5d40c009
```

# オーバレイ・ネットワークの作成

以降の作業では、Swarm クラスタに対する操作を行う。常に、次の環境変数の定義されているものとする。

```
$ eval $(docker-machine env --swarm swarm-node0)
```

ネットワーク情報を見るには、`docker network ls` コマンドを実行する。次のように `swarm-node1` と `swam-node0` の各ホスト上に `host` や `bridge` という Docker 標準ネットワーク情報を確認できる。これは各ホスト毎に存在している。そして、別々のネットワーク ID を持つ。

```
$ docker network ls
NETWORK ID          NAME                 DRIVER
392db5e3bd70        swarm-node1/host     host
9d23b56deb9f        swarm-node0/bridge   bridge
5ab9066e5c50        swarm-node0/none     null
bb18b404a6c6        swarm-node0/host     host
dd8fc71c6bbc        swarm-node1/bridge   bridge
a747d9e4ef62        swarm-node1/none     null
```

オーバレイ・ネットワークは、ホスト毎にまたがるネットワークであり、ネットワーク ID も共通している。手動で `internal` という名称のネットワークを作成するには、次のように実行する。

```
$ docker network create internal
1b2b66f54a72b83d5151bad8af84ac7244a3cc74a18151fbaeaf4bf4ff92b8de
```

再び `docker network ls` を実行すると、所属するホスト名がない `DRIVER` が `overlay` のネットワークが作成できる。

```
$ docker network ls
NETWORK ID          NAME                 DRIVER
bb18b404a6c6        swarm-node0/host     host
1b2b66f54a72        internal             overlay
9d23b56deb9f        swarm-node0/bridge   bridge
dd8fc71c6bbc        swarm-node1/bridge   bridge
a747d9e4ef62        swarm-node1/none     null
392db5e3bd70        swarm-node1/host     host
5ab9066e5c50        swarm-node0/none     null
```

### nginx コンテナを手動で作成して、挙動の確認

この時点で `docker ps` を実行しても、Swarm クラスタ上にはコンテナが作成していないため、次のように実行中のコンテナはなにも表示されない（実際には各ホスト上で swarm 用のコンテナが実行中だが、Swarm を通すと表示されない）

```
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

Swarm クラスタで、web という名称の nginx コンテナを起動する。`constraint`フィルタを指定し、起動するホストを`swarm-node0`と指定している（指定しない場合は、ノードのいずれかがランダムに選択される。あるいは Swarm のステラテジに依存する。フィルタやストラテジの詳細は [Docker Swarm 入門](http://www.slideshare.net/zembutsu/introduction-to-docker-swarm)を参照のこと）。

```
$ docker run -d --name=web --net=internal --env="constraint:node==swarm-node0" nginx
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
6473ca45464d        nginx               "nginx -g 'daemon off"   25 seconds ago      Up 23 seconds       80/tcp, 443/tcp     swarm-node0/web
```

この状態で、もう１つのホスト `swarm-node1` を指定して busybox コンテナを起動し、疎通を確認する。

```
$  docker run -it --net=internal --env="constraint:node==swarm-node1" busybox
```

シェルが表示され、コンテナ内で操作が可能になる。`/etc/hosts`を見ると、自動的に先ほど作成されたホスト名と IP アドレスが自動的に追加されているのが分かる。

```
/ # cat /etc/hosts
10.0.0.3        5656bd4c2072
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.0.0.2        web
10.0.0.2        web.internal
```

※ネットワークは `docker network connect xxx` で接続、`docker network disconnect xxx` で、任意のコンテナに対し、任意のネットワークの追加・削除が動的に可能。設定を適用した瞬間、コンテナ内のインターフェースと /etc/hosts エントリに追加・削除される。

この状態で `web` という名称で名前確認が可能なため、次のように `ping` の疎通確認や `wget` によるコンテンツ取得がオーバレイ・ネットワークを通して可能となる。この２つのコンテナは別々のホスト上に存在しているが、オーバレイ・ネットワーク機能のおかげで、同一のネットワーク上に存在しているように見えるため。

```
/ # ping web
PING web (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.680 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.600 ms
64 bytes from 10.0.0.2: seq=2 ttl=64 time=0.731 ms
^C
--- web ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.600/0.670/0.731 ms
/ # wget -O - http://web
Connecting to web (10.0.0.2:80)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
(省略)
```

# Docker Compose で WordPress を分散実行

従来の Docker Compose は１つのホスト上でのみ複数コンテナの操作が可能だった。Docker Compose 1.5 と Docker Swarm 1.0, Docker Engine 1.9 の組み合わせにより、複数のホスト上で複数のコンテナを起動し、かつ名前解決が可能となる。

ここでは `cms` という Compose プロジェクトを使い、オーバレイ・ネットワークの自動起動と、コンテナの自動起動・ネットワークの割り当てを行う。

`web` と `db` という名称のコンテナを定義する。`web` コンテナは `swarm-node0` 上で実行する設定とするが、`db` は Swarm のストラテジに従う（デフォルトは Spread 方式なので、リソースが空いている別のサーバ上でコンテナは起動する）。

```
mkdir cms
cd cms
cat << EOF > docker-compose.yml
web:
  image: wordpress
  environment:
        - "WORDPRESS_DB_HOST=cms_db_1"
        - "WORDPRESS_DB_USER=root"
        - "WORDPRESS_DB_PASSWORD=example"
        - "constraint:node==swarm-node0"
  ports:
    - 8080:80

db:
  image: mariadb
  environment:
    MYSQL_ROOT_PASSWORD: example
EOF
```

オーバレイ・ネットワーク機能を有効にしてコンテナを起動するには `--x-networking --x-network-driver` を実行する。

```
$ docker-compose --x-networking --x-network-driver overlay up -d
Creating network "cms" with driver "overlay"
Pulling web (wordpress:latest)...
swarm-node1: Pulling wordpress:latest... : downloaded
swarm-node0: Pulling wordpress:latest... : downloaded
Creating cms_web_1
Pulling db (mariadb:latest)...
swarm-node0: Pulling mariadb:latest... : downloaded
swarm-node1: Pulling mariadb:latest... : downloaded
Creating cms_db_1
```

実行状態の確認は `docker-compose ps` を実行する。

```
$ docker-compose ps
  Name                 Command               State              Ports
--------------------------------------------------------------------------------
cms_db_1    /docker-entrypoint.sh mysqld     Up      3306/tcp
cms_web_1   /entrypoint.sh apache2-for ...   Up      188.166.225.83:8080->80/tcp
```

この例では、ブラウザから `http://188.166.225.83:8080` にアクセスすると、WordPress の初期画面が表示される。あとは `docker-compose stop` での停止や `docker-compose rm` の環境削除は、通常の Compose コマンドと変わりない。

注意点としては、`docker-compose up`時に各マシン上でイメージのダウンロードが開始される。そのため、一見すると止まって見える場合も有る。また、今回の例でタイムアウトして自動起動に失敗して見える場合 `stop` `start` を繰り返すと正常に起動する。

どこのノード上にどのコンテナを実行するかは、基本的に Swarm の[ストラテジ](http://qiita.com/zembutsu/items/c89e138196a34da28919)と[フィルタ](http://qiita.com/zembutsu/items/cee53ce370b0c13149e5)に依存する。

Swarm 全般については以下、

* [DockerSwarm - Docker Swarm ドキュメント参考日本語訳 目次 - Qiita](http://qiita.com/zembutsu/items/a1f0a89581d1d5832e2e)

Compose 全般については以下参照。

* [docker-compose - Docker Compose ドキュメント参考日本語訳 概要・目次 - Qiita](http://qiita.com/zembutsu/items/c277830c469d7a25ba15)