ようへいの日々精進XP

よかろうもん

docker + serf でどんなことが出来るか考えてみた(触ってみた)

はじめに


参考


構築のイメージ

構築のイメージは以下のような感じ。

f:id:inokara:20131226071551p:plain

ということで、早速、やったみよー!

準備

ホスト側にも serf をインストール

こちらを参考にさせて頂いて、VirtualBox 内の Ubuntu Server 13.04serf をインストール。インストールの手順としてはとても簡単。

wget https://dl.bintray.com/mitchellh/serf/0.3.0_linux_amd64.zip
unzip 0.3.0_linux_amd64.zip
cp serf /usr/local/bin/

そして、serf を起動する。

serf agent &

serf 入りのコンテナを作る

以下のような Dockerfile を使ってコンテナを作成。

FROM inokappa/wheezy-7.2-basic
RUN apt-get update
RUN apt-get -y install openssh-server
RUN apt-get -y install build-essential wget curl unzip
RUN cd /tmp && wget https://dl.bintray.com/mitchellh/serf/0.3.0_linux_amd64.zip && unzip 0.3.0_linux_amd64.zip
RUN cp /tmp/serf /usr/local/bin/
RUN mkdir -p /var/run/sshd
RUN useradd -d /home/serf -m -s /bin/bash serf
RUN echo serf:serf | chpasswd
RUN echo 'serf ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

FROM には inokappa/wheezy-7.2-basic を指定しているが、適宜、適当なコンテナに読み替えても問題無いかと思う(Debian とか Ubuntu であればイケるはず!)

docker build -t inokappa/wheezy-serf .

コンテナを起動して serf を起動する

以下のようにして serf 入りのコンテナを起動して ssh を使ってログインする。

container=`sudo docker run -t -d -p 22 inokappa/wheezy-serf /usr/sbin/sshd -D`
ssh serf@`sudo docker inspect $container | jq -a '.[].NetworkSettings.IPAddress' | cut -d\" -f2`

ssh でログインしたら以下のように serf を起動してクラスタにジョインする。

serf agent -join 172.17.42.1:7946

172.17.42.1docker コンテナのデフォルトゲートウェイであると同時にホストの docker0IP アドレスとなる。

クラスタの状況を確認してみる

serf members を使ってクラスタの状態を確認する。

serf members

以下のような出力が得られる。

    2013/12/22 13:21:04 [INFO] agent.ipc: Accepted client: 127.0.0.1:55332
ubuntu1304    172.16.80.60:7946    alive
1ebce8b3a058    172.17.0.23:7946    alive
773969c90968    172.17.0.25:7946    alive

おお、一応、クラスタ構成されているっぽい。

クラスタの一台を停止させてみる

コンテナを停止してみる。

sudo docker stop ${コンテナ ID}

他のクラスタ内のホストに以下のようなログが出力されると共に serf members でも該当のホストが fail となる。

    2013/12/22 13:24:01 [INFO] serf: EventMemberFailed: 773969c90968 172.17.0.25
    2013/12/22 13:24:02 [INFO] agent: Received event: member-failed

serf members は以下のような出力となる。

ubuntu1304    172.16.80.60:7946    alive
1ebce8b3a058    172.17.0.23:7946    alive
773969c90968    172.17.0.25:7946    failed

イベントハンドラを試してみる

イベントタイプ

serf のセールスポイントの一つ(だと思っている)、クラスタ内のノードからイベントを検知した時点で任意のスクリプトを実行することが出来る。

イベントタイプは以下の通り。(ドキュメントをなんちゃって翻訳)

イベント 詳細
member-join クラスター内にメンバーがジョインした場合
member-leave クラスター内のメンバーが外れた場合
member-failed クラスタ内のメンバーに通信障害が発生した場合(ping のレスポンスが内場合)
user ユーザーが任意のタイミング

試す準備

member-failed が発生したら該当するコンテナを停止して新しいコンテナを起動してジョインさせるというハンドラスクリプト(handler.sh)を以下のように書いてみた。

#!/bin/sh

CONTAINER_IMAGE="inokappa/wheezy-serf"
CONTAINER=`serf members | grep failed | awk '{print $1}'`
echo ${CONTAINER}
sudo docker stop ${CONTAINER}
sudo docker run -d -t ${CONTAINER_IMAGE} serf agent -join 172.17.42.1:7946

予め sudo docker コマンドをパスワード入力無しで動作させられるように /etc/sudoers に以下を設定しておく。

user  ALL=(ALL) NOPASSWD:/usr/bin/docker*

また、serf のコマンドライン引数として -event-handler= オプションがあるが member-failed だけをどうやって引数として渡すかが解らなかった(以下、追記)ので serf.conf というファイルを下記のように作成し -config-fileserf.conf を指定して起動することで対応した。 (形式は JSON 形式となっている)

{
  "role": "test-cluster",

  "event_handlers": [
    "member-failed=./handler.sh"
  ]
}

※追記

-event-handler=member-failed 等のサブイベントを指定する場合には以下のようにすると良い。

serf agent -log-level=debug -event-handler=member-failed=./handler.sh

実際に試す

ホスト側(Ubuntu Server 13.04)で以下のように serf を起動する。

serf agent -log-level=debug -config-file=serf.conf

次に docker コンテナを下記のように起動する。

sudo docker run -d -t inokappa/wheezy-serf serf agent -join 172.17.42.1:7946

serf member でメンツを確認する。

ubuntu1304    172.16.80.60:7946    alive    test-cluster
9eb01f63d9bc    172.17.0.32:7946    alive  

起動したコンテナを停止してみる。

sudo docker stop 9eb01f63d9bc

停止後、暫くするとホスト側(Ubuntu Server 13.04)の標準出力に以下のようなメッセージが表示される。

    2013/12/22 17:36:53 [INFO] agent.ipc: Accepted client: 127.0.0.1:59413
    2013/12/22 17:36:56 [INFO] serf: EventMemberFailed: 9eb01f63d9bc 172.17.0.32
    2013/12/22 17:36:57 [INFO] agent: Received event: member-failed
    2013/12/22 17:36:57 [INFO] agent.ipc: Accepted client: 127.0.0.1:59414
    2013/12/22 17:37:12 [DEBUG] Event 'member-failed' script output: 9eb01f63d9bc
9eb01f63d9bc
c2683c387161546136ac53e51b77998e6097506693317f3748ba70d426f1e530
    2013/12/22 17:37:12 [INFO] Responding to push/pull sync with: 172.17.0.33:44921
    2013/12/22 17:37:12 [INFO] serf: EventMemberJoin: c2683c387161 172.17.0.33
    2013/12/22 17:37:12 [DEBUG] serf-delegate: messageJoinType: c2683c387161
    2013/12/22 17:37:12 [DEBUG] serf-delegate: messageJoinType: c2683c387161
    2013/12/22 17:37:12 [DEBUG] serf-delegate: messageJoinType: c2683c387161
    2013/12/22 17:37:12 [DEBUG] serf-delegate: messageJoinType: c2683c387161
    2013/12/22 17:37:12 [INFO] Initiating push/pull sync with: 172.17.0.33:7946
    2013/12/22 17:37:13 [INFO] agent: Received event: member-join
    2013/12/22 17:37:21 [DEBUG] serf: forgoing reconnect for random throttling
    2013/12/22 17:37:22 [INFO] agent.ipc: Accepted client: 127.0.0.1:33185
    2013/12/22 17:37:42 [INFO] Initiating push/pull sync with: 172.17.0.33:7946

改めて serf member メンツを確認してみる。

ubuntu1304    172.16.80.60:7946    alive    test-cluster
9eb01f63d9bc    172.17.0.32:7946    failed    
c2683c387161    172.17.0.33:7946    alive

おお、新しいコンテナ(c2683c387161) がメンバーとしてジョインしている。でも、failed となったコンテナが member 一覧から消えてしまわないのはナゼなのか(そもそも消えるという概念がないのか)、まだまだ serf は奥深い。(以下、追記(2))

※追記(2)

ドキュメントに一部、エージェントが停止した際のことが書かれているのでざっくり意訳。

An agent can be stoped in two ways: gracefully or forcefully. To gracefully halt an agent, send the process an interrupt signal, which is usually Ctrl-C from a terminal. When gracefully exiting, the agent first notifies the cluster it intends to leave the cluster. This way, other cluster members notify the cluster that the node has left.

エージェントを停止する方法は gracefullyforcefully の二つの方法がありまっせ。通常ターミナル上から Ctrl-c を送るとプロセスは interrupt signal をエージェントに送る。gracefully にエージェントが停止する際には最初にクラスタに対して「すまん、帰る(intends to leave)」と通知する。その他のクラスタメンバーにはエージェントが停止したノードが去りました(node has left)と通知される。

Alternatively, you can force kill the agent by sending it a kill signal. When force killed, the agent ends immediately. The rest of the cluster will eventually (usually within seconds) detect that the node has died and will notify the cluster that the node has failed.

あるいは(意訳:ちなみに)、エージェントを kill シグナルで強制的に kill することが出来る。強制的に kill した場合にはすぐにエージェントが停止する。残されたクラスターのメンバー達はエージェントが停止したことを(ノードがお亡くなりになったと)検出し、クラスター内にノードは failed だと通知する。

と書かれているので、ノード内の serf エージェントの停止の仕方次第(強制停止 or serf leave)でクラスタ内に通知される情報は異なる(left or failed)ものの、一旦、メンバーシップを組んでしまうとお亡くなりになったノードも残り続ける(serf members に表示される)のが正しい動きのようだ。


最後に

  • serf 初めてちゃんと使ってみたけど面白かった
  • serfdocker の組み合わせで自立するインフラがマジで作れるような気がする
  • 既に先人たちが道を切り開いて下さっているので意外にサクッと試すことが出来た
  • ただし、今回は「使ってみた」だけなので、もう少し、深堀して動作等を確認していきたい