AWS ECS + Java の場合の注意点と解決方法
AWS ECS で Java アプリケーションを動かそうとすると、最初の立ち上がりが遅くて、ヘルスチェックが通らないことがあります。
Docker が立ち上がってから Java のアプリケーションが立ち上がるまでに時間がかかるのですが、ECS は(デフォルトだと) Docker が立ち上がったらすぐヘルスチェックを開始するので、まだアプリケーションが立ち上がっておらず、ヘルスチェックが失敗します。
Java 以外の言語だと立ち上がりが早いのでハマることはあまりないんですが、Java の場合はよくハマってしまいます。
ECS へのデプロイ時に、最初のヘルスチェックが通らないと、コンテナが立ち上がってはストップを繰り返して、ECS のデプロイが終わらなくなります。たまに運良くヘルスチェックが通って成功したりします。
このような場合、LB (Load Balancer) のヘルスチェックの設定を見直すのがよいです。AWS ECS において、ヘルスチェックの設定は大きく2箇所あります
- LB のターゲットグループのヘルスチェックの設定
- ヘルスチェックの間隔
- ヘルスチェックが失敗になるタイムアウトの時間
- など...
- ECS Service で設定する「ヘルスチェックの猶予期間」の設定
Java の立ち上がりが遅い問題については、「ヘルスチェックの猶予時間」を調整することで解決するのが良いです。
ヘルスチェックの猶予期間(healthCheckGracePeriodSeconds)について
[Amazon ECS Service Definition Parameters)https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service_definition_parameters.html に healthCheckGracePeriodSeconds
の説明があります。
以下、公式ドキュメントのざっくり日本語訳
Amazon ECS において、タスクのステータスが RUNNING
になってから、この時間の間は、ELB ターゲットグループのヘルスチェックなどが失敗していても、無視します。単位は「秒」で指定します。ロードバランサを利用している ECS Service でのみ利用できます。デフォルトは0秒です。
タスクが start してから、ヘルスチェックに応答できるようになるまで時間を要する場合に利用できます。最大値は 2,147,483,647 秒です。この設定を使えば、タスクが Unhealthy と判定されて止まってしまうのを防ぐことができます。
※ドキュメントに出てくる「ECS service scheduler」とは、ECS のタスクの start と stop を管理している仕組みのことです。
このパラメータは 2017年12月に追加されたようです。
この設定は ECS の「サービス」の方で設定するもので、 Load Balancer の方の設定ではないので注意してください。
AWS CDK で healthCheckGracePeriodSeconds を設定する
例えば、 ApplicationLoadBalancedFargateService
を使っている場合は以下のように設定します
import { Duration } from "aws-cdk-lib"; ... const ecsService = new ApplicationLoadBalancedFargateService(this, "...", { // (中略) // Java が立ち上がるまでに時間がかかるため、ヘルスチェックを遅らせる healthCheckGracePeriod: Duration.seconds(120), });
ECS のログでヘルスチェックの様子を確認する
上記解決方法で解決したらOKなのですが、一応、ECS でヘルスチェックの様子を確認する方法を書いておきます。
デプロイ時のヘルスチェックの様子は、AWS のコンソールで、対象となるサービスの画面を開き、「デプロイ」または「イベント」のタブを確認すると見られます。
Amazon Elastic Container Service > クラスター > {クラスタ名} > サービス > {サービス名}
と開いてくと、各サービスの画面が見られると思います。
ただし、残念ながら非常に見づらいです...が、僕が知っている限り、他にいい感じのログとかはないのでここで確認するしかないです。
通常は以下のようにログが出ていると思います
- service XXX has started 1 tasks: task {task_id_1}
- 新しくタスク(Dockerコンテナ)が実行されます
- service XXX registered 1 targets in target-group {target_group_name}
- 新しく実行された Docker コンテナが Load balancer の target-group に追加されます
- service XXX has stopped 1 running tasks: task {task_id_0}
- 古いタスク(Docker コンテナ)が終了されます
- service XXX deregistered 1 targets in target-group {target_group_name}
- 古いタスク(Dockerコンテナ)が LB から外れます
- (service XXX, taskSet ecs-svc/YYY) has begun draining connections on 1 tasks.
- LB から外れるのにともない、古いタスクにリクエストが来なくなります
これがコンテナの数だけ繰り返されます
このログの task_id を見比べて、start したタスクと別のタスクが stop していればとくに問題がありません。 start した task_id がすぐに stop して、それが延々繰り返される場合は、最初のヘルスチェックに失敗している可能性が高いです。
さらに残念なことに、まだ Java のアプリケーションが立ち上がってないので、アプリケーションログを出力することも非常に難しいです *1。
AWS CDK でヘルスチェック周りの設定
当然ですが、 AWS CDK でヘルスチェック自体の設定もできます。例えば以下のように設定します。他にも設定項目はあるので、必要に応じて調べてください。
const ecsService = new ApplicationLoadBalancedFargateService(...); ecsService.targetGroup.configureHealthCheck({ path: "/health", // ヘルスチェックの間隔。デフォルト10秒 interval: Duration.seconds(30), // ヘルスチェックのリクエストのタイムアウト。デフォルト5秒 timeout: Duration.seconds(10), // unhealthy な状態から healthy な状態になるために3回ヘルスチェックが成功すれば良い // デフォルトは5回 // 間隔を30秒にしていると5回のヘルスチェックに2分半かかるので3回に減らしている healthyThresholdCount: 3, });
参考ページ
ここから先、特に追加のコンテンツはありませんが、役に立ったら投げ銭してもらえると嬉しいです。
*1:tomcat の設定とかで何らかのログを出すことはできるかも