Swarm cluster with multiple Consul nodes

[この投稿 (Docker Swarm Cluster without docker-machine)](http://qiita.com/hisak/items/c6c69b0825efc67a0cf4)を書いている時に思った。SwarmプロセスをHAで複数立ち上げても、この構成ではConsulプロセスが一つしかないからConsulが死んだらサービス停止だと。Consul自体は複数ノードで運用できる (詳しくは[こちら](https://hub.docker.com/r/progrium/consul/))ので、今回は複数ノードで立ち上がっているConsulとSwarmをどのように組み合わせるか実験してみた。

結論から言ってしまうと、Swarmプロセスを起動時に紐付けるConsulノードは一つしか記述できないようだ。[Docker Swarm Discovery](https://github.com/hisaki/swarm/blob/master/docs/discovery.md)の例にEtcdは`swarm join --advertise=<node_ip:2375> etcd://<etcd_addr1>,<etcd_addr2>/<optional path prefix>`とあるのに対し、Consulでは`swarm join --advertise=<node_ip:2375> consul://<consul_addr>/<optional path prefix>`とあり、いかにも複数ノード記述できなそう。実際に[docker/libkv/store/consul/consul.go](https://github.com/docker/libkv/blob/master/store/consul/consul.go)に次のような記述がある。

```go
var (
	// ErrMultipleEndpointsUnsupported is thrown when there are
	// multiple endpoints specified for Consul
	ErrMultipleEndpointsUnsupported = errors.New("consul does not support multiple endpoints")

	// ErrSessionRenew is thrown when the session can't be
	// renewed because the Consul version does not support sessions
	ErrSessionRenew = errors.New("cannot set or renew session for ttl, unable to operate on sessions")
)
<snip>
func New(endpoints []string, options *store.Config) (store.Store, error) {
	if len(endpoints) > 1 {
		return nil, ErrMultipleEndpointsUnsupported
	}
```

ということで、Swarm起動時に紐付けたConsulノードが行方不明になった場合、そのノードを利用していたSwarmプロセスはService Discoveryを出来なくなってしまう。これでは話が終わってしまうので、ConsulノードをServerとClientの組合せとし、ConsulのClientノードをproxyとして見立ててSwarmプロセスと紐付けることを試してみた。proxyが落ちれば、そのproxyと紐付けられたSwarmももちろんサービス停止となる。構成図は次の通り。

![swarm-consul2.PNG](https://qiita-image-store.s3.amazonaws.com/0/32090/9df5964e-e9fc-0667-7fa7-21eaab00f28c.png)

7つのVMを利用する。Consul用に10.0.1.0/24のアドレスを割り当てた。一覧は次の通り。

| VM name | IP address | IP address for consul |
|:-----------|:------------:|:------------:|
| consul-server1 | 192.168.209.168 | 10.0.1.129 |
| consul-server2 | 192.168.209.169 | 10.0.1.130 |
| consul-server3 | 192.168.209.171 | 10.0.1.134 |
| consul-client | 192.168.209.170 | 10.0.1.131 |
| swarm-mgr | 192.168.209.138 | 10.0.1.128 |
| swarm-node1 | 192.168.209.166 | 10.0.1.132 |
| swarm-node2 | 192.168.209.167 | 10.0.1.133 |

(ああ、めんどくさい。)

では、早速Consulクラスターを立ち上げる。

```shell-session:consul-server1
$ docker run --rm progrium/consul cmd:run 10.0.1.129 -d
eval docker run --name consul -h $HOSTNAME      -p 10.0.1.129:8300:8300         \
-p 10.0.1.129:8301:8301         -p 10.0.1.129:8301:8301/udp     -p 10.0.1.129:8302:8302 \
-p 10.0.1.129:8302:8302/udp      -p 10.0.1.129:8400:8400         -p 10.0.1.129:8500:8500 \
-p 172.17.0.1:53:53     -p 172.17.0.1:53:53/udp         -d      progrium/consul \
-server -advertise 10.0.1.129 -bootstrap-expect 3
```

`progrium/consul cmd:run`でこのようにevalな形で実行形式の文字列を返してくれる。以下を実際には実行する。

```shell-session:consul-server1
$ docker run --name consul1 -p 10.0.1.129:8300:8300 \
                            -p 10.0.1.129:8301:8301 \
                            -p 10.0.1.129:8301:8301/udp \
                            -p 10.0.1.129:8302:8302 \
                            -p 10.0.1.129:8302:8302/udp \
                            -p 10.0.1.129:8400:8400 \
                            -p 10.0.1.129:8500:8500 \
                            -p 172.17.0.1:53:53 \
                            -p 172.17.0.1:53:53/udp \
                            -d progrium/consul -server -advertise 10.0.1.129 \
                            -bootstrap-expect 3
```

`consul-server2`と`consul-server3`をConsulクラスターにjoinさせる。

```shell-session:consul-server2
$ docker run --name consul2 -p 10.0.1.130:8300:8300 \
                            -p 10.0.1.130:8301:8301 \
                            -p 10.0.1.130:8301:8301/udp \
                            -p 10.0.1.130:8302:8302 \
                            -p 10.0.1.130:8302:8302/udp \
                            -p 10.0.1.130:8400:8400 \
                            -p 10.0.1.130:8500:8500 \
                            -p 172.17.0.1:53:53 \
                            -p 172.17.0.1:53:53/udp \
                            -d progrium/consul -server -advertise 10.0.1.130 \
                            -join 10.0.1.129
```

```shell-session:consul-server3
$ docker run --name consul3 -p 10.0.1.134:8300:8300 \
                            -p 10.0.1.134:8301:8301 \
                            -p 10.0.1.134:8301:8301/udp \
                            -p 10.0.1.134:8302:8302 \
                            -p 10.0.1.134:8302:8302/udp \
                            -p 10.0.1.134:8400:8400 \
                            -p 10.0.1.134:8500:8500 \
                            -p 172.17.0.1:53:53 \
                            -p 172.17.0.1:53:53/udp \
                            -d progrium/consul -server -advertise 10.0.1.134 \
                            -join 10.0.1.129
```

最後に`consul-clinet`をクライアントとしてjoinさせる。`-server`の指定がなくなっただけ。

```shell-session:consul-client
$ docker run --name consul-cli -p 10.0.1.131:8300:8300 \
                               -p 10.0.1.131:8301:8301 \
                               -p 10.0.1.131:8301:8301/udp \
                               -p 10.0.1.131:8302:8302 \
                               -p 10.0.1.131:8302:8302/udp \
                               -p 10.0.1.131:8400:8400 \
                               -p 10.0.1.131:8500:8500 \
                               -p 172.17.0.1:53:53 \
                               -p 172.17.0.1:53:53/udp \
                               -d progrium/consul -advertise 10.0.1.131 \
                               -join 10.0.1.129
```

Consulクラスターのmemberを確認してみる。大丈夫そう。

```shell-session:consul-server1
$ docker exec consul1 consul members
Node          Address          Status  Type    Build  Protocol  DC
1011d34dc1c5  10.0.1.129:8301  alive   server  0.5.2  2         dc1
b0d93c5dd007  10.0.1.130:8301  alive   server  0.5.2  2         dc1
c277ab2f8165  10.0.1.134:8301  alive   server  0.5.2  2         dc1
375a31f7fb52  10.0.1.131:8301  alive   client  0.5.2  2         dc1
```

ではいよいよSwarm Managerを立ち上げる。Consulのbindingには`consul-client (10.0.1.131)`を指定する。

```shell-session:swarm-manager
$ docker run --restart=unless-stopped -d -p 3375:2375 swarm manage \
--replication --advertise 192.168.209.138:3375 consul://10.0.1.131:8500
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
6461f48b1b04        swarm               "/swarm manage --repl"   4 seconds ago       Up 3 seconds        0.0.0.0:3375->2375/tcp   tiny_raman
```

最後にswarm nodeを追加する。`docker info`の結果からきちんと追加されたことが分かる。

```shell-session:swarm-manager
$ docker -H=tcp://192.168.209.166:2375 run -d swarm join \
--advertise=192.168.209.166:2375 consul://10.0.1.131:8500
$ docker -H=tcp://192.168.209.167:2375 run -d swarm join \
--advertise=192.168.209.167:2375 consul://10.0.1.131:8500
$ DOCKER_HOST=192.168.209.138:3375 docker info
Containers: 27
 Running: 2
 Paused: 0
 Stopped: 25
Images: 14
Server Version: swarm/1.2.0
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 ubu-swarm-node1: 192.168.209.166:2375
  └ Status: Healthy
  └ Containers: 15
  └ Reserved CPUs: 0 / 2
  └ Reserved Memory: 1 GiB / 4.047 GiB
  └ Labels: executiondriver=, kernelversion=3.13.0-85-generic, operatingsystem=Ubuntu 14.04.4 LTS, storagedriver=aufs
  └ Error: (none)
  └ UpdatedAt: 2016-05-02T14:49:12Z
  └ ServerVersion: 1.11.0
 ubu-swarm-node2: 192.168.209.167:2375
  └ Status: Healthy
  └ Containers: 12
  └ Reserved CPUs: 0 / 2
  └ Reserved Memory: 1 GiB / 4.047 GiB
  └ Labels: executiondriver=, kernelversion=3.13.0-85-generic, operatingsystem=Ubuntu 14.04.4 LTS, storagedriver=aufs
  └ Error: (none)
  └ UpdatedAt: 2016-05-02T14:48:49Z
  └ ServerVersion: 1.11.0
Plugins:
 Volume:
 Network:
Kernel Version: 3.13.0-85-generic
Operating System: linux
Architecture: amd64
CPUs: 4
Total Memory: 8.094 GiB
Name: 6461f48b1b04
Docker Root Dir:
Debug mode (client): false
Debug mode (server): false
WARNING: No kernel memory limit support
```

適当にコンテナを起動してみる。

```shell-session:swarm-manager
$ DOCKER_HOST=192.168.209.138:3375 docker run -dit ubuntu /bin/bash
$ DOCKER_HOST=192.168.209.138:3375 docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
9bd2e4eccc01        ubuntu              "/bin/bash"         5 seconds ago       Up 4 seconds                            ubu-swarm-node2/hungry_archimedes
$ DOCKER_HOST=192.168.209.138:3375 docker exec -it ubu-swarm-node2/hungry_archimedes bash
root@9bd2e4eccc01:/#
```

`consul-server1`のconsulを落としてみる。

```shell-session:consul-server1
$ docker stop consul1
```

次のように、落とした直後はErrorが返ってくるが、leader electionが実行されると処理を継続することができた。ちなみにConsulサーバーが2台構成の場合、1つを落とすとleader electionが出来ずswarmの処理は停止してしまう。3台以上のサーバーが必要らしい。

```shell-session:swarm-manager
$ DOCKER_HOST=192.168.209.138:3375 docker ps
Error response from daemon: No elected primary cluster manager
$ DOCKER_HOST=192.168.209.138:3375 docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
66a76e873e51        ubuntu              "/bin/bash"         56 seconds ago      Up 55 seconds                           ubu-swarm-node2/drunk_galileo
```

`consul-cli`のconsulを落としてみる。

```shell-session:consul-cli
$ docker stop consul-cli
```

冒頭に述べたようにこの場合はswarmの処理を継続できない。

```shell-session:swarm-manager
$ DOCKER_HOST=192.168.209.138:3375 docker ps
Error response from daemon: No elected primary cluster manager
```

おしまい。