Dockerで試す、はじめてのSerf

  • 79
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

最近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の実用例を挙げておきます。

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