Posted at

Dockerで試す、はじめてのSerf

More than 5 years have passed since last update.


はじめに

最近Serfに関する話題が盛り上がってきていて、Serfを紹介した資料がブクマ数をそれなりに稼いでいます。

そこで本エントリーでは、


  • Serfって何?

  • Serfってどうやって使うの?

  • Serfって何が出来るの?

などと思っている方を対象として、Serfの使い方を紹介したいと思います。


Serfとは

Serf ( http://www.serfdom.io/ )とは、サーバプロビジョニングにおける、オーケストレーションレイヤーで利用するためのツールです。

オーケストレーションレイヤーでは


  • サーバを冗長化させたり

  • 障害を検知したらサーバを抜き差ししたり

などの操作を行います。

Serfはクラスタ内のサーバの状態を監視し、これらの操作を発火させるためのツールです。

発火のタイミングは


  • クラスタに新たなサーバが加わる

  • クラスタ内のサーバがダウンする

など様々です(詳しくは後で説明します)。

なおプロビジョニングについては下記資料が参考になります。


Serfの実行環境を整える

Serfを検証するためには複数台のマシンが必要になります。

最低2台あれば検証は可能ですが、10数台あったほうが都合がよく、それらを効率よく用意するにはDockerが最適です。

また、Dockerを動かす環境はVagrantBoxで公開( https://vagrantcloud.com/3scale/docker )されているので、そのBoxを利用してサクッと用意します(Dockerが動けばどんな環境でも大丈夫です)。


Dockerの動作環境の整備

Vagrantfileを用意したので git clone して vagrant up するだけで整います。

$ git clone git@github.com:foostan/vagrant-docker.git

$ cd vagrant-docker
$ vagrant up

ただ、今回利用したBox 3scale/docker のダウンロードに結構時間がかかるので、手持ちのBoxを利用してhttps://www.docker.io/gettingstarted/ を参考にしてDockerをインストールした方が早いかもしれません。


Serfイメージの作成

DockerでSerf用のイメージを作成します。

Serf用のDockerfileを用意したので下記のように実行するだけでコンテナが出来上がります(コンテナの名前は適宜設定してください)。

$ vagrant ssh

vagrant@ubuntu-12:~$ cd /vagrant/serf
vagrant@ubuntu-12:/vagrant/serf$ docker build -t foostan/serf .


Serfコンテナの起動

コンテナの起動時にSerfを起動するようにしてもいいのですが、Serfの検証をしたいので、今回はあえて /bin/bash でコンソールに入ります(DockerfileでもあえてENTRYPOINTを設定していない)。

vagrant@ubuntu-12:/vagrant/serf$ docker run -i -t foostan/serf /bin/bash


Serfの使い方


準備

コンテナを起動し、コンソールに入るとSerfコマンドが使える状態になっていると思います。

root@node1:/# serf

usage: app [--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
join Tell Serf agent to join cluster
keygen Generates a new encryption key
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を利用して検証したいため、新しいウインドウでもう一台Serfのコンテナを立ち上げてコンソールに入って下さい。

$ vagrant ssh

vagrant@ubuntu-12:~$ docker run -i -t foostan/serf /bin/bash
root@node2:/#

ここではわかりやすいように、

- root@node1

- root@node2

と表記してあります。


serf agent でクラスタを形成する

node1およびnode2で

serf agent -discover=cluster1 &

を実行して下さい。

以前は1台目を serf agent で起動し、2台目以降は起動中のマシンに対して serf join -join 接続先のIPアドレス でクラスタを形成するようになっていましたが(SPOFとなっていたが)、0.4.5 から -discover オプションを利用することで2台目以降も同じコマンドでクラスタを形成できるようになりました。

ちなみに、serf agent コマンドはフォアグラウンドで実行されてコンソールが奪われちゃうので & をつけてあります。

以下、実行結果です。

root@node1:/# serf agent -discover=cluster1 &

[1] 77
==> Starting Serf agent...
==> Starting Serf agent RPC...
==> Serf agent running!
Node name: 'node1'
Bind addr: '0.0.0.0:7946'
RPC addr: '127.0.0.1:7373'
Encrypted: false
Snapshot: false
Profile: lan
mDNS cluster: cluster1

==> Log data will now stream in as it occurs:

2014/04/07 16:52:43 [INFO] agent: Serf agent starting
2014/04/07 16:52:43 [INFO] serf: EventMemberJoin: node1 172.17.0.2
2014/04/07 16:52:43 [INFO] agent: joining: [172.17.0.2:7946] replay: false
2014/04/07 16:52:43 [INFO] agent: joined: 1 Err: <nil>
2014/04/07 16:52:43 [INFO] agent.mdns: Joined 1 hosts
2014/04/07 16:52:44 [INFO] agent: Received event: member-join

root@node2:/# serf agent -discover=cluster1 &

[1] 17
==> Starting Serf agent...
==> Starting Serf agent RPC...
==> Serf agent running!
Node name: 'node2'
Bind addr: '0.0.0.0:7946'
RPC addr: '127.0.0.1:7373'
Encrypted: false
Snapshot: false
Profile: lan
mDNS cluster: cluster1

==> Log data will now stream in as it occurs:

2014/04/07 16:54:10 [INFO] agent: Serf agent starting
2014/04/07 16:54:10 [INFO] serf: EventMemberJoin: node2 172.17.0.3
2014/04/07 16:54:11 [INFO] agent: joining: [172.17.0.2:7946 172.17.0.3:7946] replay: false
2014/04/07 16:54:11 [INFO] serf: EventMemberJoin: node2 172.17.0.2
2014/04/07 16:54:11 [INFO] agent: joined: 2 Err: <nil>
2014/04/07 16:54:11 [INFO] agent.mdns: Joined 2 hosts
2014/04/07 16:54:12 [INFO] agent: Received event: member-join

serf members で確認してみます。

root@node1:/# serf members

2014/04/07 17:06:24 [INFO] agent.ipc: Accepted client: 127.0.0.1:42530
node1 172.17.0.2:7946 alive
node2 172.17.0.3:7946 alive

無事にクラスタが形成されたことが確認できました。


serf leave でクラスタから抜ける

serf learve コマンドで所属しているクラスタから抜けます。

root@node1:/# serf leave

2014/04/07 17:10:30 [INFO] agent.ipc: Accepted client: 127.0.0.1:42549
2014/04/07 17:10:30 [INFO] agent.ipc: Graceful leave triggered
2014/04/07 17:10:30 [INFO] agent: requesting graceful leave from Serf
2014/04/07 17:10:31 [INFO] serf: EventMemberLeave: node1 172.17.0.2
Graceful leave complete
2014/04/07 17:10:32 [INFO] agent: requesting serf shutdown
2014/04/07 17:10:32 [INFO] agent: shutdown complete

node2側で serf members でnode1が抜けたことを確認してみます。

root@node2:/# serf members

2014/04/07 17:11:10 [INFO] agent.ipc: Accepted client: 127.0.0.1:42550
node2 172.17.0.3:7946 alive
node1 172.17.0.2:7946 left


イベントハンドラについて

Serfでは以下のイベントハンドラが用意されています。


  • member-join: メンバーが加わったとき

  • member-leave: メンバーが抜けたとき

  • member-failed: メンバーを見失ったとき(疎通確認に失敗たとき)

  • member-update: メンバーの情報が更新されたとき

  • member-reap: メンバーをリストから削除したとき(member-leave またはmember-failed から一定時間経過したとき)

  • user: 任意のユーザイベントが発行されたとき

  • query: 任意のクエリが発行されたとき

member-joinmember-leave については前の節で既に出てきていて、

2014/04/07 17:10:32 [INFO] agent: Received event: member-leave

2014/04/07 16:55:12 [INFO] agent: Received event: member-join

が、イベントハンドラが呼ばれたタイミングとなります。


イベントハンドラで任意のスクリプトを実行する

Serfでは下記のようにスクリプトを指定することで、イベントハンドラの発火時にスクリプトが実行されるようになります。

※ イベントハンドラの標準出力は、-log-level=debug でないと表示されないため、-log-level を指定しています。

serf agent -discover=cluster1  -log-level=debug -event-handler=./handle.sh

handle.shの内容は以下のとおりです。

#!/bin/bash

echo
echo
"SERF_EVENT: ${SERF_EVENT}"
echo "SERF_SELF_NAME: ${SERF_SELF_NAME}"
echo "SERF_SELF_ROLE: ${SERF_SELF_ROLE}"
echo "SERF_SELF_TAG: ${SERF_SELF_TAG}"
echo "SERF_TAG_ROLE: ${SERF_TAG_ROLE}"
echo "SERF_USER_EVENT: ${SERF_USER_EVENT}"
echo "SERF_USER_LTIME: ${SERF_USER_LTIME}"
echo "SERF_QUERY_NAME: ${SERF_QUERY_NAME}"
echo "SERF_QUERY_LTIME: ${SERF_QUERY_LTIME}"
while read line; do
echo "event data: ${line}"
done

イベントハンドラによって呼ばれるスクリプト内では、上記のような環境変数が使用できます。

環境変数の詳細については http://www.serfdom.io/docs/agent/event-handlers.html を参照してください。

上記スクリプトがイベントハンドラに呼ばれると下記のように実行されます。

member-join イベント

--- 省略 ---

2014/04/08 15:05:53 [INFO] agent: Received event: member-join
2014/04/08 15:05:53 [DEBUG] agent: Event 'member-join' script output:
SERF_EVENT: member-join
SERF_SELF_NAME: node1
SERF_SELF_ROLE:
SERF_SELF_TAG:
SERF_TAG_ROLE:
SERF_USER_EVENT:
SERF_USER_LTIME:
SERF_QUERY_NAME:
SERF_QUERY_LTIME:
event data: node2 172.17.0.3

member-leave イベント

--- 省略 ---

2014/04/08 15:05:59 [INFO] serf: EventMemberLeave: node2 172.17.0.3
2014/04/08 15:06:00 [INFO] agent: Received event: member-leave
2014/04/08 15:06:00 [DEBUG] agent: Event 'member-leave' script output:
SERF_EVENT: member-leave
SERF_SELF_NAME: node1
SERF_SELF_ROLE:
SERF_SELF_TAG:
SERF_TAG_ROLE:
SERF_USER_EVENT:
SERF_USER_LTIME:
SERF_QUERY_NAME:
SERF_QUERY_LTIME:
event data: node2 172.17.0.3

SERF_EVENTやSERF_SELF_NAMEが取得できていることがわかります。

さらに、標準入力としてホスト名とIPアドレスが入力されることが確認できます(event data: として表示)。


イベントハンドラ毎に別々のスクリプトを実行する

そろそろ引数が多くなってきたので、設定ファイルを記述したJsonを読み込ませてserfを実行してみます。

以下の様なjsonファイルが /serf.json としてDockerのコンテナ起動時に配置してあります。

{

"tags": {
"role": "web"
},
"discover": "app",

"encrypt_key": "cg8StVXbQJ0gPvMd9o7yrg==",
"log_level": "debug",

"event_handlers": [
"member-join=./member-join.sh",
"member-leave=./member-leave.sh",
"member-failed=./member-failed.sh",
"member-update=./member-update.sh",
"user:deploy=./deploy.sh",
"query:sh=sh"
]
}

この設定ファイルをみると、イベントハンドラ毎にスクリプトが指定されているのがわかります。

これで、イベントハンドラ毎に別々のスクリプトを実行することが可能となります。

そしてこのファイルからSerfを起動するには以下のように -config-file を指定してします。

root@node1:/# serf agent -config-file=serf.json

node2側も同様に

root@node2:/# serf agent -config-file=serf.json

と読み込ませてみます。

適当に、node1のSerf agentを停止してみたり、node2側で serf leave してみたりすると、それぞれのイベントハンドラに応じて指定したスクリプトが実行されることがわかります。

(今回用意した member-*.sh は単純にホスト名とIPアドレスを表示しているだけなので面白みはないけど)


user イベントと query イベント

Serfでは member-join など予め用意されたイベントとは別に任意のイベントを発生させることができます。

任意のイベントは user イベントと query イベントがあり、それぞれ以下のように実行します(見やすいように log_level=info にしてあります)。

user イベント

root@node2:/# serf event sh hostname

2014/04/08 17:15:42 [INFO] agent.ipc: Accepted client: 127.0.0.1:42907
Event 'sh' dispatched! Coalescing enabled: true
2014/04/08 17:15:43 [INFO] agent: Received event: user-event: sh

query イベント

root@node2:/# serf query sh hostname

2014/04/08 17:15:06 [INFO] agent.ipc: Accepted client: 127.0.0.1:42903
2014/04/08 17:15:06 [INFO] agent: Received event: query: sh
Query 'sh' dispatched
Ack from 'node2'
Response from 'node2': node2
Ack from 'node1'
Response from 'node1': node1
Total Acks: 2
Total Responses: 2

どちらもイベント発行時に指定されたスクリプトorコマンド(今回は/bin/sh)が実行されることに変わりはありません。

ただし user イベントでは、実行結果が取得出来ないのに対して、query イベントでは取得できます(Response from)。


さいごに

本エントリーではSerfの基本的な使い方についてDockerを用いて解説しました。

Serfの手軽さや柔軟性の高さを感じて頂けたでしょうか。

これをきっかけにSerfを使って”何か出来そう”、”何かやってみよう”などと興味を持って頂けると幸いです。

最後にSerfの実用例を挙げておきます。

工夫次第で様々なことに活用できそうでワクワクしますね。