ApplibotのBlue-Green Deployment(AWS編)

こんにちは。インフラエンジニアの西村です。

弊社の運用タイトルでは主にAmazon Web Service(以下AWS)を使用していますが、
Google Cloud Platform(以下GCP)も今後利用すべく検証を重ねています。

今回は各VM(AWS:EC2,GCP:GCE)を使用した、Blue-Green Deploymentの実装について、2回に分けて書かせていただきます。

ApplibotでのBlue-Green Deployment

前述の通り、ApplibotではAWSとGCPを使用しております。
現在運用中のタイトルではAWSが主体ですので、今回はそちらの導入の経緯を交えて紹介させていただきます。

なぜBlue-Green Deploymentにしたか

Applibotのこれまでのデプロイ方式はサーバ内でtomcatをポートを分けて起動させ、
apacheでルーティングを切り替えるという擬似的なBlue-Green deploymentでした。

こうすることで、(ほぼ)無停止で入れ替えができますが、下記の問題点がありました。
– 切り替え状況のわかりづらさ
– 瞬間的にアプリケーションが2プロセス起動する為、メモリを確保する為のヒープ/EC2インスタンスクラス調整
– ロールバックに時間がかかる(旧系のアプリケーションを再度起動させ、接続できるまで待機する必要がある為)

各所にログを仕込んだり、失敗時の通知は飛ばすようにしてましたが、直感的に入れ替わりがわかりづらい状況でした。
元々AutoScalingは使用しており、起動時にアプリケーションのデプロイが完了した状態でLBへ接続する処理はできていたので、
新規アプリケーションのリリースのタイミングで、アプリチームと共に動作検証を行いながら作り上げていきました。

デプロイの流れ

横version_集約plus (1).png
※今回はサーバの動きに焦点を当てている為、コードビルドについて省いています。
 実際はJenkinsでコードビルドを行い、warファイルをS3へupload、EC2起動時にS3からDLするという形で、
 新規サーバへのコードデプロイが行われています。

  1. 確認用(standby)環境で最新バージョンがデプロイされたサーバを起動
  2. standby側へアクセスし動作確認
  3. 本番用(active)と確認用(stanby)のAutoScalingGroupを入れ替える
  4. 旧バージョンのEC2インスタンスを削除

これらの実行はJenkinsから行えるようにしており、EC2は起動と同時にデプロイまで完了となるように設定しています。
必要なログはtd-agent経由で別サーバへ集約することで、入れ替わりでサーバを削除してもログの消失がないようにしています。
手順詳細は次項にさせていただきます。

AWSでのBlue-Green Deployment詳細

手順についてaws-cliを使用した例と共に解説させていただきます。
以下の設定、サービスを使用しています。

構成要素

サービス名 役割 必要数 備考
ApplicationLoadBalancer ロードバランサー 2台 本番・確認用の2つが存在。到達先はリスナーでTargetGroupを指定
TargetGroup リクエストを受けるサーバグループ 2グループ リスナーとの紐付けは固定
AutoScalingGroup サーバ(EC2)の起動管理 2グループ どのターゲットグループに属するかを設定
EC2 サーバ n台(アプリによる) 起動時に最新のソースが展開されている状態となるよう設定済み

横version (2).png

図の通り、実際に入れ替え操作を行なっているのはAutoScalingGroupに設定するTargetGroupの登録先のみになります。

実行手順

実際の流れをコマンドと共に解説させていただきます。

1. Standby ALBに紐付くAutoScalingGroupでサーバ起動

横version_up.png

目的
新バージョンのコードがデプロイされたサーバをStandby ALB経由でアクセスできるようにする。

awscli+jqでのコマンド例

# 設定
# ASG = AutoScalingGroup
# TG = TargetGroup
ASG_NAME_BLUE=”autoscalinggroup名1(図のASG:Blue)”
ASG_NAME_GREEN=”autoscalinggroup名2(図のASG:Green)”
NUM=”起動台数”

# autoscaling groupのtargetgroupがactiveか調査
## TargetGroupのARN(AmazonResourceName)を取得
ASG_BLUE_TG_ARN=`aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names ${ASG_NAME_BLUE} | \
    jq '.AutoScalingGroups[].TargetGroupARNs[]'`

## TargetGroupのARNからActive/Standbyを判断
case ${ASG_BLUE_TG_ARN} in
    *active* )
        # BLUEのTGがactiveであればSTANDBYとしてGREENを設定
        STANDBY_ASG_NAME="${ASG_NAME_GREEN}"
        ;;
    *standby* )
        # BLUEのTGがstandbyであればSTANDBYとしてBLUEを設定
        STANDBY_ASG_NAME="${ASG_NAME_BLUE}"
        ;;
    * )
        echo "TargetGroupの設定が取れていない可能性があります。確認してください"
        exit 1
        ;;
esac

# Standby側のAutoScalingGroupでEC2 instance起動
aws autoscaling set-desired-capacity \
    --auto-scaling-group-name ${STANDBY_ASG_NAME} \
    --desired-capacity ${NUM}

2. 起動後、確認用環境へアクセスチェック

check.png
テスト用端末から、Standby ALBへアクセスし、新規追加部分での不具合がないか最終確認。

3. Standby側⇄Active側のサーバ切り替え

横version_move (3).png

ここでBlueとGreenを切り替えます。
最終的にBlueをTargetGroup:Standbyへ紐づけていますが、こちらは次回操作の為となります。
Rollbackの際は再度この部分を実行するだけです。

注意点

この時、ダウンタイムが発生しないよう新旧のバージョンが1分程混ざることを許容しています。

awscli+jqでのコマンド例

# 設定
# ASG = AutoScalingGroup
## STANDBY_ASG_NAME=”TG:Standbyに登録しているASG名(図のASG:Green)”
## ACTIVE_ASG_NAME=”TG:Activeに登録しているASG名(図のASG:Blue)”
ASG_NAME_BLUE=”autoscalinggroup名1(図のASG:Blue)”
ASG_NAME_GREEN=”autoscalinggroup名2(図のASG:Green)”

# TG = TargetGroup
## TG_ACTIVE_ARN=”TG:Activeのarn(amazon resource name)”
## TG_STANDBY_ARN=”TG:Standbyのarn”

# autoscaling groupのtargetgroupがactiveか調査
## TargetGroupのARN(AmazonResourceName)を取得
ASG_BLUE_TG_ARN=`aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names ${ASG_NAME_BLUE} | \
    jq '.AutoScalingGroups[].TargetGroupARNs[]'`

## TargetGroupのARNからActive/Standbyを判断
case ${ASG_BLUE_TG_ARN} in
    *active* )
        # BLUEのTGがactiveであればSTANDBYとしてGREENを設定
        STANDBY_ASG_NAME="${ASG_NAME_GREEN}"
        ;;
    *standby* )
        # BLUEのTGがstandbyであればSTANDBYとしてBLUEを設定
        STANDBY_ASG_NAME="${ASG_NAME_BLUE}"
        ;;
    * )
        echo "TargetGroupの設定が取れていない可能性があります。確認してください"
        exit 1
        ;;
esac


# ①Standby⇄Activeの切り替え
aws autoscaling attach-load-balancer-target-groups \
    --auto-scaling-group-name ${STANDBY_ASG_NAME} \
    --target-group-arns ${TG_ACTIVE_ARN}

aws autoscaling detach-load-balancer-target-groups \
    --auto-scaling-group-name ${STANDBY_ASG_NAME} \
    --target-group-arns ${TG_STANDBY_ARN}


# 切り替え後ヘルスチェックの時間を考慮してsleep
# この間はBlue/Green両側へアクセスが行くことを許容している
sleep 60


# ②Active⇄Standbyの切り替え
aws autoscaling attach-load-balancer-target-groups \
    --auto-scaling-group-name ${ACTIVE_ASG_NAME} \
    --target-group-arns ${TG_STANDBY_ARN}

aws autoscaling detach-load-balancer-target-groups \
    --auto-scaling-group-name ${ACTIVE_ASG_NAME} \
    --target-group-arns ${TG_ACTIVE_ARN}

4-1. 旧バージョンのEC2インスタンスを削除

横version_delete.png

1.で使用したスクリプトがほぼそのまま使用できます。
Standby側に紐づいているAutoScalingGroupの数を0にするだけです。

awscli+jqでのコマンド例

# 設定
# ASG = AutoScalingGroup
# TG = TargetGroup
ASG_NAME_BLUE=”autoscalinggroup名1(図のASG:Blue)”
ASG_NAME_GREEN=”autoscalinggroup名2(図のASG:Green)”
NUM=”0” # 削除の為、台数は0台に設定

# autoscaling groupのtargetgroupがactiveか調査
## TargetGroupのARN(AmazonResourceName)を取得
ASG_BLUE_TG_ARN=`aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names ${ASG_NAME_BLUE} | \
    jq '.AutoScalingGroups[].TargetGroupARNs[]'`

## TargetGroupのARNからActive/Standbyを判断
case ${ASG_BLUE_TG_ARN} in
    *active* )
        # BLUEのTGがactiveであればSTANDBYとしてGREENを設定
        STANDBY_ASG_NAME="${ASG_NAME_GREEN}"
        ;;
    *standby* )
        # BLUEのTGがstandbyであればSTANDBYとしてBLUEを設定
        STANDBY_ASG_NAME="${ASG_NAME_BLUE}"
        ;;
    * )
        echo "TargetGroupの設定が取れていない可能性があります。確認してください"
        exit 1
        ;;
esac

# Standby側のAutoScalingGroupでEC2 instance起動
aws autoscaling set-desired-capacity \
    --auto-scaling-group-name ${STANDBY_ASG_NAME} \
    --desired-capacity ${NUM}

4-2. Rollback

サービスインした時に問題が発生してしまった!
という時でも素早く戻せるのがBlueGreenのいいとこです。
3.で記載した通り、3の手順のコマンドを実行する事で戻すことができます。

まとめ

弊社のサービスで使用している設定を簡単なコマンドサンプルと共に解説させていただきました。
一見すると操作対象が多く見えがちですが、実際に切り替えを行う箇所は1つだけと非常にシンプルです。

当初ALBのRuleのプライオリティーを操作してアクセスを切り替えるよう設定していましたが、
テストの段階で切り替える際に1秒程度の接続エラーが起きてしまうことが発覚した為、今回の方式に切り替えています。

また、Blue-Green Deploymentはコンテナ技術との相性が良い為、ECSでの実装も考えましたが、
アプリケーションが起動時にメモリを多く取る性質の為一つのコンテナでリソースを大きく取らなければならないこと、
EC2をRIで購入している為非常に安価に使用できること、を理由にEC2での運用としています。

現在この方式で問題なく動いており、EC2をAutoScalingGroupで管理している為、
負荷増大によるスケーリングにも十分に対応可能です。

次回は「同じ構成をGCPで構築してみた」になります。
LB/AutoScaling設定周りの違いについて等、はまった部分もあるのでぜひご覧ください。