メッセージングツールSerfをEC2で使ってみる
こんにちは、望月です。
今日はSerfというプロダクトを触ってみたので、学んだ内容をまとめたいと思います。
Serfとは?
Serfはゴシッププロトコルを利用したサーバ間のメッセージングツールです。固定のマスターを持たず、クラスタに参加しているノード間で通信して情報をやりとりするのが特徴です。
Serfの特徴として、公式ページには3つの特徴が挙げられています。
- メンバーシップ : 複数のサーバでクラスタを組み、ノードが追加されたり減少したりした際にイベントを発行することができる
- 不具合検知と修復 : クラスタ内のノードの生死を自動で判断し、不調のインスタンスが存在することを知らせるイベントを発行することができる
- カスタムイベントの通知 : ユーザ独自のイベントを発行し、任意の処理を行わせることができる
とりあえず動かしてみる
Amazon Linuxを使って、まずはインストールと動作確認を行ってみます。
SerfではTCP/UDPの7946番ポートを利用しますので、あらかじめセキュリティグループで開放をしておく必要があります。
今回は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の検証を行って、ブログにしていこうと思います。