ECSでローリングアップデートを実現する

はじめに

こんにちは。hiranoです。
ECSでアプリケーションを運用していると、避けて通れないのがアップデートです。
例えば、nginxの設定変更、コンテナイメージの差し替え、ミドルウェアのバージョン更新など
「動いているものを新しい仕様に置き換える」場面は定期的に発生するかと思います。

そういったなかで、ECSでサービスを更新する方法はいくつかあり、代表的には次のようなパターンがあります。
・ローリングアップデート:稼働中タスクを残しながら新タスクを順次起動し、入れ替えていく
・ブルー/グリーンデプロイ:新旧の環境(ブルー/グリーン)を分けて用意し、切り替えで移行する
・その他:一時的に止めて入れ替える など

そこで今回実践したいのが、タスクを止めずに更新できるローリングアップデートです。
具体的には、既存タスクを残したまま新しいタスクを起動し、新タスクがヘルスチェックに合格したらALBのトラフィックを寄せ、古いタスクを順次停止する――という流れで、サービスを無停止で更新していきます。

前提の構成

本記事は、前回の記事「ECS Managed Instances 入門:ALB 経由で nginx コンテナを公開してみる」とほぼ同構成で進めます。

ということで、以下の構成・リソースが揃っている状態を想定します。
詳細が気になる方はぜひ前回記事をご覧ください。
・VPC
・パブリックサブネット2つ(別々のAZ)
・ECSクラスター(クラスター名:Rolling-update-nginx-cluster)
・nginxのタスク定義(タスク定義名:Rolling-update-nginx-task)
・ECSサービス(サービス名:Rolling-update-nginx-service)
・ALB/ターゲットグループ
・セキュリティグループ
・CloudWatch Logs
・ECRへコンテナイメージをプッシュするための作業用EC2(Amazon Linux 2023)
 ※IAMロール:AmazonEC2ContainerRegistryPowerUser付与済み
  Docker、AWS CLIインストール済み
  ECRへ到達可能

ちなみにアップデート前に適用しているイメージURlは「nginx:latest」で、以下のnginxのデフォルトページが表示されます。

本記事の流れ

ローリングアップデートをハンズオンで確認していきます。流れは大きく次のとおりです。
1. 事前準備
⇒更新差分を分かりやすくするための準備をします。
2. アップデート条件の確認
⇒文字通りアップデートの条件を決めます。
3. タスク定義更新
⇒新しいリビジョンを作成します。
4. サービス設定変更
⇒アップデート条件通りに設定変更し、新リビジョンをデプロイ(アップデート)します。
5. 実行結果の確認
⇒ECSサービスイベント/CloudWatch Logsから置き換えの様子を確認します。

それでは、事前準備から順に進めていきます。

実践

1. 事前準備

ローリングアップデートの挙動を確認するために、更新後のタスクが動いていることを判別できる差分を準備します。
今回は分かりやすく「nginxが返すトップページの表示内容を変更したコンテナイメージ」を作成し、Elastic Container Registry(ECR)に格納します。あとでそれをタスク定義で使うことにしましょう。

リポジトリの作成手順
①Amazon Elastic Container Registry > プライベートレジストリ > [リポジトリを作成]の順で押下
②以下の設定値を入力し、[作成]を押下
 リポジトリ名:rolling-update-nginx
 ※他はデフォルトのままです。

作成したリポジトリの概要から「リポジトリ URI」を控えておきます。

続けて、差分用コンテナイメージの作成とリポジトリへの格納を行います。
AWSコンソール上でのイメージ作成はできませんので、準備していた作業用EC2を使います。
今回コマンドはSession Manager上で実行しています。

コンテナイメージのプッシュ手順
①作業用EC2に接続
②認証トークンを取得し、レジストリに対してDockerクライアントを認証する(※)
③作業ディレクトリを作成する

mkdir -p ~/rolling-update-nginx && cd ~/rolling-update-nginx

④差分のDockerfileを用意する

cat << 'EOF' > Dockerfile
FROM nginx:latest
RUN printf "Hello from v2 (rolling update)\n" > /usr/share/nginx/html/index.html
EOF

⑤Dockerイメージ(コンテナイメージ)を構築する

docker build -t rolling-update-nginx .

⑥リポジトリにイメージをプッシュできるように、イメージにタグを付ける(※)
⑦作成したリポジトリにこのイメージをプッシュします(※)

※以下を押下することで、実行コマンドを確認することができます。
Amazon Elastic Container Registry > プライベートレジストリ > rolling-update-nginx >イメージタブ > [プッシュコマンドの表示]

リポジトリにプッシュしたイメージが存在するか確認します。
とりあえず問題なさそうです。

2. アップデート条件の確認

次はECSサービスにて設定するローリングアップデート条件を決めていきます。
ローリングアップデート時の入れ替わり方(同時に何個起動して、いつ古いタスクを止めるか)をコントロールしていきますが、今回は無停止を意識するので、ポイントとしては以下の3点とします。

・必要なタスク= 2
⇒「通常時はタスクを2個動かし続けたい」という宣言です。ECSサービスは基本的に常にこの数に戻そうとします。
 AZを2つ利用しているのでタスク数は2を前提とします。
・最小実行タスク = 100%
⇒ 更新中でも最低2個はヘルシーを維持します。(2×100%=2)
・最大実行タスク = 200%
⇒ 更新中に一時的に最大4個まで増やしてOKとします。(2×200%= 4)

ローリングアップデート中は新旧タスクが一時的に混在しながら置き換わっていきます。
このとき、更新中に維持したい稼働タスク数の下限/上限を決めるのが、最小実行タスクと最大実行タスクです。

3. タスク定義更新

タスク定義を更新して新しいリビジョンを作成します。
ここで先ほど作成したリポジトリ内のイメージを指定してあげます。

タスク定義更新手順
①Amazon Elastic Container Service > タスク定義 > 対象タスク定義 > 新しいリビジョンの作成
②コンテナ設定内「イメージ URI」右側の「ECRイメージを閲覧」にて今回作成したイメージを選択
③[作成]を押下

タスク定義名の横の数字(Rolling-update-nginx-task:4)を見て、リビジョンが正常に更新されたことを確認しましょう。

4. ECSサービス設定

アップデート条件を決め、タスク定義の新リビジョンも作成できました。
あとはサービスの設定更新で、タスク定義のリビジョンを新しいものに切り替えて更新すると、ECSが自動的にローリングアップデート(新タスク起動 → ヘルシー確認 → 旧タスク切り離し)を開始します。

ECSサービス設定更新手順
①Amazon Elastic Container Service > クラスター > 対象クラスター を選択
②サービスタブ > 対象サービス >[サービスを更新] の順で押下
③デプロイ設定にて、以下の設定値を入力し、[更新]を押下
 タスク定義のリビジョン:最新のリビジョン
 デプロイ戦略:ローリングアップデート
 必要タスク:2つ
 最小実行タスク:100%
 最大実行タスク:200%

5. 実行結果の確認

結果の確認をしていきましょう。
まずは、ECSサービスのイベントから、ローリングアップデートが「開始→進行→収束」したことを確認します。

ECSサービスのイベント確認手順
①Amazon Elastic Container Service > クラスター > 対象クラスター を選択
②サービスタブ > 対象サービス > イベントタブの順で押下
③下記のメッセージに注目する
・has started 2 tasks: ... / registered 1 targets in target-group ...
⇒新リビジョンのタスクが2つ起動し、ターゲットグループに登録された
・has begun draining connections on 1 tasks. / deregistered 1 targets in target-group ... / has stopped 1 running tasks: task ...
⇒旧タスクの切り離し→ターゲットグループ登録解除→旧タスクが停止した
・deployment ... deployment completed. / has reached a steady state.
⇒デプロイ処理完了、正常に稼働状態になった

ローリングアップデート中のターゲットグループは以下のようになってました。 ローリングアップデート後

次にCloudWatch Logsから、新リビジョンが起動し、実際にリクエストを処理していることを確認します。

CloudWatch Logs確認手順(ECS側から)
①Amazon Elastic Container Service > クラスター > 対象クラスター を選択
②サービスタブ >対象サービス >タスクタブの順で押下
③実行中タスクを選択
④ログタブを押下し、CloudWatch Logsの該当ログストリームを表示
⑤下記のメッセージに注目する
・/docker-entrypoint.sh ... ready for start up / start worker processes など
⇒起動に成功していること
・ELB-HealthChecker/2.0のGET / 200 など
⇒ターゲットがヘルスチェックに応答できていること
アクセスログも確認できます。

ここまでログを確認してきましたが、一番重要なのはブラウザにアクセスして更新されているかですね。
http://<ALBのDNS名>でアクセスしたところ、、 新イメージURlが適用されているようなので、特に問題なくローリングアップデートできました。

まとめ

今回は、ECSサービスの 必要タスク/最小実行タスク/最大実行タスク を調整したうえで、タスク定義のリビジョンを更新し、ローリングアップデートで新しいタスクへ段階的に入れ替えました。
また、各種ログを確認することで、新タスクの起動から旧タスクの切り離し/停止、デプロイ完了までの流れを追えることも分かりました。
コンテナのアップデート方法は、求める要件(停止可否、切り戻しのしやすさ、構成の複雑さ、コストなど)によっては、別のアプローチが有効な場合もあります。とはいえ、ローリングアップデートはECS標準の仕組みで段階的に入れ替えられるため、まず検討しやすい選択肢だと思います。
ぜひ本記事の手順をベースに、ローリングアップデートの運用を検討してみてください。