シチュエーション
Consul serverが3台+Consul client数台という状態で、Consul 0.5.0から0.5.1あるいは0.5.2へアップグレードするのに https://www.consul.io/docs/upgrading.html の「Standard Upgrades」に従って1台ずつConsul Serverのconsulプロセスを再起動する、という作業をAnsibleを使って実施したところ、Consulは起動はしているようだがまともに動いていなかった。
例えばconsul exec hostname
を実行するとFailed to create session: Unexpected response code: 500 (No cluster leader)
というエラーメッセージが返ってきた。
0.5.1から0.5.2へのアップグレードを同じ方法で行うと問題は発生しなかった。
解説
どうやらleaderとなっているConsul serverが再起動されると再度leader選定が行われるが、選定が終わる前に別のConsul serverを再起動すると選定ができなくなってしまうようだ。
https://www.consul.io/docs/upgrade-specific.html にある通り、0.5.0から0.5.1以降にアップグレードするとデータ用DBのマイグレーションが発生する影響で普段のアップグレードよりleader選定に多少時間が掛かる。
そのため0.5.0から0.5.1以降へのアップグレードでは1台再起動したら即、次の1台を再起動する、ということをやっているとleader選定ができなくなってConsulクラスタ全体が死ぬ。
0.5.1から0.5.2へのアップグレードでは1台再起動したら即、次とやっていっても選定が間に合うので問題が発生しなかったと考えられる。
試していないがConsul serverが1台だけならleader選定もへったくれもないのでこの問題が発生する余地はないだろう。
Ansible playbookの修正
手作業でやっていれば自然に入っていたwaitが、Ansibleを使う場合のように処理の実行全体を自動化すると入らなかったのが原因、というわけで、アップグレードを行うAnsibleのplaybookにleader選定が完了するまで待つ処理を追加した。
秒数指定でも良かったのだけど5秒待ちでもアウトだったので余裕持たせると結構待たされるなあ、というのと秒数待ちだと簡単すぎてネタとして面白くなかったからやめた。
というわけで次が修正したConsulのダウンロード、展開、再起動を行うplaybookである。
# assign the following variables with --extra_vars etc.
# version: 0.5.2
# local_dest: /opt
# remote_dest: /usr/local/sbin
# consul_addr: http://localhost:8500
# consul_ui_unzip_dir: /var/lib/consul
- hosts: localhost
tasks:
- name: get official checksum
set_fact: checksum={{ item | regex_replace(' +.+$', '') }}
when: '"{{ version }}_linux_amd64.zip" in "{{ item }}"'
with_url: https://dl.bintray.com/mitchellh/consul/{{ version }}_SHA256SUMS?direct
- name: stat exec
stat: path={{ local_dest }}/{{ version }}_linux_amd64.zip get_checksum=no get_md5=no
register: exec
- name: get local checksum
set_fact: local_checksum={{ lookup('pipe', 'sha256sum ' + local_dest + '/' + version + '_linux_amd64.zip') | regex_replace(' +.+$', '') }}
when: exec.stat.exists
- name: download exec
get_url:
url: https://dl.bintray.com/mitchellh/consul/{{ version }}_linux_amd64.zip
dest: '{{ local_dest }}'
sha256sum: '{{ checksum }}'
when: '"{{ checksum }}" != "{{ local_checksum | default() }}"'
- name: stat ui
stat: path={{ local_dest }}/{{ version }}_web_ui.zip get_checksum=no get_md5=no
register: ui
- name: download ui
get_url:
url: https://dl.bintray.com/mitchellh/consul/{{ version }}_web_ui.zip
dest: '{{ local_dest }}'
when: not ui.stat.exists
- hosts: all
tasks:
- name: unzip and copy exec
unarchive: src={{ local_dest }}/{{ version }}_linux_amd64.zip dest={{ remote_dest }}
- hosts: consul-servers
serial: 1
tasks:
- name: install python-httplib2
yum: name=python-httplib2
tags: upgrade
- name: install ui
unarchive: src={{ local_dest }}/{{ version }}_web_ui.zip dest={{ consul_ui_unzip_dir }}
- name: restart consul
service: name=consul state=restarted
- name: check leader
uri: url={{ consul_addr }}/v1/status/leader
register: check
until: "{{ check.status | default(500) }} == 200 and {{ check.json | default('') | length }} > 0"
retries: 10
delay: 2
tags: upgrade
- hosts: all:!consul-servers
tasks:
- name: restart consul
service: name=consul state=restarted
この件で追加したのはconsul-serversに対するplayの中のtags: upgradeになっている2つのtaskである。
- leader選定が終わっているかどうかは
http://localhost:8500/v1/status/leader
に聞くのが良さそうだったのでuriモジュールで問い合わせを行っている。 - uriモジュールの使用にはpython-httplib2をインストールする必要があるのでそのインストール。
使うにはplaybookの最初にコメントで書いたように-e(--extra_vars)で5つの変数を指定して実行する。
# ansible-playbook -i hosts consul_install.yml -e 'version=0.5.2 local_dest=/opt remote_dest=/usr/local/sbin consul_addr=http://localhost:8500 consul_ui_unzip_dir=/var/lib/consul'
まあversion以外はいつも一緒だろうからこのplaybookをincludeするだけのplaybookを作って、
- include: consul_install.yml local_dest=/opt remote_dest=/usr/local/sbin consul_addr=http://localhost:8500 consul_ui_unzip_dir=/var/lib/consul
こう実行するなどした方がすっきりしてて良いだろう。
# ansible-playbook -i hosts consul_install_include.yml -e 'version=0.5.2'
ちなみに--skip-tags upgradeをオプションに指定すればアップグレードではなくインストールに使用可能である。
Consulのサービス化にはconsulのinitスクリプトとsystemdユニットファイルを書いてみたを使用している。
なお
冒頭のFailed to create session: Unexpected response code: 500 (No cluster leader)
状態からのまともな復旧のさせ方はわからなかったので、停止してConsulのデータディレクトリを削除してもう一度起動した。
要は諦めて初期化したというわけである。