なぜConsulを使うのか
サービスからサーバの切り離しを素早く行いたいから。
サービスを提供するAPPサーバが複数台あり、リバースプロキシからリクエストを振り分けているような場合、諸々設定やらですぐ切り離せなかったり、面倒だったりします。そこでConsulを参照させ、HTTP APIつきDNSとして便利に使おうというわけです。Health checkで自動切り離しもできる。
構成
Consulはサーバモードとクライアントモードがあり、他のエージェント(サーバ・クライアント)と繋がることでクラスタが構成されます。耐障害性をもたせるならサーバが3台以上必要です(Consensus Protocol - Consul by HashiCorp)。
Consulはクラスタ全体でservice
というグループごとにnode
という情報を持ちます。使用するときは、service
で問い合わせて所属するnode
一覧を取得する。1エージェントにつき、service
ごとに 1 node
が登録される形です。エージェントがクラスタから離脱すると、そのエージェントが持っていたservice
内のnode
は返されなくなる。
つまり、ConsulはAPPサーバと同居し、それぞれ提供するservice
を登録するとなります。少なくともクライアントは、専用サーバを用意してみんなで使用するといった形ではない。
導入
Consulはバイナリをばらまいて、バイナリ単体で動かします。コンテナの潮流を感じる……。
非コンテナ環境なので、とりあえず以下の感じでバイナリ置いてデーモンとして動いてもらいます。
# バイナリ取得
curl https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_linux_amd64.zip -o /tmp/consul.zip
unzip /tmp/consul.zip -d /usr/local/bin
chmod 755 /usr/local/sbin/consul
# 設定置く用ディレクトリ
mkdir /etc/consul.d
# キャッシュ置く用ディレクトリ
mkdir /opt/consul
# 設定を投入
echo '{
"server": true,
"bootstrap_expect": 3,
"bind_addr": "'$(ip -f inet -o addr show eth0|cut -d\ -f 7 | cut -d/ -f 1)'",
"data_dir": "/opt/consul",
"start_join": ["host-of-consul"]
}' > /etc/consul.d/consul.json
# スクリプトを置く
# http://qiita.com/yunano/items/7ef5fa5670721de55627
# 圧倒的感謝!!!
chmod 755 /etc/init.d/consul
chkconfig --add consul
chkconfig consul on
設定は以下のとおり。(公式 Configuration - Consul by HashiCorp)
項目 | 説明 |
---|---|
server |
server として起動させたい時true |
bootstrap_expect | consulはクラスタ構成時にbootstrapという初期動作を行いますが、この数値のサーバが揃うまでそれをやらない。 構成の server の台数を設定でよいです |
bind_addr | 複数のネットワークがある場合必要。例だとeth0のIPを設定時に拾って突っ込んでる |
data_dir | キャッシュ置き場 |
start_join | 起動時にクラスタとして繋ぎに行く先。後からコマンドで設定もできる。自サーバでもOK |
/opt/consul
ですが、一旦落として立ち上げ直した後も参照して設定を引き継ぎます。start_join
の設定を入れていなくても、かつて所属していたクラスタに勝手に復帰する。なので、検証とかしたいときは都度削除しないとよくわからない事になります。
service
の登録
HTTP APIが使えます。(Agent (HTTP) - Consul by HashiCorp)
# 登録
curl localhost:8500/v1/agent/service/register -X PUT -d '{"name": "service-name", "port": 8080, "check": {"script": "curl localhost:8080 >/dev/null 2>&1", "interval": "10s"}}'
# 削除
curl localhost:8500/v1/agent/service/deregister/service-name
登録された設定はすみやかにクラスタに行き渡り、どこからでもservice-name
で参照できます。これで切り離したい時に登録を削除し、復帰時に登録しなおせばよい。カラヒー!
IPはbind_addr
で指定したか自明な自IPが使用されます。
試してないですが、SpringだとSpring Cloud Consulで、この辺も記述しておけば勝手にやってくれるみたい。
nginxから参照
DNS APIを使って参照します。(DNS Interface - Consul by HashiCorp)
まず、参照したいサーバにもConsulエージェントが居る必要があります。あと、dnsmasqなどDNSサーバを間に挟む(デフォルトのポート番号とか、ConsulのDNS機能が完全ではないようなので……もしかしたらポートを53に変えればいいだけかも)。DNSの設定は公式で提供されています Forwarding - Consul by HashiCorp。
これで、例えばservice-name.service.consul
というドメイン名の名前解決をすれば、service-name
に付いてるnode
が取得できます。複数のAレコードが返ってくる、DNSラウンドロビンになります。解釈によりますが、同一ネットワークならランダムで簡単な負荷分散も見込めるかと思います。
後はnginxのconfで参照先をconsulドメインにするだけ。ところが、nginxは一旦名前解決したアドレスをずっと使い続けます。なので、Nginxでproxy_passにホスト名を書いた時の名前解決のタイミング - (ひ)メモ などを参考に対応が必要です。自分はupstream使わずset変数ハックで対応。
触ってみての感想
以上、リリースで色々問題がありベストソリューションや! と思い使ってみましたが、今のところ特に問題も出ておらず狙い通り動いてます。
調べつつ思ったのが、やはり世はイミュータブル・インフラストラクチャに向かっているんだなと。1コンテナに1つのサービスで、Consulがくっついててサービスの情報を持っている。切り離すときはコンテナごと捨てて終わり。そういう潮流みたいなのを強く感じました。
というのも、コンテナであれば、作る段階でアプリと一緒にConsulを送って、設定ファイルにservice
の設定も入れておいて起動、使わなくなったらコンテナごと捨てる、といった形でシンプルになるのではないかと思われました。1サーバで複数のアプリが動く可能性があると、アプリごとにConsulエージェントを立ち上げるためポートを変えたりする必要があり、ほとんど無理。しかし、service
の設定をHTTP APIで投入するってちょっと内容に曖昧さがあって(誰が投入するんだとか)不安を感じるのですね。とはいえSpring Cloud Consulだと起動時にSpringから投入になるんですが。どういう形がよいのか……
Consulはまだまだ機能があり、KVSやevent、lockなど気になっています。
リリースをeventキックするだけというのも良さそう。lockを使えばサービス提供を担保しつつ逐次切り替えとかもできるはず。まぁ現状jenkinsで構築した環境があり十分なので後回しですが、より動的なインフラ環境になってくると必要性が出てくると感じます。
設定はアプリに持つのではなく設定サーバから提供したいという思いがあり(設定変えるのにリリースするのはめんどう)、ConsulのKVSは良さそうに思いますが、どこからでも設定変更できるがACLの設定で制御できるか、変更管理はどうするか、運用どうなるかといった所で、他のソリューションを含めて模索している所です。