1. 背景 ─ CPU/メモリが違うだけのタスク定義が 2 つあった
対象は、当社が運用保守を担当している Rails 7 系 / ECS Fargate の業務システムです。Web は ECS サービスで 2 タスク常時起動、バッチは ECS の「スケジュールされたタスク」から 毎時・毎日あわせて 8 本の rake タスクを起動しています。
このシステムには myapp-prod(Web 用)と myapp-prod-batch(バッチ用)という 2 つのタスク定義 family がありました。2 つの最新リビジョンを describe-task-definition で取り出して比べると、差分は次の通りです(環境変数は値をハッシュ化して比較しました)。
| 項目 | myapp-prod(Web) | myapp-prod-batch(バッチ) |
|---|---|---|
| イメージ | 同じ(prod-<commit sha> の不変タグ) | |
| 環境変数 | 22 個すべて同じキー・同じ値 | |
| タスクロール / 実行ロール / ネットワークモード | 同じ | |
| CPU / メモリ | 1 vCPU / 4 GiB | 2 vCPU / 8 GiB |
| ロググループ(awslogs) | /ecs/myapp-prod | /ecs/myapp-prod-batch |
実質的な差分は CPU/メモリだけ(ロググループは §3 で触れます)。それでもタスク定義が 2 つあるせいで、デプロイのたびにバッチ側を手で登録し直し、8 本のルールが参照するリビジョンも直す、という手作業が発生していました。「バッチだけ大きなタスクサイズで起動できれば、タスク定義は 1 つで済む」── これが本記事の出発点です。
CPU/メモリ、コマンド、環境変数、タスクロールは RunTask 時に上書きできます(§3)。これらだけが理由なら、タスク定義を分ける必要はありません。
2. 仕組み ─ Input は RunTask の overrides としてそのまま渡る
2-1. スケジュールされたタスクの実体は EventBridge ルール
ECS コンソールの「スケジュールされたタスク」タブで作るものは、内部的には EventBridge のルール(cron 式)+ ECS クラスターをターゲットにした設定です。aws events list-targets-by-rule で見ると、ターゲットには次の情報が入っています。
Arn:起動先の ECS クラスターRoleArn:EventBridge が RunTask を呼ぶためのロール(ecsEventsRoleなど)EcsParameters:タスク定義 ARN、起動タイプ、ネットワーク設定、タスク数Input:RunTask に渡す overrides(JSON 文字列)
cron の時刻になると EventBridge がこの内容で ecs:RunTask を呼びます。ルールを更新してもバッチが動くわけではなく、次の cron 時刻に、そのとき設定されている内容で起動するだけです。
2-2. Input に cpu / memory を書く
ポイントは Input です。EventBridge のドキュメントでは、ECS ターゲットへの入力を RunTask の TaskOverride の構造に合わせれば、そのまま 1 対 1 で RunTask に渡されると説明されています。多くの人が containerOverrides でコマンドを差し替える用途にしか使っていませんが、TaskOverride にはタスクレベルの cpu / memory も含まれます。つまり、Input に次のように書くだけで、タスク定義を触らずに その起動だけ CPU/メモリを変えられます。
{
"cpu": "2048",
"memory": "8192",
"containerOverrides": [
{
"name": "Rails",
"command": ["bin/rails", "sync:hourly"]
}
]
}当社の構成では、タスク定義 myapp-prod は 1 vCPU / 4 GiB のままで、8 本のルールすべての Input に cpu: 2048 / memory: 8192 を入れています。起動されたバッチタスクの詳細画面では「Task definition: myapp-prod:607」「CPU | memory: 2 vCPU | 8 GiB」と表示され、タスク定義の値ではなく Input の値でタスクが確保されていることが確認できます(§4-3)。
3. 上書きできるもの・できないもの
3-1. TaskOverride / ContainerOverride の範囲
「何が上書きできるか」は TaskOverride / ContainerOverride の API 仕様で決まります。タスク定義を統合する判断のためにまとめておきます。
| 項目 | RunTask で上書き | 備考 |
|---|---|---|
| タスクの cpu / memory | できる | タスクレベル。Fargate の有効な組み合わせであること |
| ephemeralStorage | できる | タスクレベル(Fargate) |
| taskRoleArn / executionRoleArn | できる | 呼び出し側に該当ロールの iam:PassRole が必要 |
| コンテナの command / environment | できる | containerOverrides。environment は上書きするキーだけ書く |
| コンテナの cpu / memory / memoryReservation | できる | containerOverrides。コンテナ定義側にハード制限があるときはこちらも上げる |
| logConfiguration(ロググループ等) | できない | 統合するとバッチのログは Web と同じロググループに入る |
| healthCheck / portMappings / volumes | できない | Web 用の設定がバッチにもそのまま付いてくる |
当社のケースではロググループが分かれなくなる点だけがトレードオフでしたが、ログはタスクごとに別ストリームになり、Logs Insights で絞り込めるため許容しました。ロググループの分離自体が要件なら、その点だけはタスク定義を分ける理由として残ります。
3-2. Fargate の組み合わせ制約とコンテナ側の memory
上書き値は Fargate がサポートする CPU/メモリの組み合わせでなければ RunTask 自体が失敗します(例:2 vCPU なら 4〜16 GiB の 1 GiB 刻み)。タスク定義より大きくも小さくもできますが、組み合わせ表の範囲内で指定してください。
もう 1 つの注意点はコンテナ定義側の memory(ハード制限)です。当社のタスク定義ではコンテナに memory を設定していなかったのでタスクレベルの上書きだけで済みましたが、コンテナに memory: 3072 のような制限がある場合、タスクを 8 GiB にしてもコンテナは 3 GiB で OOM kill されます。その場合は containerOverrides[].memory も併せて上書きしてください。
4. 設定手順(AWS CLI)
8 本のルールに同じ変更を入れるので、コンソールではなく CLI でまとめて処理しました。実行環境は AWS CloudShell が便利です。コンソールのサインインがそのまま使え、aws と jq が入っています。
4-1. 現状のターゲットをバックアップする
対象ルールは「ECS クラスターをターゲットにしているルール」なので、list-rule-names-by-target にクラスター ARN を渡すと一覧できます。まず全ルールのターゲットをファイルに落とします。これがそのままロールバック用の入力になります。
CLUSTER=arn:aws:ecs:ap-northeast-1:123456789012:cluster/my-cluster
mkdir -p backup
for rule in $(aws events list-rule-names-by-target --target-arn $CLUSTER --query RuleNames --output text); do
aws events list-targets-by-rule --rule "$rule" --query Targets > "backup/$rule.json"
done4-2. jq で差し替えて put-targets
バックアップした JSON のうち、myapp-prod-batch を参照しているターゲットだけを選び、(1)タスク定義 ARN を Web 用に、(2)Input に cpu/memory を追加、の 2 点を変えて put-targets で書き戻します。コマンド上書き、ネットワーク設定、ロールはそのまま引き継がれます。
NEW=arn:aws:ecs:ap-northeast-1:123456789012:task-definition/myapp-prod:607
for f in backup/*.json; do
rule=$(basename "$f" .json)
jq --arg arn "$NEW" '[.[]
| select((.EcsParameters.TaskDefinitionArn // "") | test("/myapp-prod-batch:"))
| .EcsParameters.TaskDefinitionArn = $arn
| .Input = (.Input | fromjson | .cpu = "2048" | .memory = "8192" | tojson)]' "$f" > new.json
[ "$(jq length new.json)" -gt 0 ] || continue
echo "== $rule"; jq -c '.[] | {TaskDefinitionArn: .EcsParameters.TaskDefinitionArn, Input}' new.json
aws events put-targets --rule "$rule" --targets file://new.json
doneput-targetsの応答は必ずFailedEntryCount: 0を確認する。0 でなければそのルールは更新されていません- 最初は
put-targetsの行をコメントアウトして、echoの出力だけ眺める(dry run) - 戻すときは
aws events put-targets --rule <rule> --targets file://backup/<rule>.json
4-3. 動作確認
ルールの更新でバッチが動くわけではないので、確認は次の cron 時刻を待ちます。当社は毎時 10 分の連携バッチで確認しました。見るポイントは次の通りです。
- ECS のタスク詳細で Task definition が
myapp-prod:607、CPU | memory が 2 vCPU | 8 GiB、Started by がルール名になっている - コンテナの終了コードが 0(
describe-tasksのcontainers[].exitCode) describe-tasksの出力にoverrides.cpu/overrides.memoryと、上書き後のcpu/memoryの両方が入っている
5. 注意点
5-1. コンソールのフォームから保存しない
ECS コンソールのスケジュールされたタスクの Update 画面には、タスク定義のリビジョン、ネットワーク、タスクロールの上書き、コンテナの上書き(コマンド・環境変数)はありますが、タスクレベルの cpu / memory を入力する欄はありません。Input に書いた cpu / memory がこのフォームからの保存でどう扱われるかは保証がないため、統合後のルールは CLI でだけ触る運用にしました。設定済みの値を見るだけなら、EventBridge コンソールのルール詳細 → Targets → 「Input to target: Constant」の View で JSON をそのまま確認できます。
5-2. put-targets はターゲット丸ごと置換
put-targets は同じ Id のターゲットを 指定した内容で丸ごと置き換えます。TaskDefinitionArn だけ渡すと、Input、NetworkConfiguration、RoleArn が消えたターゲットになります。必ず list-targets-by-rule の出力を土台にして、変えたい項目だけ書き換えて渡してください(§4-2 のスクリプトはそうしています)。
5-3. 「Web ごと大きくする」案との料金比較
上書きを使わずに タスク定義自体を 2 vCPU / 8 GiB に上げるのが一番簡単な統合ですが、Web の 2 タスクが常時その料金になります。Fargate(東京・Linux/x86)の公開単価で概算すると次の差になります。
| 案 | Web(2 タスク常時) | 月額の増分(概算) |
|---|---|---|
| タスク定義を 2 vCPU / 8 GiB に上げる | 1 vCPU / 4 GiB → 2 vCPU / 8 GiB | +約 $106 |
| Input で上書き(本記事) | 1 vCPU / 4 GiB のまま | $0 |
※ 1 vCPU $0.05056/h + 4 GiB × $0.00553/h ≒ $0.0727/h、× 730 h × 2 タスク ≒ $106 として計算。バッチ側は起動中だけ課金され、毎時 2 分程度の実行なら 2 vCPU / 8 GiB でも月 $4 程度です。単価は 2026 年 9 月時点の公開料金に基づく概算で、最新値は AWS 公式の料金ページでご確認ください。
あとがき
EventBridge の Input が RunTask の TaskOverride にそのまま渡る、という仕様は知っていれば当たり前ですが、コンソールのフォームには cpu / memory の欄が出てこないため見落とされがちです。「バッチだけ大きくしたい」だけの理由でタスク定義を分けているなら、Input の数行で済みます。
ECS のネットワーク周りでは、姉妹記事 ECS の固定 IP を維持したまま NAT Gateway を fck-nat に置き換えた記録 もあわせてどうぞ。AWS の運用設計・コスト診断のご相談は info@moobon.jp までお気軽にご連絡ください。
よくある質問
QECS コンソールの「スケジュールされたタスク」画面から、タスクレベルの CPU/メモリを上書きできますか?
2026 年 9 月時点の ECS コンソール(スケジュールされたタスクの Update 画面)にあるのは、タスク定義 family / リビジョン、タスク数、ネットワーク、タスクロールの上書き、EventBridge の IAM ロール、そしてコンテナの上書き(コマンド・環境変数)で、タスクレベルの cpu / memory を入力する欄はありません。タスクレベルの上書きは EventBridge ルールのターゲットに渡す Input(JSON)に直接書く必要があり、当社は AWS CLI の put-targets で設定しています。設定済みの値は、EventBridge コンソールのルール詳細 → Targets → 「Input to target: Constant」の View で JSON として確認できるほか、aws events list-targets-by-rule でも読めます。
Qタスク定義より小さい CPU/メモリに上書きすることもできますか?
できます。RunTask の overrides に指定する cpu / memory はタスク定義の値と独立しており、Fargate で有効な組み合わせであれば大きくも小さくもできます。本記事では Web 用のタスク定義(1 vCPU / 4 GiB)をバッチ側で 2 vCPU / 8 GiB に引き上げていますが、逆に軽いバッチを 0.5 vCPU / 1 GiB に絞って料金を抑える使い方も同じ仕組みで成立します。ただし、コンテナ定義側に memory(ハード制限)が設定されている場合、タスクレベルを上げてもコンテナはその制限で頭打ちになるため、containerOverrides 側の memory も併せて上書きしてください。
Qログ出力先(logConfiguration)もルールごとに変えられますか?
変えられません。RunTask の TaskOverride で上書きできるのは cpu / memory / ephemeralStorage / taskRoleArn / executionRoleArn とコンテナごとの command / environment / cpu / memory などで、awslogs のロググループやストリームプレフィックスは対象外です。そのため、タスク定義を 1 つにするとバッチのログは Web と同じロググループに入ります。ロググループを分けることが要件なら、その点だけはタスク定義を分ける理由になります。
Qコンテナの環境変数もルールごとに上書きできますか?その場合の注意点は?
containerOverrides の environment で上書きできます(上書きするキーだけ書けば、それ以外はタスク定義の値が使われます)。注意点は、ルール側に書いた値はタスク定義より優先されることです。後からタスク定義側の値を変えても、そのキーをルールで上書きしている限りルールの古い値が使われ続けます。ルールで上書きするのは本当にバッチ固有のキーだけに絞り、シークレットのようにタスク定義側で一元管理したい値はルールに書かないことをおすすめします。
