Consul + Capistrano でオーケストレーションさせてみた
はじめに
Serfに続いてHashiCorpからConsulが発表されて、2ヶ月少々経ちました。 公式では
- Serf: service discovery and orchestration
- Consul: service discovery and configuration
と言っていますが(http://www.serfdom.io/intro/vs-consul.html)、Consulも使い方によってはオーケストレーションできるかなと思って、試してみました。
ちなみに Serf や Consul の最近の動向については @zembutsu さんの記事がわかりやすいです
そもそもオーケストレーションとは
- webサーバをproxyから追加したり抜いたり
- webサーバにデプロイしたり
- 障害が発生したサーバを撤去したり
- dbのslaveを追加したり
など、複数のサーバが連携している様な構成に対して、SSHで入って手作業で行っていた作業を自動化することの総称です(多少バズってるので言葉の定義は曖昧ですが)。 日々の運用業務を簡略化でき、障害の一次対応などを自動化することで、アラート対応に追われる日々が改善されるかもしれません。
オーケストレーションの内容
オーケストレーションの動作はおおまかに次の2ステップに分けられると思います。
- Step1. 対象サーバを監視し、変化を検知する
- Step2. 検知したサーバに対して何かしらの操作を行う
例は次のとおりです。
- ハードウェアの異常
- ホストダウンなどサービスを提供することが困難な状態を検知する
- サービスに影響が出ないよう、サービスアウトを行う(webの場合は、proxyから抜くなど)
- ハードウェアの負荷
- CPU/メモリの使用率、ロードアベレージの監視し、特定のサーバに負荷がかかっていることを検知する
- スケールアウトを行う(webの場合は、proxy配下に新たにwebサーバを追加するなど)
- ソフトウェアの異常
- ソフトウェアの死活監視やログ監視によって、サービスの異常動作を検知する
- サービスに影響が出ないようサービスアウトを行うなど
またこれらを実現するための既存のツールは次のとおりです。
- Step1. 監視ツールの類
- Zabbix
- Sensu
- Serf
- Consul など
- Step2. 任意のサーバにて任意のコマンドを実行するツールの類
- Fabric
- Capistrano
- Serf など
Serfについて
各サーバにAegentを配置し、ゴシッププロトコルを利用してクラスタを形成します。 Serfの主な機能は以下のとおりです
- 各種イベントを検知して任意のスクリプトを実行する
- member-join: メンバーが加わったとき
- member-leave: メンバーが抜けたとき
- member-failed: メンバーを見失ったとき(疎通確認に失敗たとき)
- member-update: メンバーの情報が更新されたとき
- member-reap: メンバーをリストから削除したとき(member-leave またはmember-failed から一定時間経過したとき)
- user: 任意のユーザイベントが発行されたとき
- query: 任意のクエリが発行されたとき
Serfの使い方についてはこちらにまとめました -> Dockerで試す、はじめてのSerf
Consulについて
HashiCorpの4つめのプロダクト(Vagrant Packer Serf の次)
監視とコンフィグレーションのためのツールで、serfの仕組み(ゴシッププロトコル)を利用して、クラスタを形成し、クラスタ内の情報を共有しています。
Consulの主な機能は以下のとおりです
- サービス検出: クラスタ内にどのようなサービス(Proxy Apache MySQL など)が存在するか検出する
- 障害検知: ホストダウンやロードアベレージ、プロセスの死活監視など、任意のスクリプトで監視して、障害を検知することができます
- Key/Value Strage: 任意のノードから編集でき、クラスタ内で共有されます
- 複数データセンター対応: serfはネットワークセグメントが異なると(データセンターが異なると)、クラスタ形成は出来ないが、consulでは対応されています
サーバ情報の取得方法
consulで形成された各サーバやクラスタの情報、サービス監視の状態などは、クラスタ内外から取得するとこができます。 取得する方法は以下の2種類の方法があります
- HTTP API: http://www.consul.io/docs/agent/http.html
- DNS Interface: http://www.consul.io/docs/agent/dns.html
取得できる情報については省略します(ドキュメント丁寧にかかれているのでそちらを見た方がいい)。
Blocking Query
HTTP APIはBlocking Queryの仕組みを持っています。
例えば /v1/health/state/passing
をGET
すると、即座に返答が返ってきます。
しかし /v1/health/state/passing?index=123
のようにindex
パラメータを付加してGET
すると、即座に返答が返らず、クラスタ内に何かしらの変化があった瞬間に返答が返ってきます。これをConsulではBlocking Queryと呼んでいます。
なおindex
はクラスタの状態が変化すると数値が変わり、最新のindex
をパラメータとして付加しないと、Blocing Queryは成立しません。
最新のindex
の値は、HTTPのヘッダーのX-Consul-Index
に格納されています。
SerfとConsulの違いについて
Step1における違い
Serfはサーバの死活監視しか行うことが出来ないのに対して、Consulは任意のスクリプトによって柔軟にサーバやサービスを監視して検知することが可能です。 ただし、Serfは任意のイベントを発行することができるため、Serf + 他の監視ツールを組み合わせることによって柔軟な監視が可能になります(監視ツールによってSerfイベントを発行することができれば)。
Step2における違い
Serfはクラスタ内のサーバの異常を検知し、任意のスクリプトを実行します(検知から実行まで自律的に行う)。それに比べてConsulは、クラスタ内のサーバの異常を検知して、クラスタの情報を更新します(検知しても対象のサーバに対して何もしないし出来ない、他のツールへの通知もできません)。 ただし、Blocking Queryの仕組みを利用することで、状態変化を検知して任意のコマンドを実行することはできます(正確には、任意のコマンドを実行するツールを作ることはできます)。
Consul と Capistarno を組み合わせる
概要
Consul と Capistarno をつなぎ合わせるため今回
- Attender
- Enforcer
をつくりました(とりあえず動かそうと思ったため実装に不安あり)。
Attender
https://github.com/foostan/attender
Consulの状態変化を、Blocking Queryの仕組みを利用して取得し、他のツールへつなげるためのツールです。 動作内容については下記のデモに記載してあります。
Enforcer
https://github.com/foostan/enforcer
Attenderからの通知を受け取り、Capistranoを実行します。また、Capistranoで設定が必要なサーバ情報は、Consulから取得します。 オーケストレーションの具体的な操作(webをproxyから抜くなど)の実装については、利用者任せです(そもそも環境によってやることが様々なのでツールとして提供できない)。 動作内容については下記のデモに記載してあります。
Consul + Attender + Enforcer のデモ
デモ環境のインストール
git clone git@github.com:foostan/enforcer.git cd enforcer bundle install vagrant up # Consul apache2 ntp がインストールされた server-01 と client-01 が起動する
Cunsulの動作確認
UI
API
> curl 192.168.33.101:8500/v1/catalog/node/server-01 -s | jq . { "Services": { "ntp": { "Port": 0, "Tags": [], "Service": "ntp", "ID": "ntp" }, "consul": { "Port": 8300, "Tags": null, "Service": "consul", "ID": "consul" }, "apache2": { "Port": 0, "Tags": [], "Service": "apache2", "ID": "apache2" } }, "Node": { "Address": "192.168.33.101", "Node": "server-01" } } > curl 192.168.33.101:8500/v1/health/state/passing -s | jq . [ { "ServiceName": "", "ServiceID": "", "Output": "Agent alive and reachable", "Notes": "", "Status": "passing", "Name": "Serf Health Status", "CheckID": "serfHealth", "Node": "server-01" }, { "ServiceName": "ntp", "ServiceID": "ntp", "Output": "", "Notes": "", "Status": "passing", "Name": "Service 'ntp' check", "CheckID": "service:ntp", "Node": "server-01" }, { "ServiceName": "", "ServiceID": "", "Output": "", "Notes": "", "Status": "passing", "Name": "Check load average", "CheckID": "loadavg", "Node": "server-01" }, { "ServiceName": "", "ServiceID": "", "Output": "Agent alive and reachable", "Notes": "", "Status": "passing", "Name": "Serf Health Status", "CheckID": "serfHealth", "Node": "client-01" }, { "ServiceName": "", "ServiceID": "", "Output": "", "Notes": "", "Status": "passing", "Name": "Check load average", "CheckID": "loadavg", "Node": "client-01" }, { "ServiceName": "apache2", "ServiceID": "apache2", "Output": "", "Notes": "", "Status": "passing", "Name": "Service 'apache2' check", "CheckID": "service:apache2", "Node": "client-01" }, { "ServiceName": "ntp", "ServiceID": "ntp", "Output": "", "Notes": "", "Status": "passing", "Name": "Service 'ntp' check", "CheckID": "service:ntp", "Node": "client-01" }, { "ServiceName": "apache2", "ServiceID": "apache2", "Output": "", "Notes": "", "Status": "passing", "Name": "Service 'apache2' check", "CheckID": "service:apache2", "Node": "server-01" } ]
DNS
> dig @192.168.33.101 -p 8600 apache2.service.dc1.consul. ; <<>> DiG 9.8.3-P1 <<>> @192.168.33.101 -p 8600 apache2.service.dc1.consul. ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24634 ;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0 ;; WARNING: recursion requested but not available ;; QUESTION SECTION: ;apache2.service.dc1.consul. IN A ;; ANSWER SECTION: apache2.service.dc1.consul. 0 IN A 192.168.33.201 apache2.service.dc1.consul. 0 IN A 192.168.33.101 ;; Query time: 1 msec ;; SERVER: 192.168.33.101#8600(192.168.33.101) ;; WHEN: Sat Jun 21 16:30:42 2014 ;; MSG SIZE rcvd: 128
Attenderの動作確認
クラスタ内の状態変化を検知すると、変化内容が標準出力されます
> attender 192.168.33.101 - server-01 service:apache2 # server-01のapache2を停止させた + server-01 service:apache2 # server-01のapache2を起動させた - server-01 service:ntp # server-01のntpdを停止させた + server-01 service:ntp # server-01のntpdを起動させた
Enforcerの動作確認
attenderコマンド
の標準出力をパイプでaxコマンド
に渡すと、変化に対応したタスクが実行されます。
> attender 192.168.33.101 | ax # '- server-01 service:apache2' を受け取ると # fail:service:apache2 タスクが実行されます(第一引数として 'server-01' が渡される)。 ** Invoke test (first_time) ** Execute test ** Invoke load:defaults (first_time) ** Execute load:defaults ** Invoke fail:service:apache2 (first_time) ** Execute fail:service:apache2 Failing check service:apache2 of server-01 # '+ server-01 service:apache2' を受け取ると # pass:service:apache2 タスクが実行されます(第一引数として 'server-01' が渡される)。 ** Invoke test ** Invoke pass:service:apache2 (first_time) ** Execute pass:service:apache2 Passing check service:apache2 of server-01
上記の例で実行されるタスクはこちら https://github.com/foostan/enforcer/blob/master/lib/enforcer/capistrano/tasks/apache2.rake
ところで
envconsulというものがあって、これを利用するとConsulのKVSの変更のタイミングでイベントを起こすことができるようです。
それじゃ、KVSではなくConsulの各変化に応じてイベントを起こせないのか?と疑問に思ったところ @zembutsu さんより有力な情報を頂きました(ありがとうございます!)。
@foostan うーん、今自分が知っているのは KVS の変化のみです。本当は Consul の障害検出と連動できれば便利そうですよね。。今するなら、通常の Consul サービスカタログを HTTP API 経由で定期チェックしたりでしょうか。後ほど、少し調べて見ます。
— 前佛 雅人(M.Zembutsu) (@zembutsu) 2014, 6月 24
@foostan そのようですね。。ざっとソースも眺めていましたが、単純に KVS のエンドポイントを確認しているだけでした。https://t.co/tsrtIiVdZI でも言及されていますが、どうやら今は無い機能ですね。
— 前佛 雅人(M.Zembutsu) (@zembutsu) 2014, 6月 24
@foostan Google Gropus の過去ログをよんでみました。ロードマップの投稿によると、イベントにフックするのは次のバージョンですね。v0.4からは、Serf のようにイベントハンドラをサポートしたり、eventやquery機能を扱えるようになるようです。
— 前佛 雅人(M.Zembutsu) (@zembutsu) 2014, 6月 24
Consulのロードマップはこちら https://groups.google.com/d/msg/consul-tool/J3Oiysk3ggQ/kxh2gFL0ksIJ
予定だともうじき 0.4 がリリースされるはず(ここ数日コミットがないようだけど…)なので、期待して待っています。
さいごに
今回、Consul + Capistrano でオーケストレーションさせてみようと思い、これらをつなぎ合わせるために Attender と Enforcer を作りました。 デモでは具体的な操作は行っていませんが、capistranoのtaskを作成すれば、割りと何でもできる気がします。
また、Consul v0.4 がリリースされると、Serfのような使い勝手が出来るようになりそうなので、Consul でできる幅がさらに広まります(Attenderの必要性はおそらくなくなりますね)。