[アップデート] ヘルスチェックに失敗した ECS タスクのスケジューリングの挙動がアップデートされました

Amazon ECS タスクのスケジューリングが更新され、ヘルスチェック失敗時に失敗タスクを終了する前に新しいタスクの起動を試みるように変更されました。これにより、突発的な負荷や偽陰性によるヘルスチェック失敗に対しての耐障害性が向上しました。
2024.01.30

こんにちは! AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。

2023年10月のアップデートです。書きかけで年を越してしまいました。

ヘルスチェックに失敗した ECS タスクのスケジューリングが耐障害性を高めるような挙動にアップデートされました。

何が変わった

今まで ALB またはコンテナ内部のヘルスチェックに失敗した場合、失敗したタスクを終了した後に、新しいタスクの起動を試みる挙動でした。

今回のアップデートにより、ヘルスチェックに失敗したタスクを終了する前に、新しいタスク起動を試みる挙動に変更されました。

何が嬉しいか

ヘルスチェック結果が偽陰性だった場合の耐障害性向上に役立ちます。

どんな時に嬉しいか

例えばですが、 Application Auto Scaling で ECS タスクのオートスケーリングを実装している環境を考えてみましょう。 CPU/メモリ 使用率に応じて Application Auto Scaling が ECS タスクのオートスケーリングをすると思います。

もし、スケーリングが間に合わないくらい、突発的なリクエストが飛んできたとします。 ECS タスクの CPU/メモリ 使用率が上昇すると思います。CPU/メモリが逼迫した ECS タスクのヘルスチェックはどうでしょうか? はい、正常に動く可能性は低くなります。このケースにおいて、 Unhealty のヘルスチェックの結果は偽陰性だった言えます。

偽陰性なヘルスチェックの結果、アップデート前だと失敗したタスクを終了した後に、新しいタスクの起動を試みる挙動でした。この挙動によって、 ECS サービスが希望するタスク数の不足や、最悪の場合、サービス断につながる可能性がありました。

今回のアップデートはそのような、耐障害性の部分を補填するアップデートになります。

もう少し深掘り

desiredCount と maximumPercent

今回の ECS タスクのスケジューリングには、 desiredCount と maximumPercent が大きく関わります。どちらも、 ECS サービスの設定値になります。

desiredCount

desiredCount (希望する値) は文字通り、 ECS サービス内で起動して欲しい ECS タスクの総数になります。ECS サービス内でタスクが 2 つ起動したい場合は desiredCount を 2 に指定するイメージです。

Desired count

maximumPercent

maximumPercent は ECS サービス内で実行可能な ECS タスクの上限です。 desiredCount との掛け合わせで数が決まり、 desiredCount が 2, maximumPercent が 200 % だった場合は、サービススケジューラは 2 つの古いタスクを停止する前に、 2 つの新しいタスクを開始する挙動になります。

サービススケジューラ戦略によってデフォルト値が異なり、 DAEMON の場合は 100 %、 REPLICA の場合は 200 % が設定されています。

maximumPercent

置換えタスクも Unhealty だった時

先ほど、「今回のアップデートにより、ヘルスチェックに失敗したタスクを終了する前に、新しいタスク起動を試みる挙動に変更されました。」と記載しました。

では、新しく立ち上がったタスクも Unhealty だった場合、どうなるのでしょうか?

この場合は、 desiredCount の数に基づき、desiredCount の数と等しくなるように、既存タスクと新しいタスクのどちらかを停止する動きを取る様です。

置き換えタスクのヘルスステータスが UNHEALTHY の場合、スケジューラーは異常のある置き換えタスクまたは既存の異常タスクのいずれかを停止して、タスク総数が desiredCount と等しくなるようにします。

サービススケジューラの概念

今までのスケジューリングにしたい

ライセンスや処理内容によっては、アップデート前のような、タスクを停止してから新しいタスクを起動したいケースもあると思います。その場合、 maximumPercent を使って、スケジュールをコントロールできます。

maximumPercent が 100 % だった場合、 UNHEALTY なタスクがなくなるまで、 UNHEALTY なタスクをランダムに 1 つずつ停止し容量を確保してから、置き換えタスクを開始する挙動を取るようです。また、タスクの入れ替えによって合計タスク数が desiredCount を超えている場合は、 desiredCount 数に戻るようタスクがランダムに停止されます。

maximumPercent パラメーターによって、置き換えタスクを先に開始できないようにスケジューラーが制限されている場合、スケジューラーは異常のあるタスクをランダムに 1 つずつ停止して容量を解放してから置き換えタスクを開始します。異常のあるタスクがすべて正常なタスクに置き換えられるまで、起動と停止のプロセスが続きます。異常なタスクがすべて置き換えられ、正常なタスクだけが実行中になると、合計タスク数が desiredCount を超える場合、タスク数が desiredCount になるまで、正常なタスクが無作為に停止されます。maximumPercent および desiredCount の詳細については、「サービス定義パラメータ」を参照してください。

サービススケジューラの概念

デプロイ中の挙動

ローリングアップデート

今回の UNHEALTY なタスクの置き換え動作ですが、ローリングアップデート中で発生した場合は挙動が異なり、異常なタスクを停止してから置き換えタスクを開始する挙動になります。あくまで、デプロイ以外のパターンにおいて UNHEALTY なタスクが検知された時のスケジューリングに仕様変更が入ったようです。

この動作は、ローリング更新デプロイタイプを使用するサービスによって実行および管理されるタスクには適用されません。ローリング更新中、サービススケジューラーはまず異常なタスクを停止してから置き換えタスクを開始します。

サービススケジューラの概念

B/G デプロイ中に発生した場合

先ほど、ローリングアップデートの場合は異常なタスクを停止してから置き換えタスクを開始すると記載しました。では、 Blue Green デプロイ(以後、 B/G デプロイ)中にタスクが UNHEALTY だった場合はどうなるのでしょうか。

やってみた

今回、以下の B/G デプロイ中に Blue のタスクに乗り込みヘルスチェックを遮断してみます。

通常時の挙動

せっかくなので、通常時の動作も確認してみます。

タスクが 1 つサービス上で起動しており、ヘルスステータスが Healthy であることがわかります。

以下のコマンドで ECS タスクに乗り込みます。

aws ecs execute-command \
    --cluster test-cluster \
    --task ae363dd96f8545a1bbea697a2d54fdc0 \
    --container test-container \
    --interactive \
    --command "/bin/bash"

/var/www/html にドキュメントルートとヘルスチェック用が配置されていますね。

実行結果

[cloudshell-user@ip-10-132-76-246 ~]$ aws ecs execute-command \
>     --cluster test-cluster \
>     --task ae363dd96f8545a1bbea697a2d54fdc0 \
>     --container test-container \
>     --interactive \
>     --command "/bin/bash"

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


Starting session with SessionId: ecs-execute-command-0ab895c4a695a9638
root@ip-10-0-1-61:/# ls -la /var/www/html/
total 16
drwxr-xr-x 2 root root 4096 Jan 30 06:07 .
drwxr-xr-x 3 root root 4096 Jan 30 06:07 ..
-rw-r--r-- 1 root root  163 Jan 30 06:06 healthcheck.html
-rw-r--r-- 1 root root  150 Jan 30 06:06 index.html
root@ip-10-0-1-61:/#

では、 healthcheck.html の名前を書き換えて、ヘルスチェックを失敗させます。

mv /var/www/html/healthcheck.html /var/www/html/healthcheck.html.bk

ELB のヘルスチェックが 200 から 404 に変わってきていますね。

ECS タスクにも動きが見えました。既存のタスクを停止する前に、新しいタスクを起動してきていますね。

B/G デプロイ中

アップも終わりましたので、 B/G デプロイ中の挙動を試してみましょう。 index.html を書き換えて、 B/G デプロイを試みます。

B/G 準備整っていることがわかります。

[cloudshell-user@ip-10-130-49-109 ~]$ curl test-lb-XXXXXXXX.ap-northeast-1.elb.amazonaws.com:80
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>testページ</title>
  </head>

  <body>
    <h1>test</h1>
  </body>
</html>
[cloudshell-user@ip-10-130-49-109 ~]$ curl test-lb-XXXXXXXX.ap-northeast-1.elb.amazonaws.com:8080
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>B/G デプロイを試してみるぞ!</title>
  </head>

  <body>
    <h1>B/G デプロイを試してみるぞ!</h1>
  </body>
</html>
[cloudshell-user@ip-10-130-49-109 ~]$

タスク定義のリビジョンをみるに、 aa3db11535d04e10ad6f366b8824c7a4 が置き換え前、 532b026a36d648b7bb1477befcac629a が置き換え後のタスクであることがわかります。

置き換え前タスクに乗り込み

それでは、 置き換え前タスクの aa3db11535d04e10ad6f366b8824c7a4 をいじってみようと思います。先ほどと同じく、 healthcheck.html を編集します。

aws ecs execute-command \
    --cluster test-cluster \
    --task aa3db11535d04e10ad6f366b8824c7a4 \
    --container test-container \
    --interactive \
    --command "/bin/bash"

# 乗り込んだ後に実行
ls -la /var/www/html/
mv /var/www/html/healthcheck.html /var/www/html/healthcheck.html.bk

なんと、置き換え前のタスクのリビジョンで、ヘルスチェックに失敗したタスクが停止する前に、新しいタスクを起動する挙動になりました。

ターゲットグループ側でも Healthy と Unhealthy なタスクが混在していることが確認できました。

置き換え後のタスクに乗り込み

それでは置き換え後タスクの 532b026a36d648b7bb1477befcac629a に乗り込んでみます。

aws ecs execute-command \
    --cluster test-cluster \
    --task 532b026a36d648b7bb1477befcac629a \
    --container test-container \
    --interactive \
    --command "/bin/bash"

# 乗り込んだ後に実行
ls -la /var/www/html/
mv /var/www/html/healthcheck.html /var/www/html/healthcheck.html.bk

こちらは、置き換え後のタスク定義リビジョンでヘルスチェックに失敗したタスクが停止する前に、新しいタスクを起動する挙動になりました。

同じように置き換え後で利用されているターゲットグループで、 Healthy と Unhealthy なタスクが混在していることが確認できました。

トラフィック置き換え後の置き換え前タスクに乗り込み

B/G デプロイでは、本番稼働のポートにトラフィックを切り替え後、置き換え前タスクを、一定期間消さずに保持できます。これにより、本番トラフィック切り替え時に異常だった場合、ロールバックを迅速に行うことができます。トラフィック置き換え後の置き換え前タスクに乗り込んでどうなるか挙動を確認してみましょう。

ALB を確認すると本番、テストリスナーどちらも置き換え後の内容が表示されています。

[cloudshell-user@ip-10-134-21-177 ~]$ curl test-lb-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com:80
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>B/G デプロイを試してみるぞ!</title>
  </head>

  <body>
    <h1>B/G デプロイを試してみるぞ!</h1>
  </body>
</html>
[cloudshell-user@ip-10-134-21-177 ~]$ curl test-lb-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com:8080
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>B/G デプロイを試してみるぞ!</title>
  </head>

  <body>
    <h1>B/G デプロイを試してみるぞ!</h1>
  </body>
</html>
[cloudshell-user@ip-10-134-21-177 ~]$

トラフィック置き換え後の待機時間が短く、デプロイし直したためタスク定義のリビジョンが、トラフィックの置き換え前と異なりますが、 9b0f35cb4fc842a8bbf02ea469ad516c が置き換え前のタスク、 abdd6700151241c7ac1aae214e8b9f9c が置き換え後のタスクになります。

以下のコマンドでタスクに乗り込み、ファイル名を書き換えてみます。

aws ecs execute-command \
    --cluster test-cluster \
    --task 9b0f35cb4fc842a8bbf02ea469ad516c \
    --container test-container \
    --interactive \
    --command "/bin/bash"

# 乗り込んだ後に実行
ls -la /var/www/html/
mv /var/www/html/healthcheck.html /var/www/html/healthcheck.html.bk

しかし、今回は タスクの置き換えも停止も起こりませんでした。なぜでしょうか?

理由は ALB のヘルスチェックが行われていないためです。ターゲットグループを確認すると、ヘルススチェックステータスは Unused でした。

トラフィック置き換え後の置き換え後タスクに乗り込み

最後にトラフィック置き換え後の、置き換え後タスクに乗り込んでみます。

aws ecs execute-command \
    --cluster test-cluster \
    --task abdd6700151241c7ac1aae214e8b9f9c \
    --container test-container \
    --interactive \
    --command "/bin/bash"

# 乗り込んだ後に実行
ls -la /var/www/html/
mv /var/www/html/healthcheck.html /var/www/html/healthcheck.html.bk

ある程度、予想できたかもしれませんが、置き換え後のタスク定義リビジョンで新しいタスクが起動しました。

ターゲットグループ側からも Healthy と Unhealthy のタスクが混在していますね。

まとめ

以上、「ヘルスチェックに失敗した ECS タスクのスケジューリングの挙動がアップデートされました」でした。このアップデートで、突発的なスパイクに対する耐障害性が、かなり向上したのではないかと思います。この記事がどなたかの参考になれば幸いです。

AWS 事業本部コンサルティング部のたかくに(@takakuni_)でした!