話題の記事

メッセージングツールSerfをEC2で使ってみる

2013.11.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、望月です。
今日はSerfというプロダクトを触ってみたので、学んだ内容をまとめたいと思います。

Serfとは?

Serfはゴシッププロトコルを利用したサーバ間のメッセージングツールです。固定のマスターを持たず、クラスタに参加しているノード間で通信して情報をやりとりするのが特徴です。
Serfの特徴として、公式ページには3つの特徴が挙げられています。

  • メンバーシップ : 複数のサーバでクラスタを組み、ノードが追加されたり減少したりした際にイベントを発行することができる
  • 不具合検知と修復 : クラスタ内のノードの生死を自動で判断し、不調のインスタンスが存在することを知らせるイベントを発行することができる
  • カスタムイベントの通知 : ユーザ独自のイベントを発行し、任意の処理を行わせることができる

とりあえず動かしてみる

Amazon Linuxを使って、まずはインストールと動作確認を行ってみます。
SerfではTCP/UDPの7946番ポートを利用しますので、あらかじめセキュリティグループで開放をしておく必要があります。

2013-11-04_1022

今回は2台のAmazon Linuxを使用します。それぞれHostnameをhost1とhost2に変更しました。
まずはSerfのインストールを行います。

[ec2-user@host1 ~]$ wget https://dl.bintray.com/mitchellh/serf/0.2.0_linux_amd64.zip
(出力省略...)
2013-11-02 06:56:15 (520 KB/s) - `0.2.0_linux_amd64.zip' へ保存完了 [2602286/2602286]
[ec2-user@host1 ~]$ unzip 0.2.0_linux_amd64.zip -d /usr/local/bin/
Archive:  0.2.0_linux_amd64.zip
  inflating: /usr/local/bin/serf
[ec2-user@host1 ~]$ which serf
/usr/local/bin/serf
[ec2-user@host1 ~]$ serf -v
Serf v0.2.0
Agent Protocol: 1 (Understands back to: 0)
[ec2-user@host1 ~]$

同様の手順で、host2の方にもSerfのインストールを行います。
両方のサーバーでインストールが完了したら、serf agentを両サーバで実行します。

[ec2-user@host1 ~]$ serf agent
==> Starting Serf agent...
==> Serf agent running!
    Node name: 'host1'
    Bind addr: '0.0.0.0:7946'
     RPC addr: '127.0.0.1:7373'
    Encrypted: false

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

2013/11/04 00:07:55 [INFO] Serf agent starting
2013/11/04 00:07:55 [INFO] serf: EventMemberJoin: host1 172.31.0.222
2013/11/04 00:07:55 [INFO] Serf agent started
2013/11/04 00:07:56 [INFO] agent: Received event: member-join

Serf agentが起動しました。このコマンドをhost2でも実行します。
この段階では、host1とhost2はお互いを認識できていない状態です。 次に、別ターミナルでhost1に接続し、host2のクラスタに入るserf joinコマンドを実行します。host2からhost1に対してコマンドを実行しても同じです。

# 172.31.0.222は、host2のプライベートIPアドレス
[ec2-user@host1 ~] $ serf join 172.31.0.222
Successfully joined cluster by contacting 1 nodes.

コマンドの実行が完了したら、Serfのログが流れているターミナルの表示を見てみると、クラスタに加わったことがわかると思います。

# Host1のターミナル
.....
2013/11/04 00:07:57 [INFO] Agent joining: [172.31.0.222] # Host2からの通知
2013/11/04 00:07:57 [INFO] Initiating push/pull sync with: 172.31.0.222:7946 
2013/11/04 00:07:57 [INFO] serf: EventMemberJoin: host1 172.31.0.222 # EventMemberJoinイベントの実行
2013/11/04 00:07:58 [INFO] agent: Received event: member-join

カスタムイベントの実行

Serfではクラスタ間でイベントを発行し、そのイベントに対応したスクリプトを実行することができます。スクリプトと紐付ける事のできるイベントハンドラには4種類あります。

  • member-join : 新しいノードがクラスタに加入した時に発生するイベント
  • member-leave : あるノードがクラスタから外れる時に発生するイベント
  • member-failed : 特定ノードが通信不可能になった際に発生するイベント
  • user : ユーザが定義できるイベント。serf eventコマンドでイベントを発行できる

上記イベントハンドラとスクリプトの関連付けを設定ファイルに記述します。
serf.confという設定ファイルを作り、コマンドラインオプションで設定ファイルを指定してエージェントを起動します。
イベントハンドラにより実行されるスクリプトは二種類作成しました。全てのイベントに対して実行されるするalwaysreact.shと、いずれかのノードがserf event triggerを発行した時にだけ実行されるtrigger.shです。
内容はどちらのスクリプトもSERFの環境変数をファイルに書き込むだけのものです。

[ec2-user@host1 ~] $ cat serf.conf
[ec2-user@host1 ~]$ cat serf.conf 
{
	"role" : "web",
	"event_handlers" : [
		"/home/ec2-user/alwaysreact.sh >> /home/ec2-user/alwaysreact.log",
		"user:trigger=/home/ec2-user/trigger.sh >> /home/ec2-user/trigger.log"
	]
}
[ec2-user@host1 ~]$ cat alwaysreact.sh 
#!/bin/bash

echo `date`
echo ${SERF_EVENT}
echo ${SERF_SELF_NAME} 
echo ${SERF_SELF_ROLE}
echo ${SERF_USER_EVENT}
echo '----------------'
[ec2-user@host1 ~]$ cat trigger.sh 
#!/bin/bash

echo `date`
echo ${SERF_USER_EVENT}
# serf起動
[ec2-user@host1 ~]$ serf agent -config-file=serf.conf
==> Starting Serf agent...
==> Serf agent running!
    Node name: 'ip-172-31-30-160'
    Bind addr: '0.0.0.0:7946'
     RPC addr: '127.0.0.1:7373'
    Encrypted: false

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

2013/11/04 00:41:08 [INFO] Serf agent starting
2013/11/04 00:41:08 [INFO] serf: EventMemberJoin: host1 172.31.30.160 # <- Host1がjoin
2013/11/04 00:41:08 [INFO] Serf agent started
2013/11/04 00:41:09 [INFO] agent: Received event: member-join 
2013/11/04 00:41:15 [INFO] Agent joining: [172.31.0.222] # <- Host2がjoin
2013/11/04 00:41:15 [INFO] Initiating push/pull sync with: 172.31.0.222:7946
2013/11/04 00:41:15 [INFO] serf: EventMemberJoin: host2 172.31.0.222
2013/11/04 00:41:16 [INFO] agent: Received event: member-join
2013/11/04 00:41:17 Requesting user event send: trigger. Coalesced: true. Payload: "" # <- host2でtriggerイベントを発行
2013/11/04 00:41:18 [INFO] agent: Received event: user-event: trigge
2013/11/04 00:41:24 [INFO] Responding to push/pull sync with: 172.31.0.222:51811
^C==> Gracefully shutting down agent... # <- Ctrl + C でシャットダウン
2013/11/04 00:41:29 [INFO] agent: requesting graceful leave from Serf
2013/11/04 00:41:29 [ERR] RPC accept error: accept tcp 127.0.0.1:7373: use of closed network connection
2013/11/04 00:41:30 [INFO] serf: EventMemberLeave: host1 172.31.30.160
2013/11/04 00:41:31 [INFO] agent: requesting serf shutdown
2013/11/04 00:41:31 [INFO] agent: shutdown complete
2013/11/04 00:41:31 [INFO] agent: Received event: member-leave
[/bash]

<p>出力内容を確認してみます。alwaysreact.logの方では、発生したイベント全てに対して(三回)実行されていることが確認できます。一方、trigger.logはtriggerイベントを発行した時だけ(一度だけ)実行されています。</p>


Mon Nov 4 00:41:09 UTC 2013
member-join
host1
web

----------------
Mon Nov 4 00:41:16 UTC 2013
member-join
host1
web

----------------
Mon Nov 4 00:41:18 UTC 2013
user
host1
web
trigger
----------------
[ec2-user@host1 ~]$ cat trigger.log 
Mon Nov 4 00:41:18 UTC 2013
trigger

まとめ

Serfを動かしてみるところまで行いました。一つのサーバでイベントを実行するとそれがクラスタ内の全ノードに伝播するというのは、サーバの増減が頻繁に起こるAutoScaling下でのEC2において活用できるのではないかと思います。引き続きSerfの検証を行って、ブログにしていこうと思います。