こんにちは。インフラの奥村です。
本日は、AnsibleとShellでALBのTargetGroupに登録されているインスタンスのWebサーバのデーモンを再起動させる記事です。
前提
今回の記事の概要としては、
ALBのターゲットグループに登録されたインスタンスで動作しているWebサーバーを
- グレイスフルにターゲットグループから外し
- 特定のコマンドを実行し、
- 起動が確認できたら再度ターゲットグループに登録する
というのをAnsible + Shellで行います。
Shellで行う部分
- ターゲットグループの操作
- 特定のコマンドの実行
- 起動確認
Ansibleで行う部分
- 対象のサーバーへ逐次実行
使うもの
- Ansible
- Bash
- awscli
- jq
対象
- ALB
- ターゲットグループのarnが必要
- Webサーバー(Appサーバー)
事前準備
- ALBのリスナーにターゲットグループを登録しておく
- 登録したターゲットグループのarnを取得しておく
- ターゲットグループに登録されたインスタンスにALBの操作権限のあるIAMロールを与えておく
記事の例では再起動の対象をunicornにしています。
本題
Shell
まず単体で動作するシェルスクリプトを作成します。
それがこちらです。
動作がわかりやすいように、実行時にメッセージを出力するようにしていますが、動作には必要ありません。
tg_restart_unicorn.sh
#!/usr/bin/bash set -euC target_group_arn=$1 rails_env=$2 target_group_name=$(echo "$target_group_arn" | awk -F":" '{print $6}' | awk -F"/" '{print $2}') my_instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id/) unicorn_pid=$(cat /var/run/amc/unicorn.pid) unicorn_port=8080 app_dir=/var/www/sample # ターゲットグループから登録を解除 aws elbv2 deregister-targets --target-group-arn "$target_group_arn" --targets Id="$my_instance_id" echo "[message] deregistered $my_instance_id from $target_group_name" # unicronを再起動 echo "[message] restart unicorn" kill -QUIT "$unicorn_pid" \ && cd "$app_dir" \ && bash -lc "RAILS_ENV=$rails_env bundle exec unicorn -c config/unicorn.rb -p $unicorn_port -D" \ && echo "[message] complete restart unicorn" # 起動を確認できるまで無限ループ while true do status_code=$(curl -s 127.0.0.1:8080 -w "%{http_code}\n" -o /dev/null) echo "[message] unicorn satetu code: $status_code" if [[ "$status_code" == 302 || "$status_code" == 200 ]];then # 起動が確認できたら再度ターゲットグループに登録 aws elbv2 register-targets --target-group-arn "$target_group_arn" --targets Id="$my_instance_id" echo "[message] register start $my_instance_id to $target_group_name " break fi sleep 1 done # ターゲットグループのヘルスチェックが通るまで無限ループ while true do target_group_info=$(aws elbv2 describe-target-health --target-group-arn "$target_group_arn") target_instance_info=$(echo "$target_group_info" | jq ".TargetHealthDescriptions[] | select(.Target.Id == \"$my_instance_id\")") # ターゲットのインスタンスのヘルスチェックの状態を取得 target_instance_state=$(echo "$target_instance_info" | jq -r .TargetHealth.State) echo "[message] instance state: $target_instance_state" if [[ "$target_instance_state" == "healthy" ]];then echo "[message] complete register $my_instance_id to $target_group_name" exit 0 fi sleep 1 done
呼び出し
./roling_restart_unicorn.sh <ターゲットグループのarn> development #今回はunicornを例にしているのでRAILS_ENV(development)を含めています
実行結果
[message] deregistered i-xxxxxxxxxxx(インスタンスID) from sample-tg(ターゲットグループネーム) [message] restart unicorn [message] complete restart unicorn [message] unicorn satetu code: 302 [message] register start i-xxxxxxxxxxx to sample-tg [message] instance state: healthy [message] complete register i-xxxxxxxxxxx to sample-tg
- ターゲットグループの操作
- 特定のコマンドの実行
- 起動確認
を実行できるシェルスクリプトができました。
Ansible
作成したシェルスクリプトを逐次実行させるAnsibleを作成します。
グループに所属するホストに順番に行う方法として、serial という方法がありますが、task単位では実行できません。
いろんな方がワークアラウンドを実現していますが、私はこちらの記事を参考にさせてもらいました。
unicornというroleを作成し、その中に2つのplaybookを配置します。
varsファイルにも二つの変数を定義します。
対象のホストは「10.1.1.1」「10.1.1.2」の2台とします。
※ ディレクトリ構成, インベントリファイルについては省略させていただきます。
roles/unicorn/tasks/main.yml
--- - name: debug ansible_play_hosts debug: var: ansible_play_hosts - name: defined unicorn_pid_file variable stat: path: "{{ unicorn.pid_file }}" register: unicorn_pid_file - name: restart unicorn process include_tasks: unicorn_restart.yml delegate_to: "{{ item }}" run_once: yes with_items: "{{ ansible_play_hosts }}" - name: start unicorn become: no shell: bash -lc "RAILS_ENV={{ rails.env }} bundle exec unicorn -c config/unicorn.rb -p {{ unicorn.port }} -D" args: chdir: "{{ rails.app_dir }}" when: not unicorn_pid_file.stat.exists
roles/unicorn/tasks/unicorn_restart.yml
--- - name: copy tg_restart_unicorn.sh become: no copy: src: tg_restart_unicorn.sh dest: /tmp mode: 0755 - name: execute tg_restart_unicorn become: no shell: bash -lc "/tmp/tg_restart_unicorn.sh {{ alb.target_group.arn }} {{ rails.env }}"
group_vars/development.yml
--- alb: target_group: arn: "ターゲットグループのarn。シェルの引数として与える。" rails: dir: /var/www/sample env: development
こうした場合、「delegate_to: "{{ item }}"」で対象のホストを変更できてはいるのですが、playbookの実行結果が以下のようになります。
TASK [rails : copy roling_restart_unicorn.sh ********************************************************************************************************** ok: [10.1.1.1] TASK [rails : execute roling_restart_unicorn ********************************************************************************************************** changed: [10.1.1.1] TASK [rails : copy roling_restart_unicorn.sh ********************************************************************************************************** ok: [10.1.1.1] TASK [rails : execute roling_restart_unicorn ********************************************************************************************************** changed: [10.1.1.1]
すべてのtaskが単一ホスト上で実行されているように見えますね。
run_onceを無しにすると、ホスト数分ループが行われます。
ansible_play_host
の変数がホスト数分存在するためですね。
delegate_toの対象ホストがplaybookの実行結果から確認できるように、loop_controle を利用します。
loop_contrl
を追加したバージョンがこちらです。
roles/unicorn/tasks/main.yml
--- - name: debug ansible_play_hosts debug: var: ansible_play_hosts - name: defined unicorn_pid_file variable stat: path: "{{ unicorn.pid_file }}" register: unicorn_pid_file - name: restart unicorn process include_tasks: unicorn_restart.yml # loop_controlで登録した変数をターゲットにする delegate_to: "{{ target_host }}" run_once: yes with_items: "{{ ansible_play_hosts }}" # loop_controlを追加 loop_control: loop_var: target_host - name: start unicorn become: no shell: bash -lc "RAILS_ENV={{ rails.env }} bundle exec unicorn -c config/unicorn.rb -p {{ unicorn.port }} -D" args: chdir: "{{ rails.app_dir }}" when: not unicorn_pid_file.stat.exists
roles/unicorn/tasks/unicorn_restart.yml
--- - name: copy tg_restart_unicorn.sh {{ target_host }} become: no copy: src: tg_restart_unicorn.sh dest: /tmp mode: 0755 - name: execute tg_restart_unicorn {{ target_host }} become: no shell: bash -lc "/tmp/tg_restart_unicorn.sh {{ alb.target_group.arn }} {{ rails.env }}"
実行結果はこうなります。
TASK [rails : copy roling_restart_unicorn.sh 10.1.1.1] ******************************************************************************************************************* ok: [10.1.1.1] TASK [rails : execute roling_restart_unicorn 10.1.1.1] ******************************************************************************************************************* changed: [10.1.1.1] TASK [rails : copy roling_restart_unicorn.sh 10.1.1.2] ****************************************************************************************************************** ok: [10.1.1.1] TASK [rails : execute roling_restart_unicorn 10.1.1.2] ****************************************************************************************************************** changed: [10.1.1.1]
delegate_toの対象が表示されるようになりました。
まとめ
Ansible + Shell でALBを含めたローリングデーモンリスタートを実現できました!
Ansibleで実現するには複雑 + 比較的シンプルな処理 は 積極的にシェルスクリプトを用いることにしています!
悩む時間が少なく、そこまでメンテンナンスの負荷が上がるわけでもないので個人的にはおすすめです。
最後までご覧いただきありがとうございまいた。