自動化
consul
consul-template
HashiCorpDay 11

consulとconsul-templateでAPサーバの自動切り替え

More than 3 years have passed since last update.


概要


  • Consulとは: サービス検出、障害検知とか。

  • Consul-templateとは: 障害を検知した時、用意されたテンプレートに

  • ようするに、障害対応など色々と自動化してくれます。それを両方触ってみたという内容です。


やったこと


  • APサーバがconsulクラスタに追加されたら、nginxのconfファイルに動的追加してreload

  • APサーバがconsulクラスタからleave、もしくはサービスダウンしてたら、nginxのconfファイルから削除してreload

  • APサーバが全部shutdownされたら、nginxのconfファイルにsorryサーバだけに記述してreload


構成

スクリーンショット 2015-12-10 18.05.33.png

※ 各サーバの構築手順はこちら:consulとconsul-templateでAPサーバの自動切り替え ~環境構築編~

役割
IP
補足

leader
serverのどれか
"bootstrap_expect": が付与されてるサーバから選出。
1台ずつserverを切り替えれば大丈夫。
リーダーが不在にならないようにエンジニア達は頑張ってるらしい。

server
10.0.1.1(keepalived)
10.0.1.2(LB)
10.0.1.3(LB)
クライアントからイベント通知を受け取る。
serverを数台で構成して残りはそれに従うクライアントにしろと言われているらしい

client
10.0.1.4(AP)
10.0.1.5(AP)
10.0.1.6(sorry)
台数の増減、シャットダウンとかするやつらはこの部類。
能動的にこいつらがイベントを発射していく

VIP
10.1.1.1
LBのVIP

社内DNS
test1.example.com
test2.example.com

AP用ドメイン
test.hoge.dev.jp A "10.1.1.1"

consulのUI用ドメイン
consul.hoge.dev.jp A "10.1.1.1"


① Consulインストール

[3台で実施。すべてroot]

$ cd /tmp
$ yum install -y jq
$ wget -O 0.5.2_linux_amd64.zip https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip
$ unzip 0.5.2_linux_amd64.zip
$ mv consul /usr/local/bin/

### consulの作業ディレクトリを掘る
$ mkdir -p /var/consul/{data,web_ui} /etc/consul.d
# /var/consul/data: consulのデータが格納
# /var/consul/web_ui: web_ui用
# /etc/consul.d: 設定ファイル

$ cd /var/consul
$ wget --no-check-certificate https://releases.hashicorp.com/consul/0.5.2/consul_0.5.2_web_ui.zip
$ unzip consul_0.5.2_web_ui.zip
$ mv dist/* web_ui/
$ rm -rf consul_0.5.2_web_ui.zip dist


② GOMAXPROCの設定、nginxでUIが見えるように設定

$ vim /etc/sysconfig/consul

GOMAXPROCS=1
# 今回は1CPUなので。本番に入れる時は"1"はやめたほうが良いらしい。

### nginxでconsulのuiが見えるようにする
$ vim /etc/nginx/conf.d/consul.conf
server {
listen 80;
server_name consul.hoge.dev.jp;
root /var/consul/web_ui;
index index.html index.htm;
location /ui {
try_files $uri $uri/ =404;
}
# Forward consul API requests
location /v1 {
proxy_pass http://127.0.0.1:8500;
}
}

$ service nginx reload


③ server群の設定


  • recursorは社内DNSに向けてます


dev1001-keepalived

$ sudo vim /etc/consul.d/consul.json

{
"node_name":"dev1001",
"datacenter":"dc1",
"data_dir":"/var/consul/data",
"ui_dir":"/var/consul/web_ui",
"start_join": [ "10.0.1.1", "10.0.1.2", "10.0.1.3" ],
"server":true,
"bootstrap_expect":3,
"bind_addr": "10.0.1.1",
"log_level":"INFO",
"recursors": [ "test1.example.com", "test2.example.com" ],
"dns_config":{
"allow_stale": true,
"max_stale": "10s"
},
"service":{
"ID":"keepalived",
"name":"keepalived",
"tag":"master",
"port":80,
"check":{
"script":"service keepalived status >/dev/null 2>&1",
"interval":"10s"
}
}
}


dev1002-LB

$ sudo vim /etc/consul.d/consul.json

{
"node_name":"dev1002",
"datacenter":"dc1",
"data_dir":"/var/consul/data",
"ui_dir":"/var/consul/web_ui",
"start_join": [ "10.0.1.1", "10.0.1.2", "10.0.1.3" ],
"bootstrap_expect":3,
"log_level":"INFO",
"bind_addr": "10.0.1.2",
"server":true,
"recursors": [ "test1.example.com", "test2.example.com" ],
"dns_config":{
"allow_stale": true,
"max_stale": "10s"
},
"service":{
"name":"nginx",
"ID":"lb",
"tag":"lb",
"port":80,
"check":{
"script":"curl localhost:80 >/dev/null 2>&1",
"interval":"10s"
}
}
}


dev1003-LB

$ sudo vim /etc/consul.d/consul.json

{
"node_name":"dev1003",
"datacenter":"dc1",
"data_dir":"/var/consul/data",
"ui_dir":"/var/consul/web_ui",
"start_join": [ "10.0.1.1", "10.0.1.2", "10.0.1.3" ],
"bootstrap_expect":3,
"log_level":"INFO",
"bind_addr": "10.0.1.3",
"server":true,
"recursors": [ "test1.example.com", "test2.example.com" ],
"dns_config":{
"allow_stale": true,
"max_stale": "10s"
},
"service":{
"name":"nginx",
"ID":"lb",
"tag":"lb",
"port":80,
"check":{
"script":"curl localhost:80 >/dev/null 2>&1",
"interval":"10s"
}
}
}


④client群の設定


  • 拡張子はなんでも良いです

  • nameやtagsの名前には . (ピリオド)が使えません


dev1004-AP

$ sudo vim /etc/consul.d/consul.json

{
"node_name":"dev1004",
"log_level":"INFO",
"bind_addr":"10.0.1.4",
"start_join": [ "10.0.1.1", "10.0.1.2", "10.0.1.3" ],
"datacenter":"dc1",
"data_dir":"/var/consul/data",
"service":{
"id":"web",
"tag":"web",
"name":"test_hoge_dev_jp",
"port":80,
"check":{
"script":"curl localhost:80 >/dev/null 2>&1",
"interval":"10s"
}
}
}


dev1005-AP

$ sudo vim /etc/consul.d/consul.json

{
"node_name":"dev1004",
"log_level":"INFO",
"bind_addr":"10.0.1.5",
"start_join": [ "10.0.1.1", "10.0.1.2", "10.0.1.3" ],
"datacenter":"dc1",
"data_dir":"/var/consul/data",
"service":{
"id":"web",
"tag":"web",
"name":"test_hoge_dev_jp",
"port":80,
"check":{
"script":"curl localhost:80 >/dev/null 2>&1",
"interval":"10s"
}
}
}


dev1006-Sorry

$ sudo vim /etc/consul.d/consul.json

{
"node_name":"dev1006",
"log_level":"INFO",
"bind_addr":"10.0.1.6",
"start_join": [ "10.0.1.1", "10.0.1.2", "10.0.1.3" ],
"datacenter":"dc1",
"data_dir":"/var/consul/data",
"service":{
"id":"web",
"tag":"web",
"name":"test_hoge_dev_jp",
"port":80,
"check":{
"script":"curl localhost:80 >/dev/null 2>&1",
"interval":"10s"
}
}
}


各項目の説明

項目
説明

node_name
エージェントを動作させるノードにつけるノード名

bind_addr
エージェントを動作させるノードのIPアドレス
複数IPが割り当てられる場合もあるので、設定したほうが良いでしょう

addresses
登場しませんが、web_uiのアドレスです。
これを設定すると、localhost:8500が出来なくなる代わりに、10.x.x.x:8500でアクセス出来ます。
これを設定しなければ、cunsul execコマンドで全台にコマンド発行ができるようになります(あまり使わなそう)

server
serverモードで動作させるかどうか

start_join
consul起動時に所属させるクラスタ(members)

ui_dir
web_uiのディレクトリ

data_dir
consulのデータが入ってるディレクトリ

datacenter
DC名。デフォルトはdc1

log_level
表示したいログレベル

bootstrap_expect
冗長構成で動作させるサーバ台数。
(N / 2 + 1)台という計算式。
consulでは3以上の奇数を推奨

dns_config
node:ttl ノードのキャッシュ。デフォルトは0
service_ttl サービスのキャッシュ。デフォルトは0
dnsmasqと連携して、leaderが死んでもdns解決することが出来る

tag
DBのmaster/slaveの値を入れるのがベスト

ポート類
HTTP: 8500
HTTPS: -1
DNS: 8600
RPC: 8400


⑤ consul起動、動作確認


  • 本番では、systemctlやinitファイルを作って、やったほうがいいと思います。


serverのみ

$ cd /var/consul

### 起動
$ nohup consul agent --config-dir="/etc/consul.d" >> /var/log/consul.log &

### 確認
$ consul members

### 離脱。リーダが離脱すると勝手に新しいリーダを決めてくれる
$ consul leave

### いざとなったとき、無理やりconsulを落とすなら
# $ ps aux | grep "[c]onsul" | awk '{print $2}' | xargs kill -9

### ノード名.node.consul でdnsとしても使える
$ dig +short @127.0.0.1 -p 8600 dev1001.node.consul
10.0.1.1

### サービス名.service.consul でもdnsとして使える
$ dig +short @127.0.0.1 -p 8600 nginx.service.consul
10.0.1.2
10.0.1.3

### タグ名.サービス名.service.consul でもdnsとして使える
$ dig +short @127.0.0.1 -p 8600 lb.nginx.service.consul
10.0.1.2
10.0.1.3

### 外部のDNSも登録できて引ける。ただしこれやるとnodeにも追加されて、「consul agentがないのにnodeとして見えて」気持ち悪い感じ
$ curl -X PUT -d '{
"Node":"vagrant",
"Address":"10.0.1.100",
"Service":{"Service":"vagrant"}
}'
-s localhost:8500/v1/catalog/register

### digも引ける
$ dig +short @127.0.0.1 -p 8600 vagrant.service.consul

### ちなみに削除するなら
$ curl -X DELETE -d '{"Node":"vagrant"}' localhost:8500/v1/catalog/deregister



⑥ dnsmasqを入れてpingを試して見る


全台

### 今の状態だとpingが飛ばない

$ ping keepalived.service.consul
ping: unknown host keepalived.service.consul

### dnqmasqインストール、設定
$ yum install -y dnsmasq
$ vim /etc/dnsmasq.conf
server=/consul/127.0.0.1#8600
bind-interfaces
listen-address=127.0.0.1

### resolv設定
$ vim /etc/resolv.conf
search node.consul service.consul
nameserver 127.0.0.1

$ service dnsmasq start
$ ping keepalived.service.consul
PING keepalived.service.consul (10.0.1.1) 56(84) bytes of data.
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.271 ms
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.191 ms



⑦ web_uiから見てみる

スクリーンショット 2015-12-07 19.25.04.png


  • 試しにnginxをstopさせてみると、ちゃんとエラー表示になる

スクリーンショット 2015-12-07 19.24.46.png


⑧ consul wachでAPサーバがメンテモードかどうかを確認する


  • consulのAPIを使って、下記を実施してみます。

  • APのどちらかがpassing(稼働)してたら、web/test.hoge.dev.jp/maintenanceに、key:"off" を入れる

  • AP2台両方criticalになったら、web/test.hoge.dev.jp/maintenanceに、key:"on" を入れる


watch用のファイルを作成


LB2台とも

$ vim /etc/consul.d/watch.json

{
"watches": [
{
"type": "service",
"service": "test_hoge_dev_jp",
"handler": "/bin/bash /tmp/check.sh"
}
]
}


死活監視用のスクリプト作成


LB2台とも

vim /tmp/check.sh

#!/bin/bash -
curl -s localhost:8500/v1/health/checks/test_hoge_dev_jp | jq '.[] | select(.ServiceID=="web" and .Status == "passing")' | grep "passing" > /dev/null 2>&1
if [[ $? -eq "1" ]]; then
curl -X PUT -d "on" localhost:8500/v1/kv/web/test.hoge.dev.jp/maintenance/key
else
curl -X PUT -d "off" localhost:8500/v1/kv/web/test.hoge.dev.jp/maintenance/key
fi


consulの設定再読み込み


LB2台とも

$ consul reload



UIから確認


  • UIから2台のnginxサーバをstopするとonになり、それ以外はoffになることが確認出来ます

スクリーンショット 2015-12-10 17.09.58.png


⑨ consul-templateで maintenance/key:on だったらsorryサーバに切り替えてみる


consul-templateをgo getしてくる


Mac

### サーバにgoインストールするのがめんどくさかったので、macでクロスコンパイルしてrsyncしてやりました

$ cd $GOPATH
$ go get github.com/hashicorp/consul-template
$ cd src/github.com/hashicorp/consul-template
$ GOOS=linux GOARCH=amd64 go build -o /tmp/consul-template .

あとはrsync
$ mv /tmp/consul-template /usr/local/bin/

### consul-template用のディレクトリ作成
$ mkdir -p /etc/consul-template/{conf.d,param.d,template.d}
# conf.d: consul-templateの設定ファイル
# param.d: consul kvに入れるJSONの元データYAML
# template.d: 動的に変化させるテンプレートファイル



動的に変化させるnginxの設定ファイルのtemplateを作る


LB2台とも

# 今回は動的にnginxのバランサーに登録するのを試す

# 拡張子はなんでも良い
# goのtext/templateパッケージの記法にそって書く。
$ vim /etc/consul-template/template.d/default.conf.tmpl
upstream app1 {
{{ range service "web.test_hoge_dev_jp@dc1" "passing" }}
server {{.Address}}:{{.Port}} weight=5;
{{ end }}
{{ range "web/test.hoge.dev.jp/maintenance" | ls }}
{{ if eq .Value "off" }}
{{ range service "sorry.test_hoge_dev_jp@dc1" "passing" }}
server {{.Address}}:{{.Port}} backup;{{end}}
{{ else }}
{{ range service "sorry.test_hoge_dev_jp@dc1" "passing" }}
server {{.Address}}:{{.Port}} weight=5;
{{end}}
{{ end }}
{{ end }}
}
server {
listen 80;
listen 443;
server_name server_name test.hoge.dev.jp;
proxy_set_header Host $host;
satisfy any;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://app1;
}
}


AP側のconsul設定

dev1004 1005で、最初のほうに書いたconsul.confを設置


sorry側のconsul設定

dev1006で、最初のほうに書いたconsul.confを設置


イベントを受け取った時にどういう動きをさせるか、consul-templateファイルを作成


LB2台

$ vim /etc/consul-template/nginx-template.conf

consul = "127.0.0.1:8500"
retry = "5s"
max_stale = "10m"
log_level = "info"
pid_file = "/var/run/consul-template.pid"
template {
source = "/etc/consul-template/template.d/default.conf.tmpl"
destination = "/etc/nginx/conf.d/default.conf"
command = "service nginx reload"
}


LBでconsul-template起動、AP,sorryでconsul起動

[LB2台]

$ nohup consul-template -config="/etc/consul-template/conf.d/nginx-template.conf" >> /var/log/consul-template.log &

[AP2台、sorry1台]
$ nohup consul agent --config-dir=/etc/consul.d >> /var/log/consul.log &

$ http://test.hoge.dev.jp/

APを追加すれば動的にdefault.confに追加されるでしょう
全APがleaveしたり、service nginx stopされたら、sorryサーバにバランシングされるでしょう


障害対応tipsなど


:cold_sweat: 何度consulを起動しなおしても「agent: failed to sync remote state: No cluster leader」と出る場合

### 各サーバでleave

$ consul leave

### peers.jsonを書き換える
$ vim /var/consul/data/raft/peers.json
[
"10.0.1.1:8300",
"10.0.1.2:8300",
"10.0.1.3:8300"
]

### consulを起動し直す


:cold_sweat: リーダーが死ぬとどうなるの?


  • リーダーが再選出されるまでDNS, HTTP疎通不可

  • 大体数3秒くらい

  • その間でもDNS解決させたい場合は、stale modeを使う

  • 一気に全server落ちるとクラスタ構成がクリアされます


:cold_sweat: key/valueのバックアップはとるべし


感想


  • 本番運用の辛さはわかりませんが、今の所かなり便利です。
    今回やった内容以外では


    • DB関連の動的切り替えやりたい

    • itamaeと連携してprovisioningしたい

    • mackerel自動登録したい

    • deployで利用(strecher 試したい)

    • 障害対応の定期オペ などなど



  • と色々と自動化してみたいです!


参考資料