先日とあるサービスに Consul を入れました。
内部 DNS と、たとえば nginx からアプリケーションサーバに振り分ける定義をするために service を使用しています。
そこで使うために、ohai-plugin-consul を書きました。Github にあります。
fujiwara/ohai-plugin-consul · GitHub
Ohai の version 6 と 7 で plugin の interface が変わっており、ohai-plugin-consul は Ohai 7 向けなので、Chefから使う場合は Chef-11.12.0 以上、または 11.10.4.ohai7.0 が必要です。
【参考】 Ohai, new Ohai plugins! - O'Reilly Radar
使用方法
ohai コマンドから使う場合は -d で plugin (consul.rb) を配置したディレクトリを指定して実行すると、最上位の consul というキーに API を叩いた情報が入ってきます。
ohai -d /path/to/plugin_dir | jq .consul
{
"agent": {
"checks": { ... }, #= /v1/agent/checks
"members": [ ... ], #= /v1/agent/members
"services": [ ... ] #= /v1/agent/services
},
"catalog": {
"datacenters": [ ... ], #= /v1/catalog/datacenters
"nodes": [ ... ], #= /v1/catalog/nodes
"services": [ ... ], #= /v1/catalog/services
"node": {
"FOO": { }, #= /v1/catalog/node/FOO
...
},
"service": {
"BAR": { }, #= /v1/catalog/service/BAR
...
}
}
"status": {
"leader": "...", #= /v1/status/leader
"peers": [ ... ], #= /v1/status/peers
}
}
Chefから使用する場合は、(client|solo).rb に plugin_path を定義してください。
Ohai::Config[:plugin_path] << '/path/to/plugins'
node[:consul] で上記と同様の情報が取得できます。
開発経緯
たとえば以下のように、app というサービスを定義して、その node に nginx からリクエストを振り分けたいとします。
$ curl localhost:8500/v1/catalog/services | jq .
{
"app": [
"pc",
"mobile"
]
}
$ curl localhost:8500/v1/catalog/service/app | jq .
[
{
"ServicePort": 0,
"ServiceTags": [
"pc",
"mobile"
],
"ServiceName": "app",
"ServiceID": "app",
"Address": "192.168.1.11",
"Node": "app001"
},
{
"ServicePort": 0,
"ServiceTags": [
"pc",
"mobile"
],
"ServiceName": "app",
"ServiceID": "app",
"Address": "192.168.1.12",
"Node": "app002"
}
]
最初は DNS interface を使って、以下のように nginx から app.service.consul の名前解決をして振り分けようとしました。
# nginx.conf
location / {
set $app "app.service.consul";
proxy_pass http://$app:5000;
}
が、以下の事情により DNS による振り分けは断念。
- Consul (v0.2.1) では DNS (UDP) でのアクセスでは、サービスに node が何台いても 3アドレスをランダムに返す
- Consul が TTL 0 で応答を返すが、nginx は1秒間は名前解決結果を cache する
- そのため、任意の1秒間では特定の 3 node にしか振り分けられない
- 4 node 以上ある場合は1秒ごとに全くアクセスが行かない node ができてしまう
ということで、Chef でテンプレートから生成している nginx.conf に Consul API から取得した service を渡す形にしました。
# nginx.conf.erb
upstream pc_backend {
<% node[:consul][:catalog][:service][:app].select{|n| n[:ServiceTags].include?("pc") }.each do |n| %>
server <%= n[:Address] %>:5000; # <%= n[:Node] %>
<% end %>
}
このテンプレートを上記の service 定義で展開すると以下のようになります。
# nginx.conf
upstream pc_backend {
server 192.168.1.11:5000; # app001
server 192.168.1.12:5000; # app002
}