ECSデプロイ後に環境変数が消えたので、タスク定義のリビジョン管理を見直した話
はじめに
AWS ECSでアプリケーションをデプロイした際、コンテナ自体は起動しているものの、ログを見ると一部の環境変数が取得できていないことが分かりました。
最初は、アプリケーション側の設定漏れや、環境変数名のタイプミスを疑っていました。
しかし調査していくと、原因はアプリケーションコードではなく、ECSのタスク定義のリビジョン管理にありました。
今回起きていたことを簡単にまとめると、以下のような内容です。
- ECRに新しいイメージをpushした
- ECSのタスク定義に新しいリビジョンが作成された
- デプロイ後のリビジョンで、以前設定していた環境変数が消えていた
- 結果として、アプリケーション側で環境変数を取得できなくなった
本記事では、実際に詰まった内容をもとに、ECSのタスク定義とデプロイ時のリビジョン作成について整理します。
この記事は、特定の構成に対する正解を示すものではなく、実務で発生した問題を通して、デプロイ設計で意識するようになったことをまとめたものです。
起きていた問題
ECSで動いているアプリケーションは、タスク定義に環境変数を設定していました。
イメージとしては、以下のような設定です。
{
"name": "sample-app",
"image": "sample-repository:latest",
"environment": [
{
"name": "APP_ENV",
"value": "production"
},
{
"name": "API_BASE_URL",
"value": "https://example.com"
}
]
}
この状態では、アプリケーションから環境変数を問題なく参照できていました。
しかし、新しくデプロイしたあと、ログ上では環境変数が取得できていないような挙動になっていました。
アプリケーション自体は起動していたため、最初は原因が分かりにくい状態でした。
最初に疑ったこと
最初は、以下のような点を疑いました。
- 環境変数名を間違えている
- Dockerfile側で上書きしている
- アプリケーションの読み込みタイミングが悪い
- デプロイされたイメージが想定と違う
- ECSタスクが古いリビジョンを参照している
ただ、ログやECSの設定を確認していくと、アプリケーション側の問題ではなさそうでした。
そこで、デプロイ後のタスク定義を確認しました。
原因: 新しいリビジョンに環境変数が入っていなかった
原因は、デプロイ後に作成された新しいタスク定義のリビジョンに、以前設定していた環境変数が入っていなかったことでした。
もともとは、タスク定義の特定リビジョンに対して環境変数を設定していました。
しかしデプロイによって新しいリビジョンが作成された際、そのリビジョンでは環境変数が引き継がれていませんでした。
つまり、以下のような状態です。
task-definition:10
image: sample-repository:old
environment: あり
task-definition:11
image: sample-repository:latest
environment: なし
前のリビジョンでは動いていたのに、新しいリビジョンでは環境変数が存在しないため、アプリケーション側では値を取得できなくなっていました。
この時点で、
ECSで動いているかどうかだけでなく、どのリビジョンのどの設定で動いているかを見る必要がある
と感じました。
なぜ気づきにくかったのか
今回の問題が分かりにくかった理由は、デプロイ自体は成功していたからです。
ECRへのpushも成功していました。
ECSのデプロイも失敗していませんでした。
コンテナも起動していました。
そのため、表面的には「デプロイできている」状態に見えました。
しかし実際には、アプリケーションが必要とする環境変数が新しいリビジョンに存在していませんでした。
このように、ECSではコンテナが起動していることと、アプリケーションが期待する設定で動いていることは別です。
対応: Build後にタスク定義を作成する流れにした
対応として、CodeBuild後の処理でタスク定義を作成し、CodeDeployで利用する流れに変更しました。
ざっくりした流れは以下です。
- CodeBuildでDockerイメージをビルドする
- ECRにイメージをpushする
- 既存のタスク定義を取得する
- 既存の環境変数などを引き継いだ新しいタスク定義を作成する
- imageだけを新しいものに差し替える
- 作成したタスク定義をCodeDeployで利用する
ポイントは、新しいリビジョンを作るときに、必要な設定を明示的に引き継ぐことです。
今回の場合、環境変数はアプリケーションの動作に必要な設定だったため、新しいタスク定義にも必ず含める必要がありました。
イメージだけ差し替える
タスク定義を作るときは、すべてを新しく書き直すのではなく、既存のタスク定義をもとにして、イメージだけを差し替える形にしました。
イメージとしては以下のような考え方です。
既存のタスク定義を取得
↓
containerDefinitionsを確認
↓
対象コンテナのimageだけ更新
↓
環境変数などは既存設定を引き継ぐ
↓
新しいタスク定義として登録
これにより、以前のリビジョンに設定されていた環境変数を保持したまま、新しいイメージを使えるようになります。
{
"name": "sample-app",
"image": "sample-repository:latest",
"environment": [
{
"name": "APP_ENV",
"value": "production"
},
{
"name": "API_BASE_URL",
"value": "https://example.com"
}
]
}
ここで latest を使うか、コミットハッシュなどの固定タグを使うかは要確認です。
今回の内容では latest に差し替えることで最新イメージを参照する方針でしたが、運用上は「どのイメージがデプロイされたか」を追いやすくするために、コミットハッシュなどの一意なタグを使う方が適しているケースもあります。
Before / After
Before
以前は、デプロイによって作成された新しいリビジョンに対して、必要な環境変数が入っている前提で考えていました。
ECRにpush
↓
ECSの新しいリビジョンが作成される
↓
そのままデプロイ
↓
環境変数がなくなる
この状態では、デプロイのたびにタスク定義の中身が意図した状態になっているか不安定でした。
After
対応後は、既存のタスク定義をもとに新しいリビジョンを作成し、imageだけを更新する流れにしました。
ECRにpush
↓
既存のタスク定義を取得
↓
環境変数などを引き継ぐ
↓
imageを更新
↓
新しいリビジョンを登録
↓
CodeDeployでデプロイ
これにより、今まで使っていた環境変数を維持したまま、新しいイメージをデプロイできるようになりました。
今回学んだこと
今回の問題で一番大きかった学びは、
デプロイ後に作られるリビジョンの中身まで確認する必要がある
という点です。
ECSでは、タスク定義のリビジョンが変わると、アプリケーションが参照する設定も変わる可能性があります。
そのため、以下のような点を確認する必要があると感じました。
- デプロイ後のタスク定義リビジョンは何か
- そのリビジョンに必要な環境変数があるか
- imageだけでなく、containerDefinitions全体が意図した内容か
- CodeDeployがどのタスク定義を参照しているか
- 新しいリビジョンを作る処理で、既存設定を引き継いでいるか
以前は、ECRにpushしてECSにデプロイできれば問題ないと考えていました。
しかし実際には、デプロイ処理の中で作られるタスク定義が、アプリケーションの実行環境そのものになります。
おわりに
今回の問題は、アプリケーションコードのバグではなく、ECSのタスク定義リビジョンの作られ方が原因でした。
コンテナが起動していると、つい「デプロイは成功している」と考えてしまいます。
しかし、実際には環境変数やイメージ、ポート設定などを含めて、タスク定義が意図した状態になっているかを確認する必要があります。
今回の対応を通して、
デプロイはイメージを更新するだけではなく、実行環境を再定義する処理でもある
と学びました。
今後は、ECSのデプロイを見るときに、ECRやCodeDeployの成功結果だけでなく、作成されたタスク定義のリビジョンまで確認するようにしていきたいです。
この記事は個人の経験や調査内容をまとめたものであり、内容の正確性を保証するものではありません。環境や構成によって適切な対応は異なるため、実際に設定を変更する場合は公式ドキュメントや利用中の構成を確認してください。