ALB配下のEC2を半分ずつ切り離しながらシェル一撃でデプロイする


やりたいこと

ALBのTarget Group配下に複数のEC2インスタンスが登録されている。

インプレースなデプロイなど、ユーザに影響を与えないように半分ずつ切り離しながら処理したいことがある。
これをシェルを使って一撃で実施したい。(jenkinsで1クリックのイメージ)

処理の流れ

AWS CLIを使って、Target Groupから半分ずつ 切り離し -> デプロイ -> 再登録 を2度繰り返す。Target Groupは以下の通りステータス遷移するため、それぞれ待機する。
参考: ターゲットヘルスステータス

  • deregister
    • healthy → draining → unused
  • register
    • unused → initial → healthy

下準備

  • 作業マシンにAWS CLIをインストール/セットアップ
  • Target Groupのヘルスチェック閾値、登録遅延タイムアウト設定値の見直し
    • デフォルト時間のままだと時間が長く、切り離しや再登録に時間がかかるので適切な値に変更(参考)
  • EC2インスタンスにグループタグを付与

ステップ

  1. Aグループ切り離し(unusedまで待機)
  2. Aグループデプロイ
  3. Aグループ再登録(healthyまで待機)
  4. Bグループ切り離し(unusedまで待機)
  5. Bグループデプロイ
  6. Bグループ再登録(healthyまで待機)

シェルの内容

target_group_arn="my_target_group_arn"
group_a_instance_ids=$(aws ec2 describe-instances --filter "Name=tag:deploy_group,Values=A" --query "Reservations[].Instances[].InstanceId" --output text)
group_b_instance_ids=$(aws ec2 describe-instances --filter "Name=tag:deploy_group,Values=B" --query "Reservations[].Instances[].InstanceId" --output text)

alb_waiter_function() {
  while :
  do
    state=$(aws elbv2 describe-target-health --target-group-arn $1 --targets Id=$2 --query "TargetHealthDescriptions[].TargetHealth.State" --output text)
    if [ "$3" = "${state}" ]; then
      echo "InstanceId: $2 State:$3 wait break!"
      break
    else
      echo "InstanceId: $2 State:$state keep waiting..."
    fi
    sleep 10
  done
}

# 1. Aグループ切り離し
echo -n ${group_a_instance_ids} | xargs -I {} -P 0 -d\\t  aws elbv2 deregister-targets --target-group-arn ${target_group_arn} --targets Id={}
for instance_id in $(echo ${group_a_instance_ids}); do alb_waiter_function ${target_group_arn} ${instance_id} 'unused'; done

# 2. Aグループデプロイ実行 (tagかinstance_idを利用。例としてansibleを実行)
ansible-playbook -i ./inventory/ec2.py --limit "tag_deploy_group_A" -u foo --private-key='~/.ssh/hoge.pem' deploy.yml

# 3. Aグループ再登録
echo -n ${group_a_instance_ids} | xargs -I {} -P 0 -d\\t  aws elbv2 register-targets --target-group-arn ${target_group_arn} --targets Id={}
for instance_id in $(echo ${group_a_instance_ids}); do alb_waiter_function ${target_group_arn} ${instance_id} 'healthy'; done

# 4. Bグループ切り離し
echo -n ${group_b_instance_ids} | xargs -I {} -P 0 -d\\t aws elbv2 deregister-targets --target-group-arn ${target_group_arn} --targets Id={}
for instance_id in $(echo ${group_b_instance_ids}); do alb_waiter_function ${target_group_arn} ${instance_id} 'unused'; done

# 5. Bグループデプロイ実行
ansible-playbook -i ./inventory/ec2.py --limit "tag_deploy_group_B" -u foo --private-key='~/.ssh/hoge.pem' deploy.yml

# 6. Bグループ再登録
echo -n ${group_b_instance_ids} | xargs -I {} -P 0 -d\\t  aws elbv2 register-targets --target-group-arn ${target_group_arn} --targets Id={}
for instance_id in $(echo ${group_b_instance_ids}); do alb_waiter_function ${target_group_arn} ${instance_id} 'healthy'; done

追記: 2019/11/20

この記事を書いた翌日にこんな機能が出た

Application Load Balancer simplifies deployments with support for weighted target groups