Apache ZooKeeperのレプリケーションモード動作を試してみる

以前の記事"[Go言語から、ZookeeperでのHA動作を試してみる](http://qiita.com/ttsubo/items/554b780d7b22347bab5f)"で、ZooKeeperを活用したGolangアプリのHA動作を紹介しましたが、ここでの記事の課題は、「ZooKeeperがStandAloneモードで動作しているため、もし、ZooKeeperが期待通り動作しなくなってしまったら、Golangアプリも動作できなくなってしまう」というものがありました。
そこで、今回は、ZooKeeperのSPOF問題を解決する方法として、レプリケーションモードで動作させてみたいと思います。

# ⬛︎ ZooKeeperの環境整備

Zookeeperレプリケーションモードでの動作を確認するにあたり、Ubuntuサーバを３台用意します。

- zookeeper-1（IP: 192.168.0.1）
- zookeeper-2（IP: 192.168.0.2）
- zookeeper-3（IP: 192.168.0.3）

##(1) Linux環境準備
zookeeper-1での作業履歴を記載します。

- Ubunts環境整備する

```
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"

$ sudo apt-get install git
```

- ネットワーク設定を確認する

```
$ 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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:57:33:bd brd ff:ff:ff:ff:ff:ff
    inet 192.168.195.168/24 brd 192.168.195.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe57:33bd/64 scope link 
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:57:33:c7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/24 brd 192.168.0.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe57:33c7/64 scope link 
       valid_lft forever preferred_lft forever
```

- 他のUbuntuサーバとの接続性を確認しておく

```
$ ping 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.598 ms
64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.495 ms
64 bytes from 192.168.0.2: icmp_seq=3 ttl=64 time=0.493 ms
^C
--- 192.168.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.493/0.528/0.598/0.055 ms

$ ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.815 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.462 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.475 ms
^C
--- 192.168.0.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.462/0.584/0.815/0.163 ms
```
- JDK環境をインストールし、JAVA_HOME環境変数にセットする

```
$ sudo apt-get install default-jdk
$ sudo update-alternatives --list java
/usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java

$ cd $HOME
$ vi .profile

...(snip)
export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64
```

## (2) golang環境整備
以前の記事"[Go言語から、ZookeeperでのHA動作を試してみる](http://qiita.com/ttsubo/items/554b780d7b22347bab5f)"では、Mac OS X環境を前提としましたが、今回は、Ubuntu環境上にインストールすることにします。

- golangをダウンロードする

```
$ cd $HOME
$ wget --no-check-certificate https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
$ mkdir $HOME/golang
$ vi .profile

...(snip)
export GOPATH=$HOME/golang
export PATH=$GOPATH/bin:/usr/local/go/bin:$PATH

$ source .profile
$ go version
go version go1.6 linux/amd64
```
- Native Go Zookeeper Client Libraryをインストールする

```
$ go get github.com/samuel/go-zookeeper/zk
```



## (3) ZooKeeper環境構築
「[ZooKeeper スタートガイド / レプリケーションモードでの ZooKeeper の実行](http://oss.infoscience.co.jp/hadoop/zookeeper/docs/r3.3.1/zookeeperStarted.html#sc_RunningReplicatedZooKeeper)」を参考にして、ZooKeeper環境を整備します。

- ZooKeeperをダウンロードする

```
$ cd $HOME
$ wget http://www-eu.apache.org/dist/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz
$ sudo tar -C /opt -xzf zookeeper-3.4.8.tar.gz
$ cd /opt/zookeeper-3.4.8
$ mkdir data
$ cd conf
```

- ZooKeeper設定ファイルを編集する

```
$ vi zoo.cfg 
tickTime=2000
dataDir=/opt/zookeeper-3.4.8/data
clientPort=2181
initLimit=5
syncLimit=2

## the zookeeper server quorum
server.1=192.168.0.1:2888:3888
server.2=192.168.0.2:2888:3888
server.3=192.168.0.3:2888:3888
```
- ZooKeeperアンサンブルでのサーバ識別IDを設定する（zooker-1の場合なので、"1"を設定する）

```
$ cd /opt/zookeeper-3.4.8/data
$ vi myid
1
```

- ZooKeepコマンドのパスを、ログイン環境設定に追加する

```
$ cd $HOME
$ vi .profile

...(snip)
export PATH=/opt/zookeeper-3.4.8/bin:$GOPATH/bin:/usr/local/go/bin:$PATH

$ source .profile
```

# ⬛︎ ZooKeeperレプリケーションモードでの基本動作を確認してみる
## (1) ZooKeeperの起動
"zookeeper-1", "zookeeper-2", "zookeeper-3"の環境整備が完了したら、３台ともZookeeperを起動します。
「[ZooKeeper 管理者ガイド](http://oss.infoscience.co.jp/hadoop/zookeeper/docs/r3.3.1/zookeeperStarted.html#sc_RunningReplicatedZooKeeper)」が参考になると思います。

- ZooKeeperを起動する

```
$ zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.8/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
```
- ZooKeeper起動結果を確認する

```
$ echo ruok | nc localhost 2181
imok
```
## (2) ZooKeeperのLeader確認
３台とも、ZooKeeperが起動したのちに、ZooKeeperのLeaderを確認しておきます。

- "zookeeper-1"で、ZooKeeper動作状態を確認する

```
$ jps
3119 Jps
2980 QuorumPeerMain

$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.8/bin/../conf/zoo.cfg
Mode: follower

$ echo srvr | nc localhost 2181
Zookeeper version: 3.4.8--1, built on 02/06/2016 03:18 GMT
Latency min/avg/max: 0/0/0
Received: 4
Sent: 3
Connections: 1
Outstanding: 0
Zxid: 0x2400000000
Mode: follower
Node count: 4
```

- "zookeeper-2"で、ZooKeeper動作状態を確認する

```
$ jps
2064 Jps
1873 QuorumPeerMain

$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.8/bin/../conf/zoo.cfg
Mode: follower

$ echo srvr | nc localhost 2181
Zookeeper version: 3.4.8--1, built on 02/06/2016 03:18 GMT
Latency min/avg/max: 0/0/0
Received: 8
Sent: 7
Connections: 1
Outstanding: 0
Zxid: 0x2500000000
Mode: follower
Node count: 4
```

- "zookeeper-3"で、ZooKeeper動作状態を確認する

```
$ jps
2020 Jps
1918 QuorumPeerMain

$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.8/bin/../conf/zoo.cfg
Mode: leader

$ echo srvr | nc localhost 2181
Zookeeper version: 3.4.8--1, built on 02/06/2016 03:18 GMT
Latency min/avg/max: 0/0/0
Received: 8
Sent: 7
Connections: 1
Outstanding: 0
Zxid: 0x2500000000
Mode: leader
Node count: 4
```
<b>以上より、"zookeeper-3"が、Leaderとして動作していることが確認できました。</b>

## (3) 試しに、現行Leaderの"zookeeper-3"をOS停止してみる
現行Leaderが停止してしまった場合、残りの２台のZooKeeperで新たなLeaderが選定されることを確認します。

- "zookeeper-3"のOSを停止する

```
$ sudo halt
Broadcast message from tsubo@zookeeper-3
	(/dev/pts/0) at 14:56 ...

The system is going down for halt NOW!
```

- "zookeeper-1"で、ZooKeeper動作状態を確認する

```
$ echo srvr | nc localhost 2181
Zookeeper version: 3.4.8--1, built on 02/06/2016 03:18 GMT
Latency min/avg/max: 0/0/0
Received: 2
Sent: 1
Connections: 1
Outstanding: 0
Zxid: 0x2500000000
Mode: follower
Node count: 4
```

- "zookeeper-2"で、ZooKeeper動作状態を確認する

```
$ echo srvr | nc localhost 2181
Zookeeper version: 3.4.8--1, built on 02/06/2016 03:18 GMT
Latency min/avg/max: 0/0/0
Received: 2
Sent: 1
Connections: 1
Outstanding: 0
Zxid: 0x2600000000
Mode: leader
Node count: 4
```
<b>"zookeeper-3"のOS停止に伴い、"zookeeper-2"が新Leaderとして動作していることが確認できました。</b>

## (4) さらに、現行Leaderの"zookeeper-2"をOS停止してみる
さらに、現行Leaderが停止してしまった場合、残りの１台のZooKeeperで新たなLeaderが選定されることを確認します。

- "zookeeper-2"のOSを停止する

```
$ sudo halt
Broadcast message from tsubo@zookeeper-2
	(/dev/pts/0) at 15:00 ...

The system is going down for halt NOW!
```

- "zookeeper-1"で、ZooKeeper動作状態を確認する

```
$ echo srvr | nc localhost 2181
This ZooKeeper instance is not currently serving requests

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

<b>ZooKeeperレプリケーションモードでは、１台のZooKeeperでは動作しないようです。
</b>そこで、「[ZooKeeper 管理者ガイド / クラスタ (マルチサーバー) セットアップ](http://oss.infoscience.co.jp/hadoop/zookeeper/docs/r3.3.1/zookeeperStarted.html#sc_RunningReplicatedZooKeeper)」を確認してみると、次のような記載があります。
> 信頼性の高い ZooKeeper サービスを提供するには、アンサンブルと呼ばれるクラスタに ZooKeeper をデプロイする必要があります。アンサンブルの過半数が動作している限り、ZooKeeper サービスは利用可能です。ZooKeeper では過半数が必要になるので、奇数台のマシンを使うのがベストです。たとえば 4 台のマシンを使う場合、ZooKeeper が対応できるのはマシン 1 台の障害までです。なぜなら、2 台のマシンに障害が発生すると、残りの 2 台では過半数にならないためです。一方、5 台のマシンを使えば、ZooKeeper はマシン 2 台までの障害に対応できます。

なるほど、
１台のZooKeeperでは、レプリケーションモードが動作しない理由が理解できました。

## (5) ZooKeeperレプリケーションモードでのLeader Electionの仕組みは？

「[ZooKeeper の内部 / Leader Activation](http://oss.infoscience.co.jp/hadoop/zookeeper/docs/r3.3.1/zookeeperInternals.html#sc_quorum)」にて、ZooKeeperでのLeader Election方法が記載されておりますね。

# ⬛︎ GolangアプリでHA動作を試してみる
いよいよ、ここからが本番です。
GolangアプリのHA動作において、ZooKeeperのSPOF問題が解決できていることを確認したいと思います。

## (1) Golangアプリを配置する
GolangアプリのHA動作を試すための環境準備から始めます。
"zookeeper-1", "zookeeper-2", "zookeeper-3"、各々に、Golangアプリを配置します。

- storedデータ確認用サンプルアプリを配置する

```golang:sample_zookeeper_myApp.go
package main

import (
    "github.com/samuel/go-zookeeper/zk"
    "os"
    "os/signal"
    "strings"
    "strconv"
    "time"
    "log"
)

var RootPath string = "/zookeeper-for-myApp"
var zksStr string = os.Getenv("ZOOKEEPER_SERVERS")

func CreateRootPath(zks []string) *zk.Conn {
    flags := int32(0)
    acl := zk.WorldACL(zk.PermAll)
    conn, _, err := zk.Connect(zks, time.Second)
    conn.Create(RootPath, []byte{0}, flags, acl)
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    return conn
}


func JoinRootPath(zks []string) {
    acl := zk.WorldACL(zk.PermAll)
    conn, _, err := zk.Connect(zks, time.Second)
    if err != nil {
        panic(err)
    }
    lockPrefix := "/lock-"

    path, err := conn.CreateProtectedEphemeralSequential(RootPath+lockPrefix,
                                                         []byte{0}, acl)
    myData := getParse(path)
    ticker := time.NewTicker(time.Second)
    for {
        var isLeader bool = true
        children, _, err := conn.Children(RootPath)
        if err != nil {
            isLeader = false
        }
        if children == nil {
            isLeader = false
        }
        for _, c := range children {
            otherData := getParse(c)
            if myData > otherData {
                isLeader = false
                break
            }
        }

        if isLeader == true {
            log.Println("### I am Leader !! ###")
        }
        <-ticker.C
    }
}

func getParse(path string) int {
    splits := strings.Split(path, "-")
    seq := splits[len(splits)-1]
    data, _ := strconv.Atoi(seq)
    return data
}

func main() {
    zks := strings.Split(zksStr, ",")
    CreateRootPath(zks)
    quit_channel := make(chan os.Signal, 1)
    signal.Notify(quit_channel, os.Interrupt)
    go JoinRootPath(zks)
    <-quit_channel
}
```

## (2) Golangアプリ自体のHA動作を試してみる
それでは、各々のUbuntuサーバで、"ZooKeeper"と"sample_zookeeper_myApp"を起動して、Golangアプリ自体のHA動作を試してみます。

![sample_zookeeper1.jpeg](https://qiita-image-store.s3.amazonaws.com/0/103595/efc4dd22-714f-4c21-18ff-97e678f8a76a.jpeg)



- zookeeper-2上で、Leaderとして選定されていることを確認する

```
$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.8/bin/../conf/zoo.cfg
Mode: leader
```

- 各々のUbuntuサーバ上での環境変数を設定する

```
$ export ZOOKEEPER_SERVERS="192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181"
```

- zookeeper-1で、sample_zookeeper_myApp.goを起動する

```
$ go run sample_zookeeper_myApp.go 
2016/04/10 17:56:32 Connected to 192.168.0.1:2181
2016/04/10 17:56:32 Authenticated: id=95700235219042308, timeout=4000
2016/04/10 17:56:33 Recv loop terminated: err=EOF
2016/04/10 17:56:33 Send loop terminated: err=<nil>
2016/04/10 17:56:33 Connected to 192.168.0.2:2181
2016/04/10 17:56:33 Authenticated: id=167757829242421252, timeout=4000
2016/04/10 17:56:33 ### I am Leader !! ###
2016/04/10 17:56:34 ### I am Leader !! ###
2016/04/10 17:56:35 ### I am Leader !! ###
2016/04/10 17:56:36 ### I am Leader !! ###

...(snip)
```

- zookeeper-2で、sample_zookeeper_myApp.goを起動する

```
$ go run sample_zookeeper_myApp.go 
2016/04/10 17:56:36 Connected to 192.168.0.1:2181
2016/04/10 17:56:36 Authenticated: id=95700235219042309, timeout=4000
2016/04/10 17:56:36 Recv loop terminated: err=EOF
2016/04/10 17:56:36 Send loop terminated: err=<nil>
2016/04/10 17:56:36 Connected to 192.168.0.2:2181
2016/04/10 17:56:36 Authenticated: id=167757829242421253, timeout=4000
```
- zookeeper-3で、sample_zookeeper_myApp.goを起動する

```
$ go run sample_zookeeper_myApp.go 
2016/04/10 17:56:38 Connected to 192.168.0.1:2181
2016/04/10 17:56:38 Authenticated: id=95700235219042310, timeout=4000
2016/04/10 17:56:38 Recv loop terminated: err=EOF
2016/04/10 17:56:38 Send loop terminated: err=<nil>
2016/04/10 17:56:38 Connected to 192.168.0.2:2181
2016/04/10 17:56:38 Authenticated: id=167757829242421254, timeout=4000
```
- zookeeper-1で、sample_zookeeper_myApp.goを強制停止してみる

```
...(snip)

2016/04/10 17:56:37 ### I am Leader !! ###
2016/04/10 17:56:38 ### I am Leader !! ###
2016/04/10 17:56:39 ### I am Leader !! ###
2016/04/10 17:56:40 ### I am Leader !! ###
2016/04/10 17:56:41 ### I am Leader !! ###
2016/04/10 17:56:42 ### I am Leader !! ###
2016/04/10 17:56:43 ### I am Leader !! ###
^C
```
- すると、zookeeper-2上の、sample_zookeeper_myAppが新たなLeaderとして選定されることが確認できる。さらに、zookeeper-2で、sample_zookeeper_myApp.goを強制停止してみる。

```
...(snip)

2016/04/10 17:56:36 Authenticated: id=167757829242421253, timeout=4000
2016/04/10 17:56:48 ### I am Leader !! ###
2016/04/10 17:56:49 ### I am Leader !! ###
2016/04/10 17:56:50 ### I am Leader !! ###
2016/04/10 17:56:51 ### I am Leader !! ###
2016/04/10 17:56:52 ### I am Leader !! ###
2016/04/10 17:56:53 ### I am Leader !! ###
2016/04/10 17:56:54 ### I am Leader !! ###
2016/04/10 17:56:55 ### I am Leader !! ###
2016/04/10 17:56:56 ### I am Leader !! ###
^C
```
- すると、zookeeper-3上の、sample_zookeeper_myAppが新たなLeaderとして選定されることが確認できる。

```
...(snip)

2016/04/10 17:56:38 Authenticated: id=167757829242421254, timeout=4000
2016/04/10 17:57:02 ### I am Leader !! ###
2016/04/10 17:57:03 ### I am Leader !! ###
2016/04/10 17:57:04 ### I am Leader !! ###
2016/04/10 17:57:05 ### I am Leader !! ###
2016/04/10 17:57:06 ### I am Leader !! ###
2016/04/10 17:57:07 ### I am Leader !! ###
2016/04/10 17:57:08 ### I am Leader !! ###
```

以上より、以前の記事"[Go言語から、ZookeeperでのHA動作を試してみる](http://qiita.com/ttsubo/items/554b780d7b22347bab5f)"でのGolangサンプルアプリのHA動作が期待通りに動作することを再確認できました。

## (3) いよいよ、Golangアプリ動作中に、ZooKeeperのLeader選定を行ってみる

![sample_zookeeper2.jpeg](https://qiita-image-store.s3.amazonaws.com/0/103595/8e32bbdf-653f-2633-0494-7137959706bc.jpeg)

- zookeeper-1で、sample_zookeeper_myApp.goを起動する

```
$ go run sample_zookeeper_myApp.go 
2016/04/10 18:34:04 Connected to 192.168.0.1:2181
2016/04/10 18:34:04 Authenticated: id=95700235219042311, timeout=4000
2016/04/10 18:34:04 Recv loop terminated: err=EOF
2016/04/10 18:34:04 Send loop terminated: err=<nil>
2016/04/10 18:34:04 Connected to 192.168.0.2:2181
2016/04/10 18:34:04 Authenticated: id=167757829242421255, timeout=4000
2016/04/10 18:34:04 ### I am Leader !! ###
2016/04/10 18:34:05 ### I am Leader !! ###
2016/04/10 18:34:06 ### I am Leader !! ###

...(snip)
```
- Leaderとして選定されているzookeeper-2上で、OS停止してみる

```
$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.8/bin/../conf/zoo.cfg
Mode: leader

$ sudo halt
Broadcast message from tsubo@zookeeper-2
	(/dev/pts/0) at 18:34 ...

The system is going down for halt NOW!
```
- すると、数秒の間、Golangアプリは、動作しなくなるが、しばらくすると、動作は回復することが確認できる

```
...(snip)

2016/04/10 18:34:09 ### I am Leader !! ###
2016/04/10 18:34:10 ### I am Leader !! ###
2016/04/10 18:34:11 ### I am Leader !! ###
2016/04/10 18:34:12 ### I am Leader !! ###
2016/04/10 18:34:13 ### I am Leader !! ###
2016/04/10 18:34:14 Recv loop terminated: err=EOF
2016/04/10 18:34:14 Send loop terminated: err=<nil>
2016/04/10 18:34:14 Connected to 192.168.0.3:2181
2016/04/10 18:34:14 Authentication failed: EOF
2016/04/10 18:34:14 Connected to 192.168.0.1:2181
2016/04/10 18:34:14 Authentication failed: EOF
2016/04/10 18:34:16 Failed to connect to 192.168.0.2:2181: dial tcp 192.168.0.2:2181: i/o timeout
2016/04/10 18:34:16 Connected to 192.168.0.3:2181
2016/04/10 18:34:16 Authenticated: id=167757829242421255, timeout=4000
2016/04/10 18:34:16 ### I am Leader !! ###
2016/04/10 18:34:16 ### I am Leader !! ###
2016/04/10 18:34:16 ### I am Leader !! ###
2016/04/10 18:34:17 ### I am Leader !! ###
2016/04/10 18:34:18 ### I am Leader !! ###
2016/04/10 18:34:19 ### I am Leader !! ###
2016/04/10 18:34:20 ### I am Leader !! ###
2016/04/10 18:34:21 ### I am Leader !! ###
2016/04/10 18:34:22 ### I am Leader !! ###
2016/04/10 18:34:23 ### I am Leader !! ###
```

- zookeeper-3上で、新Leaderとして、選定されたことが確認できる

```
$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.8/bin/../conf/zoo.cfg
Mode: leader
```

<b>以上より、ZooKeeperレピリケーションモード動作により、zooKeeperのSPOF問題が解決できたことを確認しました。</b>

![sample_zookeeper3.jpeg](https://qiita-image-store.s3.amazonaws.com/0/103595/fe50d78d-5364-fab2-616a-ad4dac4638fa.jpeg)


# ⬛︎ 終わりに
Big data用途の各種ミドルウェアでのHA動作として、ZooKeeperレプリケーションモード動作が活用されております。結構、縁の下の力もち的な存在だったりしますが、その基本動作を実際に動かしてみて期待通り動作することが確認できました。