FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

cdk deploy時、無限ループで焦った件

cdk deployしたら処理が終わらない…

ゲーム・アニメ事業部でスマートフォンゲームのサーバサイド開発&インフラ&分析基盤を担当している宇城です。

今回はCDKバージョンのアップデートをしようと思い、各環境で実行したcdk deployが無限ループしてしまい、 一旦落ち着こうと思って実行したスタック更新キャンセル(Rollback)処理すら無限ループするという体験についての 内容と原因を記載します。

状況

前述の通りCDKで諸々を管理しているのですが、開発メンバーだけが使う開発環境も当然CDKで管理しています。 開発環境はdev1,dev2,…と複数環境をECS上に用意しているのですが、それらの環境を1つのCDKスタックで管理しています。

上記は大まかな構成図ですが、 ALBとECS-Service以下を環境別に設定-起動し、その他は共通利用としているイメージです。 ※ElastiCacheやS3などもいますが構成図上割愛します。

この構成図上の赤い枠で囲っている部分を作るコードをざっくり抜粋すると以下のような物になっています。

test-stack.ts
    // dev1アプリケーションを起動
    const loadBalancedFargateDev1Service = createEcs("dev", this, albSecurityGroup, vpc, cluster, properties, applicationDev1EcrTagNumber)
    // dev2アプリケーションを起動
    ...
create-ecs.ts
export function createEcs(targetEnvironment: String, xxxCdkStack: xxxCdkStack, albSecurityGroup: cdk.aws_ec2.SecurityGroup, vpc: cdk.aws_ec2.Vpc, cluster: cdk.aws_ecs.Cluster, properties: envInfo, applicationEcrTagNumber: any): cdk.aws_ecs.FargateService {
  // 環境別ECS設定情報を取得
  const individualEcsConfig = individualProperties.find(property => property.env == targetEnvironment);
  if (individualEcsConfig === undefined) throw new Error("No Support environment")
  // 環境別アプリケーションコンテナ
  const repository = ecr.Repository.fromRepositoryArn(xxxCdkStack, individualEcsConfig.ecrName, "arn:aws:ecr:xxxx" + individualEcsConfig.ecrName)
  const containerImage = ecs.ContainerImage.fromEcrRepository(repository, applicationEcrTagNumber)
  const alb = new ApplicationLoadBalancer(xxxCdkStack, individualEcsConfig.albId, {
    loadBalancerName: individualEcsConfig.albName,
    securityGroup: albSecurityGroup,
    vpc
  });
  const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(xxxCdkStack, individualEcsConfig.ecsServiceName, {
    cluster,
    serviceName: individualEcsConfig.ecsServiceName,
    ...
  });

 ... 中略 ..

 // HealthCheckの設定
  loadBalancedFargateService.targetGroup.configureHealthCheck({
    path: "/index_v2.html",
    healthyThresholdCount: 2, // Optional
    unhealthyThresholdCount: 2, // Optional
    timeout: Duration.seconds(5), // Optional
    interval: Duration.seconds(30), // Optional
  });

  ...
 

test-stack.tsでは環境名とタグを指定してcreateEcsを呼び出し、 create-ecs.ts側では受け取った環境情報を元にindividualEcsConfigを参照して環境別設定情報を取得してECSを立ち上げています。 1行追加+環境ファイル追記で開発環境が増やせるといった感じです。

このCDKを実行したところECSタスクの立ち上げと失敗を繰り返し、実行が終わらない状態になったわけです。 (省略が多すぎてわかりにくいかもしれません。すみません。)

以前ECS起動時にコンテナの中身であるモジュール実装に問題があり、タスクの起動に失敗→再度立ち上げ直す→…というパターンで処理が終わらないことがあったので、その線を疑ってECSタスクを確認するもタスクは問題なさそう。 よくわからないが一旦戻すかーということで「スタックの更新をキャンセル」し、Rollback Inprogressになるが、ロールバック処理も一生終わらない…というのが今回の状況です。

原因と対処

原因箇所はどこだろう…ということでマネジメントコンソールをいろいろ見てみたところ、どうやらALBのヘルスチェックが失敗しているということがわかりました。 またdev1環境は正常稼働していて、それ以外の環境のヘルスチェックが通っていない状態でした。

そこで直近dev1環境に対して更新を行った担当者に確認をしたところ、前日にdev1環境に対してヘルスチェックの設定を変更していたことがわかりました。

改めてCDKのヘルスチェック項目を見てみると

 // HealthCheckの設定
  loadBalancedFargateService.targetGroup.configureHealthCheck({
    path: "/index_v2.html",
    healthyThresholdCount: 2, // Optional
    unhealthyThresholdCount: 2, // Optional
    timeout: Duration.seconds(5), // Optional
    interval: Duration.seconds(30), // Optional
  });

pathが明示的に /index_v2.htmlに設定されています。 今回はこの設定が原因でエラーとなっていました。 実は /index_v2.htmlは前日のdev1に対する対応で入れられたdev1にしかないヘルスチェック用APIで その他の環境にはモジュールが反映されていませんでした。

ただCDKコード実装上、ヘルスチェックのパスが環境別の設定になっておらず、 dev1環境への反映でその他の環境ALBのヘルスチェックパスも変わってしまっており、 dev1以外ではそのAPIが存在しないためエラーとなったということです。

これはつまり前日の時点ですでにdev1以外はヘルスチェックが通っていなかった…ということになります。 たまたま、dev1以外を見る人がその日いなかったため気づかなかったというお恥ずかしい話です。 開発環境だからと油断してました。

暫定対処としてdev1以外のヘルスチェックパスをマネジメントコンソール上から前日の状態に戻してみると、終わらなかったcdk deployのロールバック処理が正常に進行しました。 またパス設定に関しては環境別ファイルに切り出して参照する形にCDKの修正を行いました。

なんでロールバックまで無限ループしたのか

それだけだとロールバックは無限ループしないでしょ…という話に思うかもしれませんが、 冒頭で話した通り、このdeploy実行目的は「CDKバージョンのアップデート」だったため、全ての環境に対して実施していました。 そして開発環境を1つのCDKスタックで管理している状態です。

「dev1以外へのヘルスチェックAPI追加モジュール反映は行っていない」 且つ 「前日反映時点でdev1以外のヘルスチェックが通っていなかった」 という状況だったためロールバック処理としては、 「dev1以外の環境は動作不良状態から動作不良状態にロールバックする」 という「行くもエラー、戻るもエラー」という状態が発生してしまったということになります。 そのためECSタスクがいつまでたっても立ち上がらず、ロールバックも無限ループが発生しました。

現状デプロイの挙動としては「指定した環境のコンテナのみ最新の環境別ECRを参照して置き換える」という挙動にしているため、前日のdev1へのモジュール反映時はdev1の「ECS更新→ヘルスチェックAPI置換」という部分は問題なくチェック&反映がされましたが、 その他の環境はECS-Serviceの差分がないためCDK的には変更なし挙動となり、ヘルスチェックは通っていないがdeployできてしまうという状態が発生したということが事の始まりなのかなと思います。

今後どうするのか

ヘルスチェックに関して言えば、パスを環境別に設定するようにしたので今回と同一の件は起きなくなりました。 また現CDK上はモジュールの中身を参照しているような個所は他にないため、類似の問題は発生しなさそうに見えます。 ただ、元をたどると「単一CDKスタックで複数環境を管理する」というのが微妙ということなんだろうなと思っています。 (実際AWS-SAの方に聞いても1環境1スタックがいいですという回答をもらっています。) ただ諸般の事情によりこの構成にしている側面もあり…

ちょっと考える時間が必要そうです。