Serf 使ってみた

先日、社内勉強会以外の何か(仮)で、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 を選択

Zabbix でホストグループのすべてのホストのグラフをずらーっと並べるやつ

死活監視やリソース監視には長いこと Nagios と Cacti を使ってきたのですが、他のものも使ってみようかなーと思って GW に Zabbix を触ってみました。

デフォで標準的な項目はひと通り監視できそうだしアラートも送れるので、概ね Cacti よりも良さそうだと思ったのですが、Cacti と比べるとグラフを俯瞰的に表示することができないのがつらいなーと思いました。

Cacti ではホスト名を適当な文字列で検索して、ヒットしたホストのグラフをずらーっと並べたりすることができます。時々、ロードアベレージとかメモリ使用量とかをずらーっと並べて異常な振りになっていないかを確認したり、長期的な傾向を見たりしていました。

Zabbix でもスクリーンというものを駆使すれば複数のホストにまたがる俯瞰的な表示ができそうですが、かなり設定が面倒そうです。

せめてグラフを文字列で検索して、チェックボックスでぽちぽちして一括で登録ー! とかできればいいのですが(Cacti にはそういうのもある)

・・・

という感じのことを Twitter でつぶやいていたら Zabbix::Senrigan というものを教えていただきました。

Zabbix::Senriganをつくりました - さよならインターネット

Perl のエコシステムを全く知らなかったのでインストールにかなり手こずりました・・・

これはこれで概ね目的は果たせそうなのですが、期間とかをもっとアドホックに指定してグラフを並べたいと思ったので、Zabbix API を使ってグラフを並べるだけのものを作りました。

ngyuki/zabbix-graph-viewer

インストール方法はちょっと特殊で、Zabbix の URL の中に配置する必要があります。Zabbix が発行するセッション用の Cookie を javascript から読んでいるためです(httpOnly とか付けられると動かなくなります)。

Zabbix にログインした状態でこのツールの URL を開くと、下記のようにホストグループの中のすべてのホストの特定のグラフをずらーっと並べることができます。

ss

Firefox と Chrome で動作することを確認しています。

ngrok が便利すぎて漏らしそう

先日、かつて社内勉強会と呼ばれた何か(仮)で、ngrok というサービスについて話しました。

そのときの資料がこちら。

ngrok を知ったきっかけは下記の Qiita の記事です。

ちょっと使ってみたところ、漏れそうなほどの便利さでした。

機能としては Vagrant share と似たようなものなのですが、Vagrant とは関係なく使えますし、バイナリいっこいれるだけで使えるのでお手軽です。

なお、上記の Qiita の記事は ngrok のバージョンが 1.X 系らしく、最新の 2.0 だと ngrok http 3000 のように指定する必要があります。

また、Mac の brew だと古い 1.X 系がインストールされたので、サイトから最新版をダウンロードすると良いと思います。

Pushbullet が便利すぎて鼻血が出そう

先日、かつて社内勉強会と呼ばれたなにか(仮)で Pushbullet というものについて話しました。

そのときのスライドがこちら。

先月ぐらいに AirGram というものについて話していたのですが・・・

はてブで Pushbullet というのを教えていただき、試してみたところ鼻血が出る便利さでした。

もともとの目的は curl でサクッと Android に Push 通知したかっただけなのですが、いやもうなんかそんなのどうでも良くなる便利さです。

特に Android の通知をすべて PC に表示する機能がクソ便利でもう鼻血出ました。

おかげ様で同僚にデスクトップを見せている時に、恋人からこっ恥ずかしい LINE が来ないかといつもヒヤヒヤしています。

.

.

.

というわけで、同僚にデスクトップを見せているタイミングを見計らってこっ恥ずかしい LINE を送ってくれる恋人を大絶賛募集中です。

AirGram で Android や iPhone に PUSH 通知

先月、かつて社内勉強会と呼ばれた何か(仮)で話したスライドがあったので晒しておきます。

諸事情でスクリプトから自分の携帯に PUSH 通知したくなったことがあり、そのときに知ったサービスです。

ネイティブアプリではない Web サービスでもモバイルへの PUSH 通知を実装できる、ということらしいですが・・・

$ curl https://api.airgramapp.com/1/send_as_guest \
  --data-urlencode email='oreore@example.com' \
  --data-urlencode msg='Hello world!' \
  --data-urlencode url='http://example.com'
curl: (51) SSL: certificate subject name '*.herokuapp.com'
  does not match target host name 'api.airgramapp.com'

証明書が invalid です。

つまりもうメンテされていないということだと思うので、使わないほうがいいと思います。

単に携帯に通知したいだけならメール投げるだけでもいいし(即時性は劣りますが)、Slack に通知するとか、Twitter で自分にダイレクトメッセージを送るとか、他に方法はいくらでもありますし。

xip.io で簡単バーチャルホスト

先日、かつて社内勉強会と呼ばれた何か(仮)で xip.io というものについて話しました。

そんときのスライドはこちら。

PowerDNS といういろいろなものをバックエンドに使える DNS サーバで作られた、IP アドレスをサブドメインに入れておくとその IP アドレスそのものに名前解決されるドメインです。

例えは、以下のような URL でローカルホストにアクセスできます(127.0.0.1 に解決される)。

開発用途とかでたくさんバーチャルホストが欲しいとき、よく /etc/hosts とかを弄ることがあったと思いますが、代わりに使えるかもしれません。

まあ社内なら最近は社内用のドメインDNSサーバがあって、わりとカジュアルにワイルドカード CNAME を登録できるので、わたしはあんまり使う機会ないかも。

Ansible おれおれユースケース

先日、社内勉強会改め、おれとわたしと仲間たち勉強会(仮)で Ansible のことを話しました。

ユースケースの紹介をしたかったというか、

「ホスト一台の構成でも Ansible 便利だから使っていくよ!」

という宣言がしたかっただけなのですが、それだけだと寂しかったので 最後のケースを無理やり突っ込みました。