この秋に覚えたい。オーケストレーションツールSerfの使い方

仮想化やクラウドに注目が集まってくると、サーバの管理というのはどんどん大変になっていきます。かつてのようにラックに配置して一台という訳ではないので、コマンド一つでサーバができあがってしまいます。どんどんできて、どんどん終了するサーバ群を適切に管理、運用するというのは考えただけでも大変でしょう。

そこで注目を集めているのがオーケストレーションツールです。サーバ間が相互にメッセージを飛ばし合うことでその生死、ステータスを確認し、必要に応じてスケーリングやアラートなどを出します。

今回はSerfをさくらのクラウド上でトライします。

Serf / SerfのGitHubリポジトリ

Serfの公式サイト

必要なもの

さくらのクラウドのアカウント

サーバの設置

サーバの配置

今回は全部で4台構成になっています。1台はインターネットにつながったWebサーバ、バックエンドに3台のサーバという構成です。

なお、さくらのクラウドを使って複数台構成を行う設定は(スイッチやルータを追加する)は当ブログのサーバを拡張してみようルータ+スイッチを使ったネットワークを構築してみようを参考にしてください。Webブラウザからスイッチやルータを作成したり、サーバのNICを追加したりするのは面白いですよ。

Serfのインストール

Serfはオープンソース・ソフトウェアですが、バイナリも配布されています。対応プラットフォームは、

  • Mac OSX
  • Linux
  • Windows
  • FreeBSD
  • OpenBSD

になります。今回は全てのサーバをCentOS 6.5で立ち上げています。まず、各サーバでserfコマンドをインストールします。執筆時点(2014年08月現在)の最新版は0.6.3でした。

Downloads - Serf

$ 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を備えていますし、Rubynodeライブラリを使えばオートスケールさせる仕組みも作れそうです。

Serf

おしらせ