先日、社内勉強会以外の何か(仮)で、serf について話したときの資料がでてきたので置いておきます。
https://gist.github.com/ngyuki/23b9fa494fd49e358734
まあ、4096番煎じぐらいで真新しいものではありません。
Serf is なに?
Packer や Vagrant の HashiCorp 社が作っているオーケストレーションツール
オーケストレーション is なに?
サーバプロビジョニングを構成する要素の一部だと言われていますが・・・
- Bootstrapping
- Kickstart とか
- Configuration
- Ansible とか
- Orchestration
- Serf とか
オーケストレーション (コンピュータ) - Wikipedia
オーケストレーション(英: Orchestration)は、複雑なコンピュータシステム/ミドルウェア/サービスの配備/設定/管理の自動化を指す用語。 何らかの知的制御や自律制御として議論されることが多いが、技術的解説と言うよりも大部分は単なるアナロジーである。実際には、オーケストレーションは制御理論の要素としてオートメーションやシステムの考え方を持ち込んだものと言える。 このようなコンピュータシステムの「オーケストレーション」という用語は、仮想化やプロビジョニングの文脈で語られることが多く、バズワード的要素が強い。
Bootstrapping でも Configuration でもないその他いろいろ、程度のニュアンスだと思います。
Docker コンテナ
試す環境として Docker を使います。
docker run -dit --name node1 -h node1 centos:7 bash docker run -dit --name node2 -h node2 centos:7 bash docker run -dit --name node3 -h node3 centos:7 bash
それぞれ別のターミナルでログインします。
docker exec -it node1 bash
docker exec -it node2 bash
docker exec -it node3 bash
この Docker コンテナは最小構成の CentOS 7 なので、あとで必要になるパッケージをインストールしておきます。
yum install -y wget unzip
serf インストール
serf は golang 製なのでバイナリいっこ配置するだけでインストールできます。
wget https://dl.bintray.com/mitchellh/serf/0.6.4_linux_amd64.zip unzip 0.6.4_linux_amd64.zip mv serf /usr/local/bin
serf をとりあえず試す
すべてのノードでエージェントを起動します。
serf agent -iface=eth0
クラスタメンバの確認します。上で開いたターミナルは serf agent がフォアグラウンドにいるので、別のターミナルから docker exec でコンテナの中でコマンドを実行します(インデント部分はコマンドの出力)。
docker exec node1 serf members node1 172.17.0.10:7946 alive docker exec node2 serf members node2 172.17.0.11:7946 alive docker exec node3 serf members node3 172.17.0.12:7946 alive
まだお互いを認識していないので、メンバには自分自身だけ表示されます。
node02 で node01 のクラスタにジョインします。
docker exec node2 serf join 172.17.0.10
もう一回クラスタメンバを確認してみます(インデント部分はコマンドの出力)。
docker exec node1 serf members node1 172.17.0.10:7946 alive node2 172.17.0.11:7946 alive docker exec node2 serf members node2 172.17.0.11:7946 alive node1 172.17.0.10:7946 alive docker exec node3 serf members node3 172.17.0.12:7946 alive
node01 と node02 はお互いを認識しました。
node03 も node01 のクラスタにジョインします。
docker exec node3 serf join 172.17.0.10
すべてのノードがお互いを認識するようになりました(インデント部分はコマンドの出力)。
docker exec node1 serf members node3 172.17.0.12:7946 alive node1 172.17.0.10:7946 alive node2 172.17.0.11:7946 alive docker exec node2 serf members node2 172.17.0.11:7946 alive node1 172.17.0.10:7946 alive node3 172.17.0.12:7946 alive docker exec node3 serf members node3 172.17.0.12:7946 alive node2 172.17.0.11:7946 alive node1 172.17.0.10:7946 alive
ディスカバリ
前述の方法だと、最初にいずれかのノードを指定してクラスタにジョインする必要がありましたが、マルチキャストが使える環境であれば自動的にクラスタにジョインさせることもできます。
いったんすべてのノードでエージェントを停止します。
次のように -discover
に適当な名前を付けて起動します。
serf agent -iface=eth0 -discover=oreore
起動後にクラスタメンバを確認してみると、自動的にクラスタにジョインされています(インデント部分はコマンドの出力)。
docker exec node1 serf members node1 172.17.0.10:7946 alive node2 172.17.0.11:7946 alive node3 172.17.0.12:7946 alive docker exec node2 serf members node2 172.17.0.11:7946 alive node1 172.17.0.10:7946 alive node3 172.17.0.12:7946 alive docker exec node3 serf members node3 172.17.0.12:7946 alive node1 172.17.0.10:7946 alive node2 172.17.0.11:7946 alive
なお、-discover
に指定した名前がクラスタの名前になるので、同じセグメントに複数のクラスタがある場合はクラスタごとに異なる名前にする必要があります。
イベントハンドラ
serf はクラスタ内で発生するさまざまなイベントに対してスクリプトを実行することができます。
一旦 node1 のエージェントを停止してイベントハンドラのスクリプトを node1 に作成します。
cat <<'EOS'> handler.sh #!/bin/bash echo printf "\e[0;32m%s=%s\e[m\n" "SERF_EVENT" "${SERF_EVENT}" printf "\e[0;32m%s=%s\e[m\n" "SERF_SELF_NAME" "${SERF_SELF_NAME}" printf "\e[0;32m%s=%s\e[m\n" "SERF_USER_EVENT" "${SERF_USER_EVENT}" printf "\e[0;32m%s=%s\e[m\n" "SERF_USER_LTIME" "${SERF_USER_LTIME}" while read line; do printf " \e[0;32m%s\e[m\n" ${line} done EOS chmod +x handler.sh
イベントハンドラを指定してエージェントを起動します。
serf agent -iface=eth0 -discover=oreore -log-level=debug -event-handler=$PWD/handler.sh
さっそくノードがジョインしたイベントが発生しました。
SERF_EVENT=member-join SERF_SELF_NAME=node1 SERF_USER_EVENT= SERF_USER_LTIME= node1 172.17.0.10 node2 172.17.0.11 node3 172.17.0.12
node2 のエージェントを停止すると member-leave イベントが発生します。
SERF_EVENT=member-leave SERF_SELF_NAME=node1 SERF_USER_EVENT= SERF_USER_LTIME= node2 172.17.0.11
もう一度 node2 のエージェントを起動すると member-join が発生します。
SERF_EVENT=member-join SERF_SELF_NAME=node1 SERF_USER_EVENT= SERF_USER_LTIME= node2 172.17.0.11
node3 のエージェントをサスペンドさせると member-failed が発生します (Ctrl+Z)。
SERF_EVENT=member-failed SERF_SELF_NAME=node1 SERF_USER_EVENT= SERF_USER_LTIME= node3 172.17.0.12
再開させると (fg) member-join が発生します。
SERF_EVENT=member-join SERF_SELF_NAME=node1 SERF_USER_EVENT= SERF_USER_LTIME= node3 172.17.0.12
カスタムイベント
次のように任意のカスタムイベントを発生させることができます
docker exec node1 serf event hoge 1234567890
hoge はイベント名で 1234567890 はペイロードです、ペイロードは標準入力から得られます
SERF_EVENT=user SERF_SELF_NAME=node1 SERF_USER_EVENT=hoge SERF_USER_LTIME=1 1234567890
クエリ
カスタムイベントはイベントを通知するだけですが、クエリだと各ノードからコマンドやスクリプトの実行結果を得ることができます
一旦すべてのノードのエージェントを停止して、次のようにイベントハンドラを指定してエージェントを起動します
serf agent -iface=eth0 -discover=oreore -event-handler=query:shell=/bin/bash
クエリを実行します
docker exec node1 serf query shell uptime
すべてのノードで uptime
を実行した結果が得られます。
Query 'shell' dispatched Ack from 'node1' Response from 'node1': 12:55:05 up 1 day, 12:09, 0 users, load average: 0.00, 0.00, 0.00 Ack from 'node2' Ack from 'node3' Response from 'node3': 12:55:06 up 1 day, 12:09, 0 users, load average: 0.00, 0.00, 0.00 Response from 'node2': 12:55:06 up 1 day, 12:09, 0 users, load average: 0.00, 0.00, 0.00 Total Acks: 3 Total Responses: 3
Docker コンテナを管理
もう少し実用的な用途として、Docker のコンテナ起動時に serf を自動でホストを含むクラスタに参加させるようにしてみます。
まず、ホスト側にも serf をインストールします。
wget https://dl.bintray.com/mitchellh/serf/0.6.4_linux_amd64.zip unzip 0.6.4_linux_amd64.zip sudo mv serf /usr/local/bin
コンテナ起動時に serf を実行するための Dockerfile を作成します。
FROM centos:7 RUN yum install -y wget unzip ;\ yum clean all RUN wget -q https://dl.bintray.com/mitchellh/serf/0.6.4_linux_amd64.zip && \ unzip 0.6.4_linux_amd64.zip && \ mv -v serf /usr/local/bin ;\ rm -vf unzip 0.6.4_linux_amd64.zip ENTRYPOINT ["serf"] CMD ["agent", "-iface=eth0", "-discover=oreore", "-event-handler=query:shell=/bin/bash"]
イメージをビルドします。
docker build -t example:serf .
コンテナを起動します。
docker run -d --name node1 -h node1 example:serf docker run -d --name node2 -h node2 example:serf docker run -d --name node3 -h node3 example:serf
さらにホスト側でも起動
serf agent -iface=docker0 -discover=oreore -node=host >/dev/null &
メンバを一覧すると・・・
serf members
コンテナの一覧が得られます。
host 172.17.42.1:7946 alive node1 172.17.0.10:7946 alive node2 172.17.0.11:7946 alive node3 172.17.0.12:7946 alive
クエリを使えば・・・
serf query shell uptime
すべてのコンテナでコマンドを実行することができます。
Query 'shell' dispatched Ack from 'host' Ack from '361998e3d055' Response from '361998e3d055': 13:10:24 up 1 day, 12:24, 0 users, load average: 0.00, 0.05, 0.02 Ack from 'd0e5b5fb6c63' Response from 'd0e5b5fb6c63': 13:10:24 up 1 day, 12:24, 0 users, load average: 0.00, 0.05, 0.02 Ack from '3fc7c50f677e' Response from '3fc7c50f677e': 13:10:25 up 1 day, 12:24, 0 users, load average: 0.00, 0.05, 0.02 Total Acks: 4 Total Responses: 3
さいごに
Docker コンテナを自動でクラスタにする例はなかなか実用的な雰囲気がありましたが、実際のところなかなかよい用途が思いつかないような気もします。
よく紹介されている例だと、次のような用途があるようです。
- ノードの起動時に /etc/hosts を自動で追記
- 停止時には /etc/hosts から自動で削除する
- ノードの起動時のロードバランサに自動で追加
- 停止時にはロードバランサから自動削除する
- ノードの起動時に自動的に munin の監視対象として追加
- 停止時には自動的に削除される
- デプロイなどの一括実行
- SSH 経由の Push 型よりも並列度を高められる
- 簡単な Active/Standby の HA クラスタ
- ノードに優先度を設けて Active を選択