Apache ZooKeeper のクラスタを構築する

今回も作業ログです.
自分用メモとも言う…:smirk:

# 環境

ZooKeeper のクラスタは奇数台で構成される必要があるので, 少なくとも 3 台のサーバが必要になります.
借りている ConoHa VPS 3 台　を使ってクラスタを構築してみます.
3 台構成の場合 1 ノードが死んでもクラスタは正常に動作することが可能です.

ホスト名, スペック, OS は以下の通り.

* node01: ConoHa 1GB プラン, CentOS 7.3
* node02: ConoHa 1GB プラン, CentOS 7.3
* node03: ConoHa 2GB プラン, CentOS 7.3

ホスト名は各サーバの `/etc/hosts` に書くなり, DNS で名前解決できるようにするなり適当に…

# 事前準備

ZooKeeper は実行時に Java が必要になるので, インストールしておきます.

```
$ sudo yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel
```

ZooKeeper 実行用のユーザを作成します.
あまり気にしない人は root で動かしても良いかもしれない?

```
$ sudo useradd -s /sbin/nologin -M zookeeper
```

# インストール

執筆時点での最新版 (v3.4.9) をインストールします.
今回は `/usr/local` にインストールすることにしました.

```
$ wget http://ftp.jaist.ac.jp/pub/apache/zookeeper/current/zookeeper-3.4.9.tar.gz
$ tar zxvf zookeeper-3.4.9.tar.gz
$ sudo mv zookeeper-3.4.9 /usr/local
$ sudo chown -R zookeeper.zookeeper /usr/local/zookeeper-3.4.9
$ sudo ln -s /usr/local/zookeeper-3.4.9 /usr/local/zookeeper
```

# 設定

ZooKeeper を運用するための最低限の設定をします.

```
$ sudo cp /usr/local/zookeeper/conf/zoo_sample.cfg /usr/local/zookeeper/conf/zoo.cfg
$ sudo vi /usr/local/zookeeper/conf/zoo.cfg
```

少なくともデータディレクトリだけは変更してください.
デフォルトの設定は `/tmp` になっているのでサーバを再起動させるとデータが飛びます.
(データディレクトリの変更を忘れて, 何回か必要だったデータをふっ飛ばした経験が…:sob:)

```:zoo.cfg
dataDir=/var/lib/zookeeper
```

設定ファイル末尾にクラスタ構築用の設定を追加します.
`<nodeId>`　はクラスタ内で各ノードが一意となる数字です.
また `<port1>` はリーダ接続用ポート, `<port2>` はリーダ選出で使用されるポートです.

```:書式
server.<nodeId>=<hostname>:<port1>:<port2>
```

今回は以下のように `<nodeId>` とホスト名を関連付けました.

```:zoo.cfg
server.1=node01:2888:3888
server.2=node02:2888:3888
server.3=node03:2888:3888
```

次に, データディレクトリを用意します.

```
$ sudo mkdir /var/lib/zookeeper
$ sudo chown zookeeper.zookeeper /var/lib/zookeeper
```

各ノードが自分の `<nodeId>` が認識できるようにデータディレクトリ直下に `<nodeId>` が書かれた `myid` ファイルを作成します.

```
$ echo <nodeId> | sudo tee /var/lib/zookeeper/myid
```

最後に systemd 用の service ファイルを書いておきます.

```
$ sudo vi /usr/lib/systemd/system/zookeeper.service
```

```:zookeeper.service
[Unit]
Description=Apache ZooKeeper
After=network.target

[Service]
User=zookeeper
ExecStart=/usr/local/zookeeper/bin/zkServer.sh start-foreground
KillMode=process
Restart=always

[Install]
WantedBy=multi-user.target
```

# ファイアウォール設定

ZooKeeper は基本的に TCP でしか通信を行わないはずです.
クライアントが接続するポート (デフォルトは 2181/tcp) とノード間が通信するポート (前述した 2 つ) が接続できるようにしておきます.

```
$ sudo firewall-cmd --add-port 2181/tcp --add-port 2888/tcp --add-port 3888/tcp --permanent
$ sudo firewall-cmd --reload
```

# 起動

systemd に登録してあるのでサービスの起動は簡単です.
必要に応じて自動起動設定をしておきます.

```
$ sudo systemctl start zookeeper
$ sudo systemctl enable zookeeper // optional
```

# 確認

各ノードの状態を確認して, クラスタが正常に動作しているか確認してみます.

```:node01
$ /usr/local/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: follower
```

```:node02
$ /usr/local/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: follower
```

```:node03
$ /usr/local/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: leader
```

今回は node03 が leader となり, 他のノードは follower となりました.
ここで ZooKeepr クラスタの耐障害性を試すために leader である node03 を止めてみます.

```:node03
$ sudo systemctl stop zookeeper
```

```:node01
$ /usr/local/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: follower
```

```:node02
$ /usr/local/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: leader
```

今度は node02 が leader として選出されました.
最後に現在の leader である node02 を止めてみます.

```:node02
$ sudo systemctl stop zookeeper
```

```:node01
$ /usr/local/zookeeper/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Error contacting service. It is probably not running.
```

ZooKeeper クラスタが動作するためには以下の式で求められる数のノードが動作している必要があります.

```math
最低ノード数 = \frac{クラスタ内の全ノード数}{2} + 1
```

今回は 3 ノードでクラスタを構成しているため, クラスタを動作するためには 2 ノード以上が動作している必要があります.
そのため 1 ノードだけ動作している状態ではエラーとなり, サービスを提供できません.
もちろんエラーになったクラスタも, 正常に動作しているノード数が最低以上になれば動作を再開することができます.

# 最後に

ZooKeeper は分散アプリケーションでよく用いられるミドルウェアなので, 構築方法を覚えておくと今後役立つかも??