ECSのタスク定義は「動く」だけなら数分で書けますが、本番で安定させるにはCPU/メモリ予約、ログ、Secrets、ヘルスチェック、2つのIAMロール、そして停止時の挙動まで詰める必要があります。現場でつまずきやすい要点を順に整理します。

ECSのタスク定義は、コンテナ基盤の設計思想がそのまま現れる場所です。docker run のオプションをJSONに落としただけの状態でも起動はしますが、本番でSLOを守り、障害時に迷わないためには、リソース予約・ログ・機密情報・ヘルスチェック・権限・停止処理までを一つずつ意図を持って決める必要があります。本記事ではFargate/EC2どちらのlaunch typeでも共通して効く「タスク定義の勘所」を、実務でつまずきやすい順に整理します。数値・上限はバージョンやプラットフォーム依存のため、最終的な確定値は必ず公式ドキュメントで最新を確認してください。

01タスク定義の全体像 — familyとcontainerDefinitions

タスク定義は複数のパートに分かれます。family(バージョン管理の単位)、IAMタスクロール、ネットワークモード、containerDefinitions、ボリューム、そしてlaunch typeです。このうち必須なのは familycontainerDefinitions の2つだけで、残りは任意です。family に登録するたびにリビジョン番号が1から順に採番され、これがロールバックやデプロイ履歴の実体になります。

containerDefinitions は最低1つのコンテナエントリを含む配列です。ここに複数コンテナを同居させるか、別タスクに分けるかが最初の設計判断になります。AWSのベストプラクティスは明快で、以下がすべて当てはまるときだけ同一タスクにまとめよ、としています。

当てはまらないなら別タスク定義に分け、個別にスケール・プロビジョニングできるようにします。「Webアプリ + ログルーター」のようなサイドカー構成は前者、「Webアプリ + バックエンドAPI」のように独立してスケールさせたいものは後者、が基本線です。

現場のコツ:タスク定義のJSONは必ずGit管理し、リビジョンではなくコードを正とします。コンソールで直接編集するとリビジョンだけが増えて再現性が失われます。IaCでの管理はIaC/CI/CDパイプラインの考え方と揃えておくと運用が一貫します。

02CPU/メモリの予約 — 予約と上限を分けて考える

Fargateではタスクレベルで cpumemory を指定し、その組み合わせは決められた表(例: 0.25 vCPU なら 0.5/1/2 GB など)から選びます。EC2 launch typeではコンテナレベルの memory(ハードリミット)と memoryReservation(ソフトリミット)を使い分けられるのが要点です。

例えば通常128 MiB、たまに256 MiBまでバーストするコンテナなら、memoryReservation を128 MiB、memory を300 MiBに設定します。こうすると配置計算では128 MiBしか消費せず、必要時には300 MiBまで使えます。タスクレベルのメモリを指定しない場合、各コンテナで memorymemoryReservation の少なくとも一方を非ゼロで指定する必要があり、両方指定するなら memorymemoryReservation より大きくしなければなりません。

現場のコツ:ソフトリミットだけで運用するとホスト逼迫時に「隣のコンテナのせいで落ちる」事故が起きます。本番ではアプリコンテナに現実的なハードリミット(memory)を必ず設定し、OOMの発生をCloudWatchで可視化しておくと原因追跡が早くなります。FargateかEC2かの判断はFargate vs EC2 launch typeを参照してください。

ECSタスク(awsvpc) app コンテナ essential: true awsfirelens driver log_router Fluent Bit sidecar stdout/stderr を forward(port 24224) taskRoleArn(アプリのAWS API権限) 実行ロール ECR pull / logs / secrets CloudWatch / Firehose ログ送信先 実行ロール=ECSエージェント用(起動時)/タスクロール=アプリ実行時
図1:アプリコンテナとFireLensサイドカー、実行ロール/タスクロールの役割分担

03サイドカーとFireLens — ログルーティングの型

サイドカーの代表例がFireLensによるログルーティングです。アプリのログ処理をインフラ層に切り離せるため、送信先の変更(CloudWatch → Firehose → S3 など)をアプリの再ビルドなしに設定だけで行えます。構成は次の3点セットです。

ECSは logConfiguration のオプションからFluent Bitの出力設定を自動生成し、match はFireLensが管理するのでタスク定義には書きません。イメージは public.ecr.aws/aws-observability/aws-for-fluent-bit を使います。デフォルトで ecs_cluster / ecs_task_arn / ecs_task_definition などのメタデータが自動付与され、不要なら enable-ecs-log-metadatafalse にできます。

現場のコツ:FireLensはポート 24224 でリッスンします。タスクのセキュリティグループでこのポートへのイングレスを絶対に許可しないでください(awsvpc ならタスクのSG、bridge なら 24224 のポートマッピングを作らない)。外部から到達可能だとログ経路が汚染されます。ログ設計全体は監視・アラート設計と合わせて詰めておくと過不足がありません。

04Secrets Manager / SSMからの環境注入

DB接続情報やAPIキーを平文で environment に書くのはアンチパターンです。DescribeTaskDefinition で誰でも読めてしまうためです。代わりに secrets パラメータで、値の参照(ARN)だけを持たせ、起動時にECSエージェントが解決してコンテナの環境変数に注入します。

形式は共通で {"name": "環境変数名", "valueFrom": "ARN"} です。ここで重要なのは、この解決を行うのはタスクロールではなく実行ロールだという点です。実行ロールに secretsmanager:GetSecretValuessm:GetParameters、KMS復号が必要なら kms:Decrypt を付与しないと、タスクは起動時に失敗します。どちらのストアを使うかは要件次第で、ローテーション必須ならSecrets Manager、単純な設定値でコストを抑えたいならParameter Storeが基本です。詳しくはSecrets Manager vs Parameter Storeを参照してください。

現場のコツ:ログにSecretsが漏れないよう、logConfigurationsecretOptions も同じ仕組みで注入します。またVPCエンドポイント経由でSecretsを解決する構成では、エンドポイント未整備が「起動時だけ失敗」の典型原因になります。マルチアカウント統制下では解決先アカウントとKMSキーポリシーも要確認です。

05ヘルスチェック — ECS・ALB・アプリの3層を混同しない

「ヘルスチェック」と一口に言っても、ECSコンテナヘルスチェック、ALBターゲットグループのヘルスチェック、アプリ内部のヘルスの3層があります。混同すると「ALBは正常判定なのにECSがタスクを殺し続ける」といった噛み合わない事故が起きます。タスク定義で設定するのは healthCheck パラメータで、コマンドの形式と各値の範囲は次のとおりです。

起動に時間のかかるJVMアプリなどでは startPeriod を適切に設定しないと、まだ立ち上がり中のコンテナがunhealthy扱いで殺され、再起動ループに陥ります。ALB側のヘルスチェックとは目的が異なる(ALBは「トラフィックを流してよいか」、ECSは「コンテナを生かすか殺すか」)ため、両者のパラメータは独立して調整します。

06ログ設定 — logConfigurationとドライバの選択

ログはアプリから stdout/stderr に書き、logConfiguration で送信先を決めるのが鉄則です。代表的なドライバは awslogs(CloudWatch Logs直送)と awsfirelens(FireLens経由)の2つです。

判断軸はシンプルで、「CloudWatch Logsに素直に集約できればawslogs、加工・振り分け・別宛先が要るならFireLens」です。CloudWatch Agentやtd-agentとの使い分けはCloudWatch Agent vs td-agentも参考になります。

07タスクロール vs 実行ロール — 混同が事故の温床

ECSには紛らわしい2つのIAMロールがあり、この違いを言語化できるかが実務の分かれ目です。

2つのロールは「使うタイミング」が違う 時間軸 → 実行ロール(executionRoleArn) 主体:ECS/Fargate エージェント タイミング:起動時 用途:ECR pull・ログ書込 secrets解決 タスクロール(taskRoleArn) 主体:アプリのコード タイミング:実行中 用途:S3・DynamoDB・SQS など業務API呼び出し よくある事故 アプリの権限を実行ロールに付けてしまう/secrets権限をタスクロールに付けて起動失敗
図2:実行ロールは「起動時にエージェントが使う」、タスクロールは「実行中にアプリが使う」

タスク実行ロール(executionRoleArn)は、ECS/Fargateエージェントがユーザーに代わってAWS APIを呼ぶためのロールです。ECRからのイメージpull、CloudWatch Logsへの書き込み、そして前述のSecrets/SSM解決に使われます。AWS管理ポリシー AmazonECSTaskExecutionRolePolicy がベースになります。

タスクロール(taskRoleArn)は、コンテナ内で動くアプリケーションのコードがAWSサービス(S3、DynamoDB、SQSなど)を呼ぶための権限です。アプリが業務で使う権限はすべてこちらに集約します。

現場のコツ:「起動に必要な権限=実行ロール、アプリが動作中に使う権限=タスクロール」と覚えます。secretsのGet権限を誤ってタスクロールに付けると、解決するのはエージェント(実行ロール)なので起動時に失敗します。最小権限の実際の落とし込みはIAM最小権限の現実を参照してください。

08graceful shutdown — SIGTERMとstopTimeoutの落とし穴

スケールインやデプロイでタスクが停止するとき、ECSはまず SIGTERM を送り、その後 SIGKILL を送ります。この間隔を決めるのが stopTimeout です。デフォルトは30秒、最大は120秒(Linux、プラットフォームバージョン 1.3.0 以降)です。アプリが SIGTERM を無視すると、ECSは SIGKILL まで待たされ、処理中のリクエストが途中で切られます。

ここで見落としがちなのが、DockerとECSは SIGTERMPID 1にしか送らない点です。シェル経由(sh -c "...")で起動するとシェルがPID 1になり、シグナルが子プロセスのアプリに伝わりません。exec 形式で起動するか、initProcessEnabled でinitプロセスを挟むことでゾンビプロセスとシグナル伝播の問題を回避します。アプリ側では SIGTERM を受けたら新規リクエストの受付を止め、処理中のものを完了させる(またはタスク外へ退避する)実装が必要です。

現場のコツ:ALBのdrainingと歩調を合わせるのが肝です。ターゲットグループの deregistration_delay.timeout_secondsstopTimeout、アプリのgraceful shutdownの3つが噛み合っていないと、無停止デプロイのつもりで502が出ます。EC2 launch typeではエージェントの ECS_CONTAINER_STOP_TIMEOUT も関係するため、長い処理を扱うサービスでは併せて確認してください。

まとめ

ECSタスク定義は、コンテナ1つを動かすだけならすぐ書けますが、本番品質にするには次の勘所を押さえる必要があります。

数値・上限はプラットフォームバージョンで変わり得るため、確定値は必ず公式ドキュメントで最新を確認してください。設計や運用の相談は導入事例もあわせてご覧のうえ、お問い合わせください。

参考(一次情報)

タスク定義の設計やコンテナ基盤の運用でお困りなら、お問い合わせください。手を動かす前提で一緒に設計します。

相談する
← ブログ一覧へ戻る