正月休みだし1人ハッカソンやろうかなぁと思って、Serf を触ってみたのでそのメモ。 触ってみた結果大分勘違いしていたことがわかったので、すごい収穫あった。

触ってみた動機

自分の観測範囲では一番最初に Serf の記事を書いていたのは @glidenote さんの Serf+HAProxyで作るAutomatic Load Balancer のブログエントリだと思うのだけど、 これを使うと最近流行りの Immutable Infrastructure で言うところの Orchestration 作業を自動化できるんじゃないかなぁ、オラワクワクしてきたぞ、と思ってたので触ってみた。ワクワクしてから大分月日経ってしまったけど。

で、@kentaro さんが serf-hosts というのを書いていて、Serf を使って /etc/hosts を自動ほげほげとか言っていたので、キタコレ!と思ったりしていたので、こちらも今回触ってみた。

ちなみに念のため言及しておくと、ここでは「Orchestration 作業」という単語は、サーバを chef とか puppet でセットアップ(ここがプロビジョニング)した後に、ロードバランサに追加したり、監視入れたり、サーバ管理ツールに登録したり、という他のツールと連携するための作業などを指して使っている。サーバ単体で完結できない作業というか。

Serf とは

Vagrant で有名な Mitchell Hashimoto 氏が書いた go 製のツール。github の README をてきとうに要約するとこんなかんじ。

Serf はサービスディスカバリと Orchestration に使える非集中的なソリューションです。軽くて、高可用性があって、障害に強いです。

Serf は他のノードの故障を見つけると、gossip protocol を使ってクラスタの他のノードに通知します。Serf の gossip protocol はデプロイ、設定変更といったイベントを伝搬してくれます。Serf は完全に masterless で、単一故障点がありません。

以下は Serf のユースケース一例です:

  • ウェブサーバをみつけ自動でロードバランサに登録する
  • twemproxy やアプリで独自管理する代わりに、memcached や redis ノードのクラスタを管理する
  • デプロイをトリガーにアクションを起こす
  • 設定の変更を関連するノードに伝搬する
  • ノードが追加されたら DNS に登録し、ノードが故障したら取りのぞく
  • などなど

Serf のインストール

go の単体アプリなので、zip ファイルを落としてきて unzip して適当な所に置くだけ。zip ファイルの中は serf コマンド1個しかおいてなかった。

自分の linux x86_64 環境ではこんなかんじ

wget https://dl.bintray.com/mitchellh/serf/0.3.0_linux_amd64.zip -O serf_0.3.0_linux_amd64.zip
unzip serf_0.3.0_linux_amd64.zip
sudo mv serf /usr/local/bin

Serf を試す(1) - クラスタへの登録

Getting Started をそのままやってみた。

serf を起動してみる

$ serf agent -node=agent-one -bind=127.0.0.1:7946 # -rpc-addr=127.0.0.1:7373

もう1個別の画面で起動してみる

$ serf agent -node=agent-two -bind=127.0.0.1:7947 -rpc-addr=127.0.0.1:7374

なお、1台で試してみる前提になっているので、-node、-bind、-rpc-addr オプションを指定してノード名やポートが被らないようにしているが、実際は1台1serf しか起動しないのでその辺は不要になる。

ちなみに -rpc-addr は serf members コマンドとかでやりとりする際の rpc 用のポートで -bind は gossip protocoal でやりとりする用のポートっぽい

つぎに serf クラスタのメンバを確認してみる

$ serf members # -rpc-addr=127.0.0.1:7373
agent-one    127.0.0.1:7946    alive

$ serf members -rpc-addr=127.0.0.1:7374
agent-two    127.0.0.1:7947    alive

まだ serf join していないのでそれぞれ自分しかいない。serf join してみる

serf join 127.0.0.1:7946

メンバを確認してみる

$ serf members
agent-one    127.0.0.1:7946    alive
agent-two    127.0.0.1:7947    alive

$ serf members -rpc-addr=127.0.0.1:7374
agent-two    127.0.0.1:7947    alive
agent-one    127.0.0.1:4946    alive

お、入ってる。

Serf を試す(2) - イベントハンドラ

で、Serf クラスタにメンバが join した、といったイベントをトリガーにスクリプトを走らせることができるらしい。 イベントの種類は環境変数で渡るので、シェルスクリプトでも perl スクリプトでも ruby スクリプトでもなにで書いても良いようにできているっぽい。

以下のような hanlder.sh という名前のファイルを用意して、chmod a+x しておく。

#!/bin/bash

echo
echo "New event: ${SERF_EVENT}. Data follows..."
while read line; do
    printf "${line}\n"
done

Serf 起動時に -event-handler オプションでスクリプトを指定する。これで Serf のイベントが起こるとスクリプトが実行される。

serf agent -log-level=debug -event-handler=$HOME/handler.sh

環境変数 SERF_EVENT にイベント毎に違う値(member-joinmember-leavemember-failed, or user)が入ってくるので、 値を見てスクリプトの処理を分岐させることができる。

user イベントというのは、serf event コマンドで送るイベントで、好きな名前でイベントを送れるものらしい。

serf event deploy

これで user:deploy イベントを送信している。SERF_EVENT 環境変数に user が、SERF_USER_EVENT 環境変数に deploy という値が入る。

serf で /etc/hosts を更新してみる

Getting Started は終わったので、ちょっと応用ということで、@kentaro さんの serf-hosts を覗いてみた。

このレポジトリにおいてあるのは、perl スクリプト1枚だけで、これを -event-handler オプションに指定して使うっぽい。

この perl スクリプト内で、

  1. member-join イベントで /etc/hosts に join してきたホストのIPアドレスとホスト名を追加
  2. member-leave, member-failed イベントで /etc/hosts から該当行を削除

の処理をしている模様。member-join イベントを受け取ると STDIN に

ホスト名 IPアドレス

という文字列が入力されるのでそれを利用して /etc/hosts に追加している。

このスクリプトを全ホストに配布して、全 serf agent の -event-handler オプションに指定して起動するかんじ。

やっと仕組みわかった。

serf で LB に登録してみる

追記。一緒に @glidenote さんの Serf+HAProxyで作るAutomatic Load Balancer の記事を改めて覗いてみた。

こちらでは、LB(HAProxy) と web app サーバ群で serf クラスタを組んで、LBの serf agent にだけ LB 更新用の event handler スクリプトを指定しておき、web app が member-join してきたら LB 設定ファイルに追加して再起動、member-leave してきたら LB設定ファイルから削除して再起動、ということをやっている模様。

やっと仕組みわかった。 

アイデア・疑問ノート

あとは、疑問におもったことなどのメモをツラツラ並べておこうとおもう。ご存知の方いれば是非。

1. event handler スクリプトは1つしか指定できない?

ドキュメントをみても、スクリプトを1つしか指定できないように見えるので、イベント発火時に 1. 監視登録、2. /etc/hosts 登録、3. ロードバランサ登録 のように複数やりたいことがある場合でも、まとめた1つのスクリプトを用意しないといけない模様。ちょっとめんどくさい。

追記: ブクコメで @kazuph さんに、serf agent に設定ファイルを渡すことができて、設定ファイルでは event handler スクリプトを複数指定できることを教えて頂きました。@kazuph++

2. 「設定の変更を関連するノードに伝搬する」というのをどうやるか?

README にそんなかんじのことが書いてあったので、Serf は、「どこかのホストで設定を変更するとそれをクラスタに伝搬してくれるもの」なのかと盛大に勘違いしていたけれど、 Serf 自体にそういう機能があるわけではなかった。

ので、そういうのをやりたければ serf の上でまた仕組みを用意しなければならない。

たとえば github エンタープライズに設定ファイルをつっこんでおいて、serf event でどこかのノードに event を送って伝搬してもらい、各ノードで git pull して反映するような event handler スクリプトを実行させる、とか?

追記:でもノードの増減に依存しない設定更新はプロビジョニングの範疇なので従来通り chef や puppet でやれば良い気がしてきた。Orchestration ツールである Serf で無理にやる必要はないかな。 

3. イベントをどのノードに発火すべきか

gossip protocol でイベントが伝搬されるにしても、まず最初にどれかのノードに event を発火しなければならない。

とはいえ、そのノードを固定にしてしまうと、masterless の良さが損なわれてしまうので、DNSラウンドロビンを使うとかして、ランダムに選択されるようにするとよさそう。

4. どれぐらいの時間でクラスタ全体にイベントが伝搬されるのか?

Serf Convergence Simulator でシミュレーションできるらしい。すごい。

ノード数が増えればもちろん 100% に達するまでの時間は遅くなるけど、2 sec とかそういうレベルのようですね。すごい。

5. 監視をはずすのはどのタイミングでやるべきか?

member-leave, member-failed イベントでホストが故障したことなどはわかるが、そこで監視をはずしたら故障アラートが飛んでこないことになるので、そのタイミングでは監視をはずせない。

やはりそこの Orchestration 作業は手動になるのかな。

6. event handler スクリプトの更新をどうやるか

event handler スクリプトにバグがあるなどして更新する必要があった場合に、serf クラスタ全体に撒いて回らないといけない。

それこそ serf を使ってクラスタ全体を更新したいんだけど、更新するためのスクリプトがバグっているわけなので、そう考えると今までどおり capistrano なんかで撒き直すしかないかな。

(追記)7. graceful restart はできるのか?

設定ファイルに複数の handler スクリプトを登録しておけることはわかったけれど、serf agent にやらせたいことが増えた時に、設定ファイルを書き直して、serf agent をrestart して回る必要がある。そのときに、member-leave、member-join イベントが全台で発生するとひどいことになるので、設定ファイルを読み込み直すだけの graceful restart ができないのか聞いてみた

どうやら SIGHUP でできるらしい :D 

Serf also supports reloading of configuration when it receives the SIGHUP signal. 

まとめ

Serf を試してみた。mizzy さんが インフラ系技術の流れ の記事で

とはいえ、Serf 自体はゴシップベースプロトコルによるクラスタリングとイベントプロパゲーションが主な仕事で、その上で Orchestration するような仕組みを自分で作らないといけなさそう、今のところは。

と言っていた意味がやっとわかった。確かに Serf 自体はイベントプロパゲーションしかしていなかった。

とはいえ、イベントプロパゲーションを使って Orchestration するというのはパラダイムシフトだと思うので、どういう風に使うとハマるのか考えていきたい。

最後に @glidenote さんの記事から引用

従来のサーバ増やした(減らした) => 設定ファイル修正commit、push、deploy みたいな作業は今後無くなっていくんだろうな-。 

ホントそう思うし、そうしていきたい。以上、@sonotsでした。