失敗は一時の恥

パッケージソフト開発をしているプログラマが気の赴くままに何かを投稿するブログ.

ZooKeeper の動的再構成

ZooKeeper 3.5 の動的再構成

概要

Apache ZooKeeper 3.5 では,Dynamic Reconfiguration という機能が追加されています.

この機能を使うと,いままでは適用に再起動を要していた設定変更を,再起動なしで即時反映させることができるようになります.

この記事では,この Dynamic Recofiguration を試してみたいと思います.

ZooKeeper 3.5 を使用する際の注意点

ZooKeeper 3.5 で Dynamic Reconfiguration を試す前に,いくつか注意しておいたほうがいいことを挙げます.

3.5 系の最新バージョンは 3.5.3-beta

2018-04-07 時点で,ZooKeeper 3.5 の最新版は 3.5.3-beta です. (ちなみに 3.5.3-beta のリリースはおよそ一年前の 2017-04-17 です……)

ちなみに,Apache Curator という ZooKeeper のライブラリがあるのですが,そこの説明では「ZooKeeper の開発チームは 3.5 を "beta" としているが,実際には多くのユーザーがプロダクションで使用している」と言われています. 実際どうなのでしょう……?

設定の変更

ZooKeeper 3.5 では,アンサンブルを構成するサーバのアドレスを指定する方法が 3.4 から変わっています.

従来の clientPort というプロパティは非推奨となり,代わりにサーバーアドレス指定の一部に指定するなりました.

clientPort=1236 # このプロパティは廃止
server.5=125.23.63.23:1234:1235;1236 # セミコロンに続けてポートを指定する

加えて,従来のプロパティ clientPortAddress もサーバーアドレス指定の一部として移行されており,さらに participant OR observer の指定もあるため,指定方法の例として次の 5 通りの記述が紹介されています.

# どれも有効な指定方法
server.5=125.23.63.23:1234:1235;1236
server.5=125.23.63.23:1234:1235:participant;1236
server.5=125.23.63.23:1234:1235:observer;1236
server.5=125.23.63.23:1234:1235;125.23.63.24:1236
server.5=125.23.63.23:1234:1235:participant;125.23.63.23:1236

3.4 からのデータ移行

サービスを無停止でローリングアップデートする場合,3.4.6 以降を使用しているならばそのまま 3.5 に移行できます. そうでない場合は,一度 3.4.6 へアップデートする必要があります.

ただし,一度サービスを全停止するならば,3.4.6 を経ずに直接 3.5 へ移行できるようです.

Dynamic Recofiguration の設定内容

機能的な設定

Dynamic Reconfiguration を行うには,大きく次の 2 つのプロパティが関係してきます. (Administrator's Guide, Advanced Configuration を参照)

  • reconfigEnabled
  • standaloneEnabled

まず,Dynamic Reconfiguration を行うためにはプロパティに reconfigEnabled=true を指定してやる必要があります.

次に,standaloneEnabled=false を指定すると,ZooKeeper を 1 台構成から複数台構成へ拡張することや,逆に,複数台構成から 1 台構成へ縮退させることができます.

standaloneEnabled=true に設定した場合は,これらの操作はできません.つまり,ZooKeeper を起動する時点で複数台の構成を取っておくことが前提となります.

この standaloneEnabled については false の指定が望ましく,レガシーなスタンドアローンモードは将来的に非推奨になるとのことです.

セキュリティに関する設定

Dynamic Reconfiguration はセキュリティの観点から,ACL を適切に設定した上で,認証されたユーザーしか実行できないようになっています.

ただし,悪意ある者がアクセスできないような環境であることが保証されているならば,プロパティに skipACL=yes を指定しておくことで認証を不要とすることもできます. (Administrator's Guide, Encryption, Authentication, Authorization Options を参照.)

設定ファイルの分割

3.4 までの設定ファイルは zoo.cfg があるのみでしたが,3.5 では

  • 起動後に変更できないプロパティは今まで通り zoo.cfg
  • 起動後に変更できるプロパティは zoo.cfg.dynamic.{zxid}

というように分割されます.

起動時は今まで通りに zoo.cfg にすべての設定を書いておいても OK ですが, 起動すると ZooKeeper がプロパティファイルを書き換えて上記の 2 ファイルに分割されます.

zoo.cfg.dynamic.{zxid} のファイルは,基本的に自動生成されるものであって,人間が手を加えるべきものではありません. Dynamic Configuration を実行した際に ZooKeeper が取り扱い,その際の zxid がファイルの末尾に付加されます.

ファイルたちをコマンドで確認すると,例えば次のような構成になります:

$ ls /conf
zoo.cfg                    zoo.cfg.dynamic.100000000
log4j.properties           zoo.cfg.bak
$ cat /conf/zoo.cfg
initLimit=5
syncLimit=2
maxClientCnxns=60
tickTime=2000
dataDir=/data
dataLogDir=/datalog
dynamicConfigFile=/conf/zoo.cfg.dynamic.100000000
$ cat /conf/zoo.cfg.dynamic.100000000 
server.1=localhost:2888:3888:participant;0.0.0.0:2181

zoo.cfg.bak は,分割される前のファイルがバックアップとして残されているものです.

zoo.cfg の中で dynamicConfigFile=/conf/zoo.cfg.dynamic.100000000 というように,ダイナミックな方のファイルパスが記述されています.

Dynamic Reconfiguration の動作仕様

incremental モードと bulk モード

Dynamic Reconfiguratin の操作には incremental モードと bulk モードの 2 種類があります.

incremental モードでは,現在の状態に対して追加・削除を行います.

bulk モードでは,新たに適用させたい状態を指定して構成の変更をかけます.

サーバの除去

アンサンブル内のどのサーバも,除去することができます.リーダーも除去できますが,多少のサービスダウンが発生します.

除去されたサーバは直ちに停止するわけではなく,"non-voting follower" という状態になります. この状態では,データの読み書きはできるのですが,クオラムへの投票は行わないという動作になります.

non-voting follower の動作は observer に似ているのですが,内部の動作が observer よりもコストのかかるものとなっており,この状態で稼働し続けるのは好ましくありません. あくまでも,管理者からの停止や何らかの次の操作を受けるまでの一時的な状態です.

サーバの追加

サーバを追加する際は,これから追加されるサーバが既存のアンサンブルのリーダーに接続し同期が取れている必要があります.

Dynamic Reconfiguration を実行してみる

それでは Dynamic Reconfiguration を試してみます.

Docker イメージ snobutaka/zookeeper:3.5.3-beta で,ZooKeeper を起動します. このイメージは,公式の ZooKeeper イメージ では考慮されていないプロパティを追加でいくつか設定できるようにしたものです.

docker-compose を使って,ZooKeeper を 3 台立ち上げます. 次に示す docker-compose.yml では,

  • zoo1 と zoo2 がアンサンブルを構成する
  • zoo3 が上記のアンサンブルに接続する (参加はしていない)

という状況が作られます. 想定としては,2 台のアンサンブルを運用しているところに,新たに 3 台目を追加しようとしているといったシチュエーションです (3 台目も docker-compose で同時に立ち上がりはするのですが……).

version: '3.1'

services:
  zoo1:
    image: snobutaka/zookeeper:3.5.3-beta
    restart: always
    hostname: zoo1
    ports:
      - 2181:2181
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181
      ZOO_STANDALONE_ENABLED: 'false'
      ZOO_RECONFIG_ENABLED: 'true'
      ZOO_4LW_COMMANDS_WHITELIST: '*'
      ZOO_SKIP_ACL: 'yes'

  zoo2:
    image: snobutaka/zookeeper:3.5.3-beta
    restart: always
    hostname: zoo2
    ports:
      - 2182:2181
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181
      ZOO_STANDALONE_ENABLED: 'false'
      ZOO_RECONFIG_ENABLED: 'true'
      ZOO_4LW_COMMANDS_WHITELIST: '*'
      ZOO_SKIP_ACL: 'yes'

  zoo3:
    image: snobutaka/zookeeper:3.5.3-beta
    restart: always
    hostname: zoo3
    ports:
      - 2183:2181
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888:observer;2181
      ZOO_STANDALONE_ENABLED: 'false'
      ZOO_RECONFIG_ENABLED: 'true'
      ZOO_4LW_COMMANDS_WHITELIST: '*'
      ZOO_SKIP_ACL: 'yes'

これでコンテナを起動します.

$ docker-compose up -d

zoo1 または zoo2 に zkcli で接続して作業を行います.

$ docker-exec -it (zoo1 または zoo2 のコンテナ名) /bin/bash
$ /zookeeper-3.5.3-beta/bin/zkCli.sh

zkcli から config コマンドを実行すると,現在の構成が取得できます.

[zk: localhost:2181(CONNECTED) 0] config
server.1=zoo1:2888:3888:participant;0.0.0.0:2181
server.2=zoo2:2888:3888:participant;0.0.0.0:2181
version=100000000

まだ zoo3 は参加していないので,2 台分の情報が出力されます.

それでは,Dynamic Reconfiguration を実行しましょう!

まず,現在のアンサンブルに接続しているが参加はしていない zoo3 を,正式にアンサンブルに参加させます.

[zk: localhost:2181(CONNECTED) 1] reconfig -add 3=zoo3:2888:3888;2181
Committed new configuration:
server.1=zoo1:2888:3888:participant;0.0.0.0:2181
server.2=zoo2:2888:3888:participant;0.0.0.0:2181
server.3=zoo3:2888:3888:participant;0.0.0.0:2181
version=100000004

地味ですが zoo3 がアンサンブルに参加しました!

次に,サーバを除外してみましょう. 今度は 2 台を同時に始末します.

[zk: localhost:2181(CONNECTED) 4] reconfig -remove 2,3
Committed new configuration:
server.1=zoo1:2888:3888:participant;0.0.0.0:2181
version=100000005

zkcli から quit し元のシェルに戻り,ついでにコンテナも停止してしまいましょう.

$ docker stop (zoo2 のコンテナ名) (zoo3 のコンテナ名)

これで 3 台中 1 台のみが残っている状態なので,アンサンブルが 3 台の設定のままならばサービスが停止しているところです. しかし,Dynamic Reconfiguration を使ってアンサンブルを 1 台の設定にしておいたため,今でも zoo1 はサービスを継続できています!

Dynamic Reconfiguration でできないこと

別々に稼働してる 2 つのアンサンブルを統一することはできません. (注: ドキュメントを読んでいる限りできないようにしか思えませんでした.もしもできる方法があるのならご教示いただきたいです……)

先の実行例は,「zoo1 と zoo2 がアンサンブルを構成済みのところに,zoo3 が接続する」という状況でした.

そうではなく,「zoo1 と zoo2 がアンサンブルを構築済みで,zoo3 は単独のアンサンブルとして起動済みである.この zoo3 を zoo1, zoo2 のアンサンブルに追加する」ということはできるのでしょうか?

答えは,「できない」になります.

実際に zoo3 を単独で起動してから,zoo1, zoo2 に接続しようとしても次のようなエラーとなります.

$ reconfig -add 1=zoo1:2888:3888;2181,2=zoo2:2888:3888;2181
No quorum of new config is connected and up-to-date with the leader of last commmitted config - try invoking reconfiguration after new servers are connected and synced

Dynamic Reconfiguration でサーバを追加するときは,新たなサーバーが既存のアンサンブルのリーダーに接続した状態で行う必要があるのでした. zoo3 で上記の操作を行った場合,zoo3 からすれば「zoo1 と zoo2 が自分につながってきていない」ということになります.

逆に,zoo1, zoo2 の側で reconfig -add zoo3 をしても同じことになるので,別個に稼働してしまったアンサンブルは合流できないということです.

おわり

Dynamic Reconfiguration を簡単に紹介しましたが,説明しきれていない設定やコマンド,注意事項がまだまだあります.実運用などで使用する際は公式のドキュメントをよく確認する必要があります.

公式ドキュメントの中で何度も参照されているのですが,Dynamic Reconfiguration の詳細については論文を参照しろとされています. 深く正確に理解するにはこちらも読んだほうがよいのでしょう (僕もまだ読んでないです).