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

# 概要
* 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](https://qiita-image-store.s3.amazonaws.com/0/29405/15a5df5b-ca55-0c41-ce3c-1ad0d6369182.png)

※ 各サーバの構築手順はこちら：[consulとconsul-templateでAPサーバの自動切り替え ~環境構築編~](http://qiita.com/sion_cojp/items/58ecf5acf63713930ef5)


|役割|IP|補足|
|:---|:---|:---|
|leader|serverのどれか|"bootstrap_expect": が付与されてるサーバから選出。<br>1台ずつserverを切り替えれば大丈夫。<br>リーダーが不在にならないようにエンジニア達は頑張ってるらしい。| 
|server|10.0.1.1(keepalived)<br>10.0.1.2(LB)<br>10.0.1.3(LB)|クライアントからイベント通知を受け取る。<br>serverを数台で構成して残りはそれに従うクライアントにしろと言われているらしい|
|client|10.0.1.4(AP)<br>10.0.1.5(AP)<br>10.0.1.6(sorry) |台数の増減、シャットダウンとかするやつらはこの部類。<br>能動的にこいつらがイベントを発射していく|
|VIP|10.1.1.1|LBのVIP|
|社内DNS|test1.example.com<br>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インストール

```bash
[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が見えるように設定
```bash
$ 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に向けてます

```json: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"
    }
  }
}
```

```json: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"
    }
  }
}
```

```json: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の名前には . （ピリオド）が使えません

```json: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"
    }
  }
}
```

```json: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"
    }
  }
}
```

```json: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アドレス<br>複数IPが割り当てられる場合もあるので、設定したほうが良いでしょう|
|addresses|登場しませんが、web_uiのアドレスです。<br>これを設定すると、localhost:8500が出来なくなる代わりに、10.x.x.x:8500でアクセス出来ます。<br>これを設定しなければ、cunsul execコマンドで全台にコマンド発行ができるようになります（あまり使わなそう） |
|server|serverモードで動作させるかどうか|
|start_join|consul起動時に所属させるクラスタ（members）|
|ui_dir|web_uiのディレクトリ|
|data_dir|consulのデータが入ってるディレクトリ|
|datacenter|DC名。デフォルトはdc1| 
|log_level|表示したいログレベル|
|bootstrap_expect|冗長構成で動作させるサーバ台数。<br>（N / 2 + 1）台という計算式。<br>consulでは3以上の奇数を推奨|
|dns_config|node:ttl ノードのキャッシュ。デフォルトは0<br>service_ttl  サービスのキャッシュ。デフォルトは0<br>dnsmasqと連携して、leaderが死んでもdns解決することが出来る|
|tag|DBのmaster/slaveの値を入れるのがベスト|
|ポート類|HTTP: 8500<br>HTTPS: -1<br>DNS: 8600<br>RPC: 8400|

# ⑤ consul起動、動作確認
* 本番では、systemctlやinitファイルを作って、やったほうがいいと思います。

```bash: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を試して見る

```bash:全台
### 今の状態だと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から見てみる

* http://consul.hoge.dev.jp/#/dc1/services/nginx
* にアクセスするとこんな感じにみえるでしょう

![スクリーンショット 2015-12-07 19.25.04.png](https://qiita-image-store.s3.amazonaws.com/0/29405/9423657d-f586-2b86-3a4a-15909b0ecab3.png)

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

![スクリーンショット 2015-12-07 19.24.46.png](https://qiita-image-store.s3.amazonaws.com/0/29405/f4976dcc-02e7-099d-a50f-c04c3a2bc37a.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用のファイルを作成

```json:LB2台とも
$ vim /etc/consul.d/watch.json
{
  "watches": [
    {
      "type": "service",
      "service": "test_hoge_dev_jp",
      "handler": "/bin/bash /tmp/check.sh"
    }
  ]
}
```

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

```bash: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の設定再読み込み

```bash:LB2台とも
$ consul reload
```

### UIから確認

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

![スクリーンショット 2015-12-10 17.09.58.png](https://qiita-image-store.s3.amazonaws.com/0/29405/fd811cf2-600a-bd4f-c110-768b54623942.png)

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

### consul-templateをgo getしてくる

```bash: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を作る

```bash: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設定

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

### sorry側のconsul設定
```bash
dev1006で、最初のほうに書いたconsul.confを設置
```

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

```bash: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起動

```bash
[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」と出る場合

* https://consul.io/docs/guides/outage.html<br>ここに書いてる通りにします

```bash
### 各サーバで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のバックアップはとるべし
* 全serverがconsulダウンすると回復不能なので。
* https://github.com/kailunshi/consul-backup

# 感想
* 本番運用の辛さはわかりませんが、今の所かなり便利です。<br>今回やった内容以外では
  * DB関連の動的切り替えやりたい
  * itamaeと連携してprovisioningしたい
  * mackerel自動登録したい
  * deployで利用（[strecher](https://github.com/fujiwara/stretcher) 試したい）
  * 障害対応の定期オペ　などなど
* と色々と自動化してみたいです！

# 参考資料
* この3つ押さえおけば。
  * [CONSUL DOCUMENTATION](https://www.consul.io/docs/)
  * [Consulと自作OSSを活用した100台規模のWebサービス運用](http://sssslide.com/speakerdeck.com/fujiwara3/consultozi-zuo-osswohuo-yong-sita100tai-gui-mo-falsewebsabisuyun-yong)
   * [Consul は 全自動オーケストレーションの 夢を見るか?](http://www.slideshare.net/udzura/consul-47144848)