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

  • 46
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

概要

  • 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 試したい)
    • 障害対応の定期オペ などなど
  • と色々と自動化してみたいです!

参考資料

この投稿は HashiCorp Advent Calendar 201511日目の記事です。