docker-compose で zookeeper のクラスタのフェイルオーバーを試す

[docker\-compose で zookeeper のクラスタを立ち上げて動作確認する \- Qiita](http://qiita.com/tily/items/faf3c7bcd072ffb4e19c) の続き。今度はフェイルオーバーを試してみる。

## leader/follower の確認

まず、クラスタ内のどのコンテナが leader でどのコンテナが follower なのかを確認できるようしておく。

基本的には、各プロセスが LISTEN してる TCP ポートに `srvr` という文字列を送ると、そのプロセスの Mode を取得することができる。

```
$ echo srvr | docker exec -i zookeeper_zoo1_1 nc localhost 2181
Zookeeper version: 3.4.9-1757313, built on 08/23/2016 06:50 GMT
Latency min/avg/max: 0/2/35
Received: 73
Sent: 72
Connections: 3
Outstanding: 0
Zxid: 0x50000000c
Mode: follower
Node count: 5
```

情報量が多いので、3 つのコンテナに上記のリクエストを投げて Mode 部分だけ表示するシェルを書いた。

```
$ cat cluster-status.sh 
for i in `seq 1 3`; do
  container_name=zookeeper_zoo${i}_1
  echo -n "${container_name} -> "
  echo srvr | docker exec -i ${container_name} nc localhost 2181 | grep Mode | cut -d " " -f2
done
```

今は 2 番目のコンテナ zoo2 が leader になっていることが分かる。

```
$ sh cluster-status.sh 
zookeeper_zoo1_1 -> follower
zookeeper_zoo2_1 -> leader
zookeeper_zoo3_1 -> follower
```

## 1 台落としてみる

この状態で、leader を停止してみる。

```
$ docker-compose pause zoo2
Pausing zookeeper_zoo2_1 ... done

```

leader が zoo3 に変更された。

```
$ sh cluster-status.sh 
zookeeper_zoo1_1 -> follower
zookeeper_zoo2_1 -> Error response from daemon: Container zookeeper_zoo2_1 is paused, unpause the container before exec
zookeeper_zoo3_1 -> leader
```

zoo1 でも zoo3 でも `get /helloworld` できる。

```
$ docker exec -ti zookeeper_zoo1_1 bin/zkCli.sh -server zookeeper_zoo1_1 get /helloworld
(略)
this is test updated
(略)

$ docker exec -ti zookeeper_zoo3_1 bin/zkCli.sh -server zookeeper_zoo3_1 get /helloworld
(略)
this is test updated
(略)
```

`set /helloworld "written while zoo2 is down"` しておく。

```
$ docker exec -ti zookeeper_zoo1_1 bin/zkCli.sh -server zookeeper_zoo1_1 set /helloworld "written while zoo2 is down"
(略)
```

## もう 1 台落としてみる

さらにもう 1 台落としてみる。

```
$ docker-compose pause zoo1
Pausing zookeeper_zoo1_1 ... done
```

**zookeper クラスタは過半数のメンバーが生きていないと機能しない**ので、zoo3 での `get /helloworld` は失敗する。

```
$ docker exec -ti zookeeper_zoo3_1 bin/zkCli.sh get /helloworld
(略)
2017-01-12 06:19:10,409 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1158] - Unable to read additional data from server sessionid 0x0, likely server has closed socket, closing socket connection and attempting reconnect
Exception in thread "main" org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /helloworld
        at org.apache.zookeeper.KeeperException.create(KeeperException.java:99)
        at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
        at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:1212)
        at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:1241)
        at org.apache.zookeeper.ZooKeeperMain.processZKCmd(ZooKeeperMain.java:725)
        at org.apache.zookeeper.ZooKeeperMain.processCmd(ZooKeeperMain.java:599)
        at org.apache.zookeeper.ZooKeeperMain.run(ZooKeeperMain.java:362)
        at org.apache.zookeeper.ZooKeeperMain.main(ZooKeeperMain.java:290)
```

cluster-stautus.sh でも残ったコンテナ zoo3 の Mode が表示できなくなった。

```
$ sh cluster-status.sh 
zookeeper_zoo1_1 -> Error response from daemon: Container zookeeper_zoo1_1 is paused, unpause the container before exec
zookeeper_zoo2_1 -> Error response from daemon: Container zookeeper_zoo2_1 is paused, unpause the container before exec
zookeeper_zoo3_1 ->
```

## 復旧させてみる

zoo1, zoo2 の一時停止を解除。

```
$ docker-compose unpause zoo1 zoo2
Unpausing zookeeper_zoo2_1 ... done
Unpausing zookeeper_zoo1_1 ... done
```

どのノードでも `get /helloworld` できるようになった。さらに、zoo2 が落ちていた間に更新されたデータが zoo2 にも反映されていることも分かる。

```
$ docker exec -ti zookeeper_zoo1_1 bin/zkCli.sh get /helloworld
(略)
written while zoo2 is down
(略)

$ docker exec -ti zookeeper_zoo2_1 bin/zkCli.sh get /helloworld
(略)
written while zoo2 is down
(略)

$ docker exec -ti zookeeper_zoo3_1 bin/zkCli.sh get /helloworld
(略)
written while zoo2 is down
(略)
```

## まとめ

zookeeper クラスタのフェイルオーバーの基本的な挙動を確認することができた。

クラスタ内の過半数より少ないサーバーが落ちてもサービス継続できるところと、一度落ちてしまったサーバーを再度クラスタに参加させるのが簡単そうなところが、よさそうに思った。

より詳しい内部的な仕様を知ってみたいというのと、[Hadoop本読書会 \- 13章 ZooKeeper \- 大規模分散技術勉強会 in 名古屋](http://seesaawiki.jp/dainagoya/d/Hadoop%CB%DC%C6%C9%BD%F1%B2%F1%20-%2013%BE%CF%20ZooKeeper) に書いてあるような watcher とか他の機能もそのうち試してみたい。

## 参考資料

* [library/zookeeper \- Docker Hub](https://hub.docker.com/_/zookeeper/)
* [ZooKeeper Getting Started Guide](https://zookeeper.apache.org/doc/r3.3.3/zookeeperStarted.html)
* [zookeeper leader follower 確認　コマンド \- Qiita](http://qiita.com/pioho07/items/43e81656a46b51cebc37)
* [Hadoop本読書会 \- 13章 ZooKeeper \- 大規模分散技術勉強会 in 名古屋](http://seesaawiki.jp/dainagoya/d/Hadoop%CB%DC%C6%C9%BD%F1%B2%F1%20-%2013%BE%CF%20ZooKeeper)
* [Cannot use shell pipes with docker exec · Issue \#8679 · docker/docker](https://github.com/docker/docker/issues/8679)