Dockerのマルチホストネットワーク+Composeで複数ノードにまたがってElasticSearch, Logstash, Kibana構築

以前、下記のPostをしていて、この時は一つのホストに複数のコンテナをDocker Composeを用いて構築していました。

[ELK(ElasticSearch, Logstash, Kibana)+fluentdの環境をDocker Composeで構築しつつ、試しにCloudWatchの統計データを収集してみた](http://qiita.com/khiraiwa/items/dc7e1c446d7e0c7d9482)

このときのdockerの--linkオプションでは、別のホストマシン上に起動してあるコンテナのhosts情報を渡せなかったのですが、
Docker Engine 1.9で追加された**マルチホストネットワーキング**を使えば、複数ホスト間でネットワークを共有し、~~--linkで渡した名前を元にしたアクセスが可能になるかと思い試してみました。~~
→　--linkオプションはマルチホストネットワーキングではサポートされていませんでした。
　　また、将来的にDockerから削除されるそうです。
　　マルチホストネットワーキングではコンテナ名でIPが引けるので、
　　下ではこれを使うように変更しています。

![Pasted_Image_1_23_16__10_23_PM.png](https://qiita-image-store.s3.amazonaws.com/0/45959/7fb86b91-8001-dba7-7275-bbf0d60a7c2f.png)

前回と同様にDocker Composeを用いて複数ホスト(上の図でいうnode1とnode2)にまたがってコンテナを構築するのが目標なので、そのために下ではDocker Swarmも使いました。
(SwarmのmasterノードにComposeを実行して、複数のノードにコンテナを起動させるようにしています)

# 環境

今回試した環境と、ツール類のバージョンは下記のとおりです。

| 環境            |         バージョンなど|
|:---------------|--------------------:|
| OS             | EI Capitan(10.11.3) | 
| Docker Engine  |               1.9.1 |
| Docker Machine |               0.5.6 |
| Docker Compose |               1.5.2 |

# Docker Toolboxのインストール

マルチホストネットワーキングを行うにはDocker Engine, VirtualBoxを使用するため、それらをインストールする必要があります。

私はDocker Engine, Compose, Swarm, Machine, ViratualBox +αをまとめてインストールできるDocker Toolboxをインストールしました。

Macでhomebrewを使っているなら下記でインストール。

```bash
$ brew cask install dockertoolbox
```

インストーラーをダウンロードして、インストールする場合は、下記から。
https://www.docker.com/docker-toolbox

# Key-Valueストア(KVS)の準備

ホストオンリーネットワークの構築にはノードからアクセスできるところにKVSのサービスを立てておく必要があります。
このKVSにはネットワークのステータス(discovery, networks, endpoints, IP addresses等)を登録するとのこと。

マルチホストネットワーキングでサポートしているKVSはConsul, Etcd, ZooKeeperとのこと。

ここではサンプルに習ってConsulを用いました。
Docker Machineを使って構築したVMにてConsulを動作させます。

## VMの作成  
```bash
$ docker-machine create -d virtualbox mh-keystore

Running pre-create checks...
Creating machine...
(mh-keystore) Copying /Users/[user]/.docker/machine/cache/boot2docker.iso to /Users/[user]/.docker/machine/machines/mh-keystore/boot2docker.iso...
(mh-keystore) Creating VirtualBox VM...
(mh-keystore) Creating SSH key...
(mh-keystore) Starting the VM...
(mh-keystore) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect Docker to this machine, run: docker-machine env mh-keystore
```
## VM上でConsulサービスを起動

コンテナを実行してホストと8500/tcpポートをポートマッピングします。

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

Unable to find image 'progrium/consul:latest' locally
latest: Pulling from progrium/consul
3b4d28ce80e4: Pull complete
e5ab901dcf2d: Pull complete
30ad296c0ea0: Pull complete
3dba40dec256: Pull complete
f2ef4387b95e: Pull complete
53bc8dcc4791: Pull complete
75ed0b50ba1d: Pull complete
17c3a7ed5521: Pull complete
8aca9e0ecf68: Pull complete
4d1828359d36: Pull complete
46ed7df7f742: Pull complete
b5e8ce623ef8: Pull complete
049dca6ef253: Pull complete
bdb608bc4555: Pull complete
8b3d489cfb73: Pull complete
c74500bbce24: Pull complete
9f3e605442f6: Pull complete
d9125e9e799b: Pull complete
Digest: sha256:8cc8023462905929df9a79ff67ee435a36848ce7a10f18d6d0faba9306b97274
Status: Downloaded newer image for progrium/consul:latest
957db0c166612c435d5379da0f1c325ab028d2d02c7fe01d77d503526a5c8755
```
## docker psでConsulが立ち上がっていることを確認

Dockerのクライアントの向け先を作成したVMに向けます。

```bash
$ eval "$(docker-machine env mh-keystore)"
```

下記で確認します。起動していれば下記のように表示されます。

```bash
$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                            NAMES
957db0c16661        progrium/consul     "/bin/start -server -"   2 minutes ago       Up 2 minutes        53/tcp, 53/udp, 8300-8302/tcp, 0.0.0.0:8500->8500/tcp, 8301-8302/udp, 8400/tcp   big_cray
```

Consul自体は下記からダウンロードできるので、コンテナを使わなくてもホストマシンでそれを実行できます。
https://www.consul.io/
ここはサンプル通りコンテナを利用して構築してみました。コンテナで実行するのはとても楽でした。

# コンテナを起動するための複数のノードを起動

今回、Docker Composeで複数ノードにまたがってコンテナを起動したいので、Docker Swarmを使ってクラスタを構築しました。

ホストオンリーネットワークを使うだけならSwarmのクラスタは必須ではありません。
Docker Machineも必須ではありません。

## Swarm masterの作成

まずクラスタのmasterになるノードを作成します。

```bash
$ docker-machine create \
-d virtualbox \
--swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
node1

Running pre-create checks...
Creating machine...
(node1) Copying /Users/[user]/.docker/machine/cache/boot2docker.iso to /Users/[user]/.docker/machine/machines/node1/boot2docker.iso...
(node1) Creating VirtualBox VM...
(node1) Creating SSH key...
(node1) Starting the VM...
(node1) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Configuring swarm...
Checking connection to Docker...
Docker is up and running!
To see how to connect Docker to this machine, run: docker-machine env node1
```

Docker Machineを使わずにマルチホストネットワーキングを利用するには、VM上でDocker Engineのデーモンを起動する際に、下記のオプションを設定すればよい、ということですね。

* --cluster-store
* --cluster-advertise

Docker Machineで起動したVM上ではデーモン起動のコマンドは下記のようになっていました。

```bash:
/usr/local/bin/docker daemon -D -g /var/lib/docker \
-H unix:// -H tcp://0.0.0.0:2376 --label provider=virtualbox \
--cluster-store=consul://192.168.99.100:8500 \
--cluster-advertise=eth1:2376 --tlsverify \
--tlscacert=/var/lib/boot2docker/ca.pem \
--tlscert=/var/lib/boot2docker/server.pem \
--tlskey=/var/lib/boot2docker/server-key.pem -s aufs
```

## Swarm clusterの作成

続いてクラスタノードを追加します。

```bash
$ docker-machine create -d virtualbox \
    --swarm \
    --swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
    --engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
    --engine-opt="cluster-advertise=eth1:2376" \
  node2

Running pre-create checks...
Creating machine...
(node2) Copying /Users/[user]/.docker/machine/cache/boot2docker.iso to /Users/[user]/.docker/machine/machines/node2/boot2docker.iso...
(node2) Creating VirtualBox VM...
(node2) Creating SSH key...
(node2) Starting the VM...
(node2) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Configuring swarm...
Checking connection to Docker...
Docker is up and running!
To see how to connect Docker to this machine, run: docker-machine env node2
```

## 構築内容の確認

ここまでで下記のように、3つ作成されていればOKです。

```bash
$ docker-machine ls

NAME          ACTIVE   URL          STATE     URL                         SWARM            DOCKER   ERRORS
mh-keystore   *        virtualbox   Running   tcp://192.168.99.100:2376                    v1.9.1
node1         -        virtualbox   Running   tcp://192.168.99.101:2376   node1 (master)   v1.9.1
node2         -        virtualbox   Running   tcp://192.168.99.102:2376   node1            v1.9.1
```

次に、masterノードからSwarmクラスタのノード情報を確認してみます。

Dockerクライアントの実行をmasterノードに向けます。

```bash
$ eval $(docker-machine env --swarm node1)
```

確認してみます。

```bash
$ docker info

Containers: 3
Images: 2
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 node1: 192.168.99.101:2376
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.021 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.1.13-boot2docker, operatingsystem=Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015, provider=virtualbox, storagedriver=aufs
 node2: 192.168.99.102:2376
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.021 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.1.13-boot2docker, operatingsystem=Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015, provider=virtualbox, storagedriver=aufs
CPUs: 2
Total Memory: 2.043 GiB
Name: node1
```

# マルチホストネットワーキングを構築します

下記、コマンドでmy-netというネットワークを作成します。

```bash
$ docker network create --driver overlay my-net

ca21981bd1e31ec3d7d40336edffdddc49d318492f6248323fb038b6307b3d32
```

作成されたことを確認します。

```bash
$ docker network ls

NETWORK ID          NAME                DRIVER
a6c941a4d77e        node2/bridge        bridge
722e0e348884        node2/none          null
3260beae2418        node2/host          host
e90de0c47ecb        node1/none          null
4b5140f289f8        node1/host          host
ad297effc7c5        node1/bridge        bridge
ca21981bd1e3        my-net              overlay
```

my-netというネットワークがoverlayというDRIVERで作成されていれば成功です。

# Docker Composeをもちいて構築

準備が整ったので、Docker Composeで構築してみます。

[ELK(ElasticSearch, Logstash, Kibana)+fluentdの環境をDocker Composeで構築しつつ、試しにCloudWatchの統計データを収集してみた](http://qiita.com/khiraiwa/items/dc7e1c446d7e0c7d9482)のdocker-compose.ymlを下記のように変更しました。

環境変数のconstraint:nodeでノード名を指定すると、指定したノードにコンテナを起動してくれます。(ここはSwarmの機能)

linksオプションはサポートされていないので、コンテナ名を環境変数で渡すように変更しています。(コンテナ名でマルチホストネットワーキングのIPが引けます。)

```yaml:docker-compose.yml
# elasticsearch
elasticsearch:
  image: khiraiwa/docker-elasticsearch:1.0.0
  environment:
    - "constraint:node==node1"
  volumes:
    - /data_elasticsearch:/data_elasticsearch
  ports:
    - "9200:9200"
    - "9300:9300"

# kibana
kibana:
  image: khiraiwa/docker-kibana:1.1.0
  environment:
    - "constraint:node==node2"
    - "ELASTICSEARCH_HOST=elk_elasticsearch_1"
  volumes:
    - /data_kibana:/data_kibana
  ports:
    - "5601:5601"

# logstash
logstash:
  image: khiraiwa/docker-logstash:1.1.0
  environment:
    - "constraint:node==node2"
    - "ELASTICSEARCH_HOST=elk_elasticsearch_1"
  volumes:
    - /data_logstash:/data_logstash
  environment:
    - AWS_ACCESS_KEY_ID=dummy
    - AWS_SECRET_ACCESS_KEY=dummy

# tdagent
tdagent:
  image: khiraiwa/docker-tdagent:1.1.0
  environment:
    - "constraint:node==node2"
    - "ELASTICSEARCH_HOST=elk_elasticsearch_1"
  volumes:
    - /data_tdagent:/etc/td-agent
  environment:
    - AWS_ACCESS_KEY_ID=dummy
    - AWS_SECRET_ACCESS_KEY=dummy
```

実行前にmasterに向けます。

```bash
$ eval $(docker-machine env --swarm node1)
```

masterに対してdocker-composeでコンテナを構築します。
マルチホストネットワーキングを使用するときは--x-networkingのオプションが必要です。

```bash
$ docker-compose --x-networking --project-name=elk up -d

Creating elk_tdagent_1
Creating elk_elasticsearch_1
Pulling logstash (khiraiwa/docker-logstash:1.1.0)...
node2: Pulling khiraiwa/docker-logstash:1.1.0... : downloaded
node1: Pulling khiraiwa/docker-logstash:1.1.0... : downloaded
Creating elk_logstash_1
Pulling kibana (khiraiwa/docker-kibana:1.1.0)...
node2: Pulling khiraiwa/docker-kibana:1.1.0... : downloaded
node1: Pulling khiraiwa/docker-kibana:1.1.0... : downloaded
Creating elk_kibana_1
```

docker psコマンドを実行した結果です。

```bash
$ docker ps

CONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS              PORTS                                                          NAMES
b08e6bd64157        khiraiwa/docker-kibana:1.1.0          "/bin/sh -c 'sed -i -"   15 seconds ago      Up 14 seconds       192.168.99.102:5601->5601/tcp                                  node2/elk_kibana_1
d679cc7a97d6        khiraiwa/docker-logstash:1.1.0        "/bin/sh -c 'sudo cho"   2 minutes ago       Up 2 minutes                                                                       node2/elk_logstash_1
eed341792147        khiraiwa/docker-elasticsearch:1.0.0   "/bin/sh -c 'sudo cho"   4 minutes ago       Up 4 minutes        192.168.99.101:9200->9200/tcp, 192.168.99.101:9300->9300/tcp   node1/elk_elasticsearch_1
2288e18e3fef        khiraiwa/docker-tdagent:1.1.0         "/bin/sh -c 'if [ ! -"   4 minutes ago       Up 4 minutes        5170/tcp, 9880/tcp, 5160/udp, 24224/udp, 24224/tcp             node2/elk_tdagent_1
```

これにて、node1, node2にまたがってコンテナが起動してマルチホストネットワークも構築されたことになります。

下記、node2のelk_kibana_1コンテナにログインして、ネットワークインタフェースをチェックしてみたものです。
eth0がマルチホストネットワーキングで使用しているmy-netに属しています。

```
$ ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
30: eth0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
    link/ether 02:42:0a:00:02:05 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.5/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:aff:fe00:205/64 scope link
       valid_lft forever preferred_lft forever
32: eth1@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe12:4/64 scope link
       valid_lft forever preferred_lft forever
```

試しに、別ノード(node1)のElasticSearchに対して、コンテナ名を指定してpingを打ってみると応答が返ってきました。
同じネットワークに属していて、コンテナ名でIPが引けていることがわかります。

```bash
$ ping elk_elasticsearch_1

PING elk_elasticsearch_1 (10.0.2.3) 56(84) bytes of data.
64 bytes from elk_elasticsearch_1 (10.0.2.3): icmp_seq=1 ttl=64 time=0.819 ms
64 bytes from elk_elasticsearch_1 (10.0.2.3): icmp_seq=2 ttl=64 time=0.742 ms
64 bytes from elk_elasticsearch_1 (10.0.2.3): icmp_seq=3 ttl=64 time=0.790 ms
64 bytes from elk_elasticsearch_1 (10.0.2.3): icmp_seq=4 ttl=64 time=0.693 ms
```

/etc/hostsを見てみると、下記のようにコンテナ名で名前解決できるように設定されていることがわかります。

```bash
$ cat /etc/hosts

10.0.2.5	b08e6bd64157
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.2.4	elk_logstash_1.elk
10.0.2.2	elk_tdagent_1
10.0.2.2	elk_tdagent_1.elk
10.0.2.3	elk_elasticsearch_1
10.0.2.3	elk_elasticsearch_1.elk
10.0.2.4	elk_logstash_1
```
# Kibanaを表示する
下記で、Kibanaが起動しているノードのIPを取得します。

```bash
$ docker-machine ip node2

192.168.99.101
```

下記のコマンドでKibanaの起動しているノードにポートマッピングします。
(sshのデフォルトパスワードはtcuserになります。)

```bash
$ ssh docker@$(docker-machine ip node2) -L 5601:localhost:5601
```

ブラウザで、
http://localhost:5601/
にアクセスするとKibanaが表示されます。