この秋に覚えたい。オーケストレーションツールSerfの使い方
仮想化やクラウドに注目が集まってくると、サーバの管理というのはどんどん大変になっていきます。かつてのようにラックに配置して一台という訳ではないので、コマンド一つでサーバができあがってしまいます。どんどんできて、どんどん終了するサーバ群を適切に管理、運用するというのは考えただけでも大変でしょう。
そこで注目を集めているのがオーケストレーションツールです。サーバ間が相互にメッセージを飛ばし合うことでその生死、ステータスを確認し、必要に応じてスケーリングやアラートなどを出します。
今回はSerfをさくらのクラウド上でトライします。
必要なもの
さくらのクラウドのアカウント
サーバの設置
今回は全部で4台構成になっています。1台はインターネットにつながったWebサーバ、バックエンドに3台のサーバという構成です。
なお、さくらのクラウドを使って複数台構成を行う設定は(スイッチやルータを追加する)は当ブログのサーバを拡張してみようやルータ+スイッチを使ったネットワークを構築してみようを参考にしてください。Webブラウザからスイッチやルータを作成したり、サーバのNICを追加したりするのは面白いですよ。
Serfのインストール
Serfはオープンソース・ソフトウェアですが、バイナリも配布されています。対応プラットフォームは、
- Mac OSX
- Linux
- Windows
- FreeBSD
- OpenBSD
になります。今回は全てのサーバをCentOS 6.5で立ち上げています。まず、各サーバでserfコマンドをインストールします。執筆時点(2014年08月現在)の最新版は0.6.3でした。
$ wget https://dl.bintray.com/mitchellh/serf/0.6.3_linux_amd64.zip -O 0.6.3_linux_amd64.zip $ unzip 0.6.3_linux_amd64.zip $ sudo mv serf /usr/bin/
これで完了です。インストールを確認する場合はversionサブコマンドを使います。
# serf version Serf v0.6.3 Agent Protocol: 4 (Understands back to: 2)
同じ時期にインストールすれば全て同じプロトコルになっているはずです。なお、他に使えるサブコマンドは以下の通りです。
# serf usage: serf [--version] [--help] <command> [<args>] Available commands are: agent Runs a Serf agent event Send a custom event through the Serf cluster force-leave Forces a member of the cluster to enter the "left" state info Provides debugging information for operators join Tell Serf agent to join cluster keygen Generates a new encryption key keys Manipulate the internal encryption keyring used by Serf leave Gracefully leaves the Serf cluster and shuts down members Lists the members of a Serf cluster monitor Stream logs from a Serf agent query Send a query to the Serf cluster reachability Test network reachability tags Modify tags of a running Serf agent version Prints the Serf version
エージェントの立ち上げ
次にエージェントを立ち上げます。エージェントは各サーバでメッセージを送受信するサービスになりますので、全てのサーバで実行する必要があります。
# serf agent & [1] 1525 [root@server1 ~]# ==> Starting Serf agent... ==> Starting Serf agent RPC... ==> Serf agent running! Node name: 'server1' Bind addr: '0.0.0.0:7946' RPC addr: '127.0.0.1:7373' Encrypted: false Snapshot: false Profile: lan ==> Log data will now stream in as it occurs: 2014/08/28 16:07:34 [INFO] agent: Serf agent starting 2014/08/28 16:07:34 [INFO] serf: EventMemberJoin: localhost 192.168.0.2 2014/08/28 16:07:35 [INFO] agent: Received event: member-join
このようにバックグラウンドで立ち上げます。なお、この時サーバはLAN内部のIPアドレスを持っている必要があります。グローバルだけではエラーになってしまいました。
全てのサーバで立ち上げたら、次に一つのノードに対して参加します。
# serf join 192.168.0.2 2014/08/28 16:20:31 [INFO] agent.ipc: Accepted client: 127.0.0.1:48717 2014/08/28 16:20:31 [INFO] agent: joining: [192.168.0.2] replay: false 2014/08/28 16:20:31 [INFO] serf: EventMemberJoin: server2 192.168.0.3 2014/08/28 16:20:31 [INFO] serf: EventMemberJoin: server1 192.168.0.2 2014/08/28 16:20:31 [INFO] agent: joined: 1 nodes Successfully joined cluster by contacting 1 nodes.
これを接続先になるノード以外全てのサーバで実行します。これが終わるとmembersサブコマンドで状況が確認できるようになります。
# serf members 2014/08/28 16:20:38 [INFO] agent.ipc: Accepted client: 127.0.0.1:50302 server1 192.168.0.2:7946 alive server2 192.168.0.3:7946 alive server3 192.168.0.4:7946 alive
イベントを実行する
各サーバがつながった所で、Serfを使ってみましょう。まずeventを使ってみます。eventはレスポンスを期待しない実行です。
# serf event test 2014/08/28 16:22:37 [INFO] agent.ipc: Accepted client: 127.0.0.1:46974 Event 'test' dispatched! Coalescing enabled: true 2014/08/28 16:22:38 [INFO] agent: Received event: user-event: test
eventを実行すると、各ノードに対してtestというuser-eventが呼び出されます。
結果を受け取るクエリー
次にコマンドを実行して、その結果を受け取ってみます。例えばこんな感じにエージェントを立ち上げます。
# serf agent -event-handler=query:check=time &
これはquery(結果を受け取る)時にcheckというクエリ名であれば、timeコマンドを実行するという意味になります。
この状態で別なノードから実行してみます。
# serf query check 2014/08/28 16:30:36 [INFO] agent.ipc: Accepted client: 127.0.0.1:50326 2014/08/28 16:30:36 [INFO] agent: Received event: query: check Query 'check' dispatched Ack from 'server1' Ack from 'server3' Ack from 'server2' real 0m0.000s user 0m0.000s sys 0m0.000s Total Acks: 3 Total Responses: 1
このようになります。server2からはtimeコマンドの結果が返ってきます。イベントやクエリ、その種類を判断するのは全て環境変数に入ってきます。環境変数は次の種類があります。
- SERF_EVENT
- イベント名です。member-join/member-leave/member-failed/member-update/member-reap/user/queryがあります。
- SERF_SELF_NAME
- 自分のノード名が入ります。
- SERF_SELF_ROLE
- ノードの権限が入ります。
- SERF_TAG_${TAG}
- エージェントが設定しているタグ名が入ります。
- SERF_USER_EVENT
- イベント名が入ります。これはSERF_EVENTがuserの場合のみです。
- SERF_USER_LTIME
- ランポートタイムが入ります。これもSERF_EVENTがuserの場合のみです。
- SERF_QUERY_NAME
- クエリ名が入ります。これはSERF_EVENTがqueryの場合のみです。
- SERF_QUERY_LTIME
- ランポートタイムが入ります。これもSERF_EVENTがqueryの場合のみです。
例えば下のようなシェルスクリプトを作成します。
# cat test.sh #!/bin/sh echo echo ${SERF_EVENT} echo ${SERF_SELF_NAME} echo ${SERF_QUERY_NAME} echo
そしてエージェントの立ち上げ時にこれを指定します。
# serf agent -event-handler=query="/bin/sh /tmp/test.sh" &
この状態でクエリイベントを実行すると、次のような結果が返ってきます。
# serf query any 2014/08/28 16:43:12 [INFO] agent.ipc: Accepted client: 127.0.0.1:50356 2014/08/28 16:43:12 [INFO] agent: Received event: query: any Query 'check' dispatched Ack from 'server1' Ack from 'server3' Ack from 'server2' Response from 'server2': query server2 any Total Acks: 3 Total Responses: 1
これで分かるのはイベントハンドラは環境変数を通じて任意のプログラムが呼び出せるということです。つまりPerlでもRubyでもJavaでも任意のプログラムを呼び出し、環境変数によって処理分けすれば良いということになります。
ノードが停止した場合
サーバが停止するなど、ノードの反応がなくなると他のノードに対して通知がいきます。以下は一度ノードが停止し、再度ジョインした場合のログです。
2014/08/28 16:24:03 [INFO] memberlist: Suspect server2 has failed, no acks received 2014/08/28 16:24:07 [INFO] memberlist: Marking server2 as failed, suspect timeout reached 2014/08/28 16:24:07 [INFO] serf: EventMemberFailed: server2 192.168.0.3 2014/08/28 16:24:08 [INFO] serf: EventMemberJoin: server2 192.168.0.3
これもSERF_EVENTによって判断できますので、サーバが停止したり、負荷が高まっているようであれば他のノードからシステム管理者へメールを出す仕組みも簡単に作れそうです。
通信を暗号化する
Serf同士の間は基本的に平文でやり取りされます。これを暗号化することができます。まずはキーを生成します。
# serf keygen MiobJ41xNr8Ulk2SnFX32w==
このキーを使ってエージェントを立ち上げます。
# serf agent -encrypt=MiobJ41xNr8Ulk2SnFX32w== ==> Starting Serf agent... ==> Starting Serf agent RPC... ==> Serf agent running! Node name: 'server1' Bind addr: '0.0.0.0:7946' RPC addr: '127.0.0.1:7373' Encrypted: true // <- 暗号化がtrueになっています。 Snapshot: false Profile: lan ==> Log data will now stream in as it occurs: 2014/08/28 17:34:50 [INFO] agent: Serf agent starting 2014/08/28 17:34:50 [INFO] serf: EventMemberJoin: server1 192.168.0.2 2014/08/28 17:34:51 [INFO] agent: Received event: member-join
もちろんこれは全てのエージェントで行う必要があります。キーの状態は以下のコマンドで確認できます。
# serf keys -list 2014/08/28 17:37:23 [INFO] agent.ipc: Accepted client: 127.0.0.1:50472 ==> Asking all members for installed keys... 2014/08/28 17:37:23 [INFO] agent: Initiating key listing 2014/08/28 17:37:23 [INFO] serf: Received list-keys query ==> Keys gathered, listing cluster keys... MiobJ41xNr8Ulk2SnFX32w== [3/3]
いかがでしたか。Serfの良い点はとにかくネットワークを作るのが簡単ということです。エージェントを立ち上げて、ジョインすれば良いだけです。後はイベントとクエリ、またはRPCを使ってメッセージを送受信できます。さらに任意のプログラムを実行できるので好きなスクリプト言語などを使って組み上げられるのが便利です。
とは言えSerfはメッセージの送受信を行う仕組みであって、それ自体がサーバを立ち上げたりステータスを管理したりする訳ではありません。その辺りは自作が必要です。さくらのクラウドではAPIを備えていますし、Rubyやnodeライブラリを使えばオートスケールさせる仕組みも作れそうです。