「コンテナは本番で動かしたい。でもKubernetesクラスタの面倒は見たくないし、EC2のパッチ当てやスケーリングにも時間を割けない」——スタートアップや一人開発で本番のコンテナ基盤を組むとき、ほぼ必ずここに行き着きます。その答えが AWS Fargate です。
私は経済産業大臣賞を受賞した木材流通SaaSで、API Gateway → NLB → ALB → ECS on Fargate という構成の上に221本のAPIエンドポイントを本番運用してきました。決済基盤(本番二重課金0件)のワーカー群もFargateで動いています。サーバーを1台も触らずに、HTTPサービス・バッチ・イベント駆動ワーカーを同じ仕組みで回せることが、少人数で本番品質を出すための土台になっています。
この記事は、AWS公式ドキュメントに忠実でありながら、公式よりわかりやすく、かつ「どの場面でどう使うか」を実コードで示すことを目的にしています。タスク設計・ネットワーキング・デプロイ・回復性・セキュリティ・コストまで、本番に出すために必要なことを一気通貫で扱います。技術選定そのもの(ECSかEKSか)は、別記事の ECS on Fargate vs EKS:スタートアップの意思決定フレームワーク を参照してください。本稿は**「ECS on Fargateを選んだ後、本番でどう作るか」**に集中します。
Fargate とは何か:EC2起動タイプとの違い
Amazon ECS(Elastic Container Service)には、コンテナを動かす計算リソース(起動タイプ / 容量)として大きく2つあります。
- EC2 起動タイプ:自分でEC2インスタンス(コンテナインスタンス)の群れを用意し、その上にタスクを詰める。OSのパッチ、インスタンスのスケーリング、ビンパッキング(詰め込み効率)の管理が自分の責任。
- Fargate:CPUとメモリを指定するだけで、AWSがその裏側のインスタンスを用意・パッチ・スケールするサーバーレス方式。サーバーという概念自体が消える。
公式の定義はシンプルです。
AWS Fargate is a technology that you can use with Amazon ECS to run containers without having to manage servers or clusters of Amazon EC2 instances.(— AWS Fargate)
セキュリティ上、最も重要な一文がこれです。
Each Fargate task has its own isolation boundary and does not share the underlying kernel, CPU resources, memory resources, or elastic network interface with another task.
つまり Fargateの各タスクは独立した分離境界を持ち、カーネルもCPUもメモリもENI(Elastic Network Interface)も他タスクと共有しません。EC2起動タイプでは複数タスクが1インスタンスのカーネルを共有しますが、Fargateは「タスク=最小の隔離単位」です。マルチテナントや厳格な分離要件があるなら、これは決定的な利点になります。
比較表:いつFargateでいつEC2か
| 観点 | Fargate | EC2 起動タイプ |
|---|---|---|
| サーバー管理 | 不要(パッチ・AMI更新もAWS側) | 自分でOS/AMI/パッチを運用 |
| スケーリング | タスク数だけ考えればよい | インスタンス群とタスクの二段スケール |
| 分離境界 | タスク単位で完全分離 | タスクはインスタンスのカーネルを共有 |
| 課金単位 | vCPU秒・メモリ秒の従量(割り当て量) | インスタンス時間(使用率に関わらず固定) |
| 起動の速さ | 数十秒(ENI割当含む) | インスタンスに空きがあれば速い |
| GPU / 特殊インスタンス | 非対応 | 対応(GPU、Inferentia等) |
| デーモン型(各ホスト1個) | 非対応(ホストの概念がない) | DAEMONスケジューリング可 |
| 常時高負荷の原価 | 使用率が高いと割高になりうる | 高使用率なら有利。Savings Plansも併用 |
指針:迷ったらFargate。サーバー管理コスト(=人件費)こそ最大のコストだからです。EC2に戻すべきなのは「GPUが要る」「常時CPU 80%超で回し続けるバッチ群があり原価が支配的」「各ホストに1個だけ常駐させるエージェント(DaemonSet相当)が必要」といった明確な理由があるときだけです。
どんな場面で使うか:3つの典型ワークロード
Fargateは「Webサーバー専用」ではありません。実務では次の3形態を同じ語彙(タスク定義)で扱えるのが強みです。
- 常駐サービス(Service):ALB/NLBの背後でHTTP APIやWebアプリを常時起動。
desiredCountで冗長化し、オートスケールする。← 最も一般的。 - スケジュールタスク(バッチ):EventBridge Schedulerでcron実行する日次集計・レポート生成・データ同期。サービスではなく**単発タスク(RunTask)**として走り、終わると課金が止まる。
- イベント駆動ワーカー:SQSのキュー長に応じてタスク数をスケールさせる非同期処理。決済のWebhook処理や画像変換など。冪等性とグレースフルシャットダウンが本質になる。
決済基盤では「常駐APIサービス」と「SQS駆動の冪等ワーカー」を両方Fargateに載せ、Webhookの順不同・重複到達を冪等性キーで吸収しました。同じデプロイ基盤で3形態を回せることが、運用の認知負荷を劇的に下げます。
コアな構成要素:4つの登場人物の関係
ECSは用語が多くて最初に混乱します。本質は4つだけです。
Cluster(論理的な箱:複数サービスをまとめる名前空間)
└── Service("常にN個のタスクを保つ"宣言=望ましい状態のコントローラ)
└── Task(実行中の1単位。1つ以上のコンテナの集合)
└── Container(あなたのアプリのイメージ)
↑
Task Definition(タスクの設計図:イメージ・CPU/メモリ・IAM・ログ・環境変数)
- Task Definition(タスク定義):不変の「設計図」。リビジョン番号で版管理される(
my-app:1,my-app:2...)。デプロイとは新しいリビジョンを登録してサービスに差し替えること。 - Task(タスク):タスク定義から起動した実体。1タスク=1つのENI(
awsvpcモード)=1つのプライベートIP。 - Service(サービス):「このタスク定義のタスクを常に
desiredCount個、健全に保て」という宣言的コントローラ。タスクが落ちれば自動で再起動し、ALBへの登録/解除も面倒を見る。 - Cluster(クラスタ):サービスやタスクを束ねる論理境界。Fargateではクラスタに「サーバー」は存在せず、ただの名前空間に近い。
この「Serviceが望ましい状態を保ち続ける」という宣言的モデルが、KubernetesのDeploymentと同じ発想です。だからこそ手でdocker runするのではなく、状態を宣言して任せるのが正しい使い方になります。
タスクサイズ設計:CPUとメモリは"組み合わせが固定"
Fargate最大の落とし穴がここです。CPUとメモリは自由な組み合わせではなく、決められたペアからしか選べません。公式の組み合わせ(Task CPU and memory)はこうです。
| CPU(タスク全体) | 選べるメモリ | 刻み |
|---|---|---|
| 256(.25 vCPU) | 512 MiB / 1 GB / 2 GB | 固定3択 |
| 512(.5 vCPU) | 1〜4 GB | 1 GB刻み |
| 1024(1 vCPU) | 2〜8 GB | 1 GB刻み |
| 2048(2 vCPU) | 4〜16 GB | 1 GB刻み |
| 4096(4 vCPU) | 8〜30 GB | 1 GB刻み |
| 8192(8 vCPU) | 16〜60 GB | 4 GB刻み(PV 1.4.0+) |
| 16384(16 vCPU) | 32〜120 GB | 8 GB刻み(PV 1.4.0+) |
CPUは
1024(CPU単位)でも1 vCPUでも指定でき、メモリは3072(MiB)でも3 GBでも指定できます。登録時に内部単位へ変換されます。
実務での効き方:たとえば「メモリは512 MiBで足りるが、CPUは1 vCPU欲しい」というワークロードでも、1024 CPUを選んだ瞬間にメモリは最低2 GBを確保する(=課金される)ことになります。逆も同様で、「8 GBメモリが要る」なら最低でも1 vCPUがついてきます。だからサイズは"計測してから"決めるのが鉄則です。憶測で大きく取ると、使わないリソースに毎秒課金され続けます。
エフェメラルストレージ(一時ディスク)
Fargateタスクには既定で 20 GB のエフェメラルストレージが付きます。ビルド成果物・一時ファイル・キャッシュに使えます。足りなければタスク定義のephemeralStorageで 最大200 GB まで拡張できます(プラットフォームバージョン1.4.0以降)。タスク終了で消える揮発領域なので、永続化が要るならEFSやS3を使います。
プラットフォームバージョンは LATEST(=Linux 1.4.0)
The LATEST Linux platform version is
1.4.0.(— Fargate platform versions)
1.4.0はエフェメラルストレージ拡張・systemControls・UDP NLBなどに必要です。特別な理由がなければLATESTを使う。新規タスクは常に最新リビジョンのインフラ(パッチ適用済み)で起動するため、セキュリティ的にもこれが既定の安全側です。ARM64(Graviton)ワークロードもサポートされ、cpuArchitectureにARM64を指定できます(後述のコスト最適化で効きます)。
ネットワーキング:awsvpc と ALB の正しい繋ぎ方
Fargateは**awsvpcネットワークモード固定**です。各タスクが専用のENIとプライベートIPを持つため、EC2のように「ホストのポートをマッピングする」概念がありません。ここを誤解するとALB連携で必ず詰まります。
重要な3点:
- ALBのターゲットグループは
target_type = "ip"。instanceではありません。タスクはEC2インスタンスではなくENIに紐づくため、IPターゲットで登録されます(公式明記)。 - セキュリティグループはタスクのENIに付く。「ALBのSG → タスクのSG(アプリのポート)だけ許可」と最小化する。タスクのSGはインバウンドをALBのSGに限定し、0.0.0.0/0を開けない。
- 配置はプライベートサブネット + NAT Gatewayが本番の定石。
assignPublicIp=ENABLEDでパブリックサブネットに直接置くこともできるが、攻撃面が広がる。ECR/CloudWatch/Secrets ManagerへはVPCエンドポイント or NAT経由で到達させる。
リクエストの流れはこうなります。
Internet → ALB(public subnet) → Target Group(type=ip)
→ Task ENI(private subnet, SG=ALBのSGのみ許可) → container:8080
↘ NAT GW → ECR / Secrets Manager / CloudWatch
サービス検出(サービス間通信)が必要なら、ECS Service Connect(または Cloud Map)でDNS名による名前解決を使い、内部通信にALBを増やさず疎結合にします。
実装①:最小構成のタスク定義(JSON)
まずは"何が必須か"を最小のタスク定義で掴みます。要点はコメントに記しました。
{
"family": "web-api",
"requiresCompatibilities": ["FARGATE"],
"networkMode": "awsvpc",
"cpu": "512",
"memory": "1024",
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
},
"executionRoleArn": "arn:aws:iam::111122223333:role/web-api-exec",
"taskRoleArn": "arn:aws:iam::111122223333:role/web-api-task",
"containerDefinitions": [
{
"name": "app",
"image": "111122223333.dkr.ecr.ap-northeast-1.amazonaws.com/web-api:1a2b3c4",
"essential": true,
"user": "10001:10001",
"readonlyRootFilesystem": true,
"linuxParameters": { "initProcessEnabled": true },
"portMappings": [{ "containerPort": 8080, "protocol": "tcp" }],
"environment": [{ "name": "NODE_ENV", "value": "production" }],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:111122223333:secret:prod/db-Ab12Cd"
}
],
"stopTimeout": 60,
"healthCheck": {
"command": ["CMD-SHELL", "wget -q -O - http://localhost:8080/healthz || exit 1"],
"interval": 15,
"timeout": 5,
"retries": 3,
"startPeriod": 30
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/web-api",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "app"
}
}
}
]
}
本番で効くポイント:
- **
imageはタグではなくダイジェスト相当の不変参照(CommitSHAタグ)**を使う。latestは再現性を壊す。ECSは既定でversionConsistency: enabledによりタグをダイジェストへ解決し、サービス内の全タスクが同一イメージで動くことを保証する。 userで非root実行 +readonlyRootFilesystem: true。書き込みが要るパスだけvolumesでtmpfsを当てる。攻撃面を最小化する基本。secrets[].valueFromでSecrets Manager / SSM Parameter Storeから機密を注入。環境変数に平文で書かない(後述)。stopTimeoutは既定30秒、最大120秒。グレースフルシャットダウンの猶予(後述)。healthCheck.startPeriodで起動直後の猶予を与え、初期化中の誤検知による強制終了を防ぐ。
実装②:Terraformで本番サービス一式
タスク定義単体では本番になりません。クラスタ・サービス・ALB・SG・ログ・オートスケールをIaCで宣言してこそ「壊れない・再現できる」状態になります。Terraformで一式を組みます(要点に絞った構成)。Terraformのモジュール設計・state分離・ドリフト検知は別記事に譲り、ここはECS固有部分に集中します。
# --- クラスタ:Container Insights を有効化(可観測性の土台) ---
resource "aws_ecs_cluster" "main" {
name = "prod"
setting {
name = "containerInsights"
value = "enhanced" # 拡張オブザーバビリティ。コスト許容なら本番推奨
}
}
# --- タスクのSG:インバウンドは ALB のSGからのみ ---
resource "aws_security_group" "task" {
name_prefix = "web-api-task-"
vpc_id = var.vpc_id
lifecycle { create_before_destroy = true }
}
resource "aws_vpc_security_group_ingress_rule" "from_alb" {
security_group_id = aws_security_group.task.id
referenced_security_group_id = aws_security_group.alb.id
ip_protocol = "tcp"
from_port = 8080
to_port = 8080
}
resource "aws_vpc_security_group_egress_rule" "all_out" {
security_group_id = aws_security_group.task.id
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0" # NAT経由でECR/Secrets/CloudWatchへ
}
# --- ALB ターゲットグループ:Fargateは必ず target_type = "ip" ---
resource "aws_lb_target_group" "app" {
name = "web-api"
port = 8080
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
deregistration_delay = 30 # 接続ドレイン。既定300sは過剰なことが多い
health_check {
path = "/healthz"
healthy_threshold = 2
unhealthy_threshold = 3
interval = 15
timeout = 5
matcher = "200"
}
}
# --- サービス:ローリング更新+デプロイサーキットブレーカー ---
resource "aws_ecs_service" "app" {
name = "web-api"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
launch_type = "FARGATE"
platform_version = "LATEST"
enable_execute_command = true # ECS Exec によるブレークグラス
deployment_minimum_healthy_percent = 100
deployment_maximum_percent = 200
deployment_circuit_breaker {
enable = true
rollback = true # 失敗を検知したら前リビジョンへ自動ロールバック
}
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.task.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "app"
container_port = 8080
}
health_check_grace_period_seconds = 30
}
desired_count = 2・minimum_healthy_percent = 100・maximum_percent = 200 の組み合わせは、「常に2タスクを健全に保ったまま、最大4タスクまで一時的に増やして新版を立ち上げ、健全化を確認してから旧版を落とす」という無停止ローリングを意味します。次節で正確に説明します。
デプロイ:ローリング更新とデプロイサーキットブレーカー
ECSの既定デプロイは**ローリング更新(ECSタイプ)**です。動きは2つのパラメータで決まります(公式)。
minimumHealthyPercent:デプロイ中に「健全に動いていなければならない」タスク数の下限(%、切り上げ)。例:min 50%・desired 4なら、新版2個を起動する前に旧版を2個まで停止できる。maximumPercent:デプロイ中に「起動してよい」タスク数の上限(%、切り下げ)。例:max 200%・desired 4なら、旧版4個を止める前に新版4個を起動できる。
min 100% / max 200% は最も安全側で、可用性を一切落とさずに新版を立ち上げ切ってから旧版を落とします(その分、一時的にリソースが倍になる)。コスト優先ならmin 50% / max 100%でリソースを増やさず入れ替える選択もあります。
失敗を"自動で巻き戻す"のがサーキットブレーカー
ここが本番品質の分かれ目です。新版がクラッシュループしているのに気付かずトラフィックを流す事故を、デプロイサーキットブレーカーが防ぎます。
Both methods support rolling back to the previous service revision.(— 公式。サーキットブレーカーとCloudWatchアラームのいずれも前リビジョンへのロールバックに対応)
deployment_circuit_breaker { enable = true, rollback = true } を入れておけば、新タスクが規定回数立ち上がらない(ヘルスチェックを通らない)場合にデプロイを失敗扱いにして自動で前の正常なリビジョンへ戻します。さらにアプリのビジネスメトリクス(エラー率など)を基準にしたいなら、CloudWatchアラーム連動を併用できます。両方有効にすると、どちらかの条件を満たした時点で失敗・ロールバックされます。
イメージダイジェストによる版の一貫性
ECSは既定でタグをイメージダイジェストに解決し、サービス内の全タスクが同一バイナリで動くことを保証します(versionConsistency)。「ビルドし直したらlatestの中身が変わって一部タスクだけ別物」という事故を構造的に防ぎます。CommitSHAをタグにし、latestに依存しない運用と合わせて、再現性を担保しましょう。
CI/CDはOIDCによる鍵レスで組むのが2026年の標準です(長期アクセスキーを置かない)。具体は GitHub Actions OIDC で鍵レスCI/CD を参照してください。デプロイ自体は新リビジョンを登録してaws ecs update-service --force-new-deploymentで差し替えるか、amazon-ecs-deploy-task-definitionアクションを使います。
回復性・冪等性:SIGTERMを受けて"綺麗に終わる"
Fargateで最も見落とされ、最も事故を生むのがグレースフルシャットダウンです。デプロイ・スケールイン・Fargate Spot中断のたびにタスクは停止します。そのときECSは次の手順を踏みます。
- タスクをALBターゲットから登録解除(新規リクエストを止め、
deregistration_delayの間、処理中の接続を待つ)。 - コンテナに
SIGTERMを送る。 stopTimeout(既定30秒・最大120秒) 待つ。- それでも終わらなければ
SIGKILLで強制終了。
The SIGTERM signal must be received from within the container to perform any cleanup actions. Failure to process this signal results in the task receiving a SIGKILL signal after the configured
stopTimeoutand may result in data loss or corruption.(— 公式)
つまりアプリがSIGTERMを握って、処理中のリクエストを捌き切り、DB接続やキュー受信を綺麗に閉じる責任がある。これを怠ると、デプロイのたびに進行中の処理がSIGKILLで殺され、データ破損やWebhookの取りこぼしが起きます。Node.jsならこう書きます。
// graceful-shutdown.ts — SIGTERM を握って in-flight を捌き切る
import http from "node:http";
export function installGracefulShutdown(
server: http.Server,
opts: { drainMs: number; onClose: () => Promise<void> },
): void {
let shuttingDown = false;
const shutdown = async (signal: NodeJS.Signals): Promise<void> => {
if (shuttingDown) return; // 二重発火を冪等に無視
shuttingDown = true;
console.info({ msg: "shutdown:start", signal });
// 1) 新規接続を止める。処理中のレスポンスは待つ
server.close(() => console.info({ msg: "shutdown:http-closed" }));
// 2) drain 上限を stopTimeout より短く張る(SIGKILL より先に終える)
const deadline = new Promise<void>((r) => setTimeout(r, opts.drainMs));
// 3) DB プール・キュー consumer など外部資源を閉じる
await Promise.race([opts.onClose(), deadline]);
console.info({ msg: "shutdown:done" });
process.exit(0);
};
process.on("SIGTERM", shutdown); // ECS が送るのはこれ
process.on("SIGINT", shutdown); // ローカル Ctrl-C 用
}
drainMsはstopTimeoutより短く設定するのが鉄則です(例:stopTimeout: 60 に対し drainMs: 50_000)。SIGKILLが来る前に自分から綺麗にexit(0)するためです。linuxParameters.initProcessEnabled: trueを入れておくと、PID 1のゾンビプロセス問題(シグナルが正しく伝播しない)も回避できます。
冪等性との関係:SQS駆動ワーカーなら「途中で殺されても、再配信されたメッセージを二重処理しない」設計が必須です。これはFargate固有ではなく分散処理一般の話で、冪等な非同期処理の原則がそのまま効きます。グレースフルシャットダウン(取りこぼさない)と冪等性(二重処理しない)は両輪です。
オートスケーリング:計測値に追従させる
Fargateの水平スケールは Application Auto Scaling のターゲット追跡(Target Tracking) で組みます。「CPU使用率を60%に保て」のように目標値を宣言すると、超えればdesiredCountを増やし、下回れば減らします。
resource "aws_appautoscaling_target" "app" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
scalable_dimension = "ecs:service:DesiredCount"
min_capacity = 2
max_capacity = 20
}
resource "aws_appautoscaling_policy" "cpu" {
name = "cpu-tt"
policy_type = "TargetTrackingScaling"
service_namespace = aws_appautoscaling_target.app.service_namespace
resource_id = aws_appautoscaling_target.app.resource_id
scalable_dimension = aws_appautoscaling_target.app.scalable_dimension
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 60.0
scale_in_cooldown = 120
scale_out_cooldown = 30 # 増やすのは速く、減らすのは慎重に
}
}
メトリクス選び:CPUバウンドならECSServiceAverageCPUUtilization、メモリなら...MemoryUtilization。HTTPトラフィックに素直に追従させたいなら**ALBRequestCountPerTarget**(1ターゲットあたりのリクエスト数)が最も実感に合います。scale_out_cooldownを短く、scale_in_cooldownを長くするのが定石——増やすのは即座に、減らすのは慎重に(スパイク直後に縮めて再び慌てる「フラッピング」を防ぐ)。
可観測性:止まった処理を一目で追える状態に
少人数運用ほど可観測性が生命線です。Fargateでは次を最初から仕込みます。
- Container Insights:CPU/メモリ/ネットワーク/タスク数を自動収集。
enhancedにすると、より細かいコンテナ単位のメトリクスが取れる。 - ログ:
awslogsドライバでCloudWatch Logsへ。JSON構造化ログにして相関ID(リクエストID)を必ず通す。高度な要件(複数宛先・パース・フィルタ)には**FireLens(Fluent Bit)**を使い、CloudWatch/S3/OpenSearch等へルーティングする。 - トレース:分散トレーシングはOpenTelemetryでサイドカー収集する。ECS上でのSRE実践は OpenTelemetry × ECS の可観測性 に詳述しました。
ECS Exec:本番コンテナへの"ブレークグラス"
SSHもポート開放も鍵管理もなしに、動いているコンテナの中へ入って調査できるのがECS Execです。
in production scenarios, you can use it to gain break-glass access to your containers to debug issues.(— ECS Exec)
# サービス/タスクで enableExecuteCommand を有効化した上で:
aws ecs execute-command \
--cluster prod \
--task <task-id> \
--container app \
--interactive \
--command "/bin/sh"
仕組みはSSM Session Managerで、操作はCloudTrailに記録され、コマンドと出力をCloudWatch/S3へ監査ログとして残せます。タスクロールにssmmessages:*の4アクション(CreateControlChannel/CreateDataChannel/OpenControlChannel/OpenDataChannel)が必要です。IAMの条件キー(ecs:container-name等)で本番コンテナへのExecだけ拒否するといった細かな統制もかけられます。「誰が・いつ・どのタスクに入ったか」を残せるのが、SSHにはない監査性です。
セキュリティ:実行ロールとタスクロールを混同しない
ここはFargate本番で最も間違えられるポイントです。IAMロールが2種類あり、役割が全く違います。
| ロール | 誰が使う | 何のため | 典型的な権限 |
|---|---|---|---|
実行ロール(executionRoleArn) | ECS/Fargateエージェント | タスクを"起動するため" | ECRからイメージpull、CloudWatch Logsへ書込、Secrets Manager/SSMから機密を取得して注入 |
タスクロール(taskRoleArn) | あなたのアプリコード | 実行中にAWS APIを呼ぶため | S3読み書き、DynamoDB、SQS送受信などアプリが必要な最小権限 |
公式の区別は明快です。
The permissions granted in the IAM role are vended to containers running in the task. This role allows your application code to use other AWS services.(— タスクロール) These permissions aren't accessed by the Amazon ECS container and Fargate agents. For the IAM permissions that Amazon ECS needs to pull container images and run the task, see Amazon ECS task execution IAM role.(— 実行ロールとの違い)
原則:
- シークレットの取得は実行ロールに寄せる(
secrets[].valueFromの解決はエージェントが起動時に行うため)。アプリが実行中に直接Secrets Managerを叩くなら、その分はタスクロールに付ける。 - アプリのAWSアクセスはタスクロール。サービス/タスク定義ごとに専用ロールを作り、最小権限にする。「全タスク共通の何でもできるロール」は最大のアンチパターン。
- Fargateでは各タスクが独立した分離境界を持つため、EC2インスタンスプロファイルのような"同居タスクの資格情報が漏れる"問題は構造的に起きにくい。
シークレットは環境変数の平文に置かない
environmentに平文でDBパスワードを書くと、DescribeTaskDefinitionできる全員に漏れます。必ずsecrets経由でSecrets ManagerやSSM Parameter Storeから注入し、実行ロールにsecretsmanager:GetSecretValue(とKMS復号権限)を最小スコープで付与します。これはこのポートフォリオのルート規約とも一貫する「秘密はenvに置き、コードに置かない」の延長です。
さらに堅くする3点
- 非root実行(
user: "10001:10001")+readonlyRootFilesystem: true。書き込みが要る箇所だけtmpfsを当てる。 - イメージスキャン:ECRのスキャン(拡張スキャン/Inspector連携)で既知脆弱性を出荷前に止める。
- タスク定義は最小:
privilegedやホスト系の共有はFargateでそもそも不可。必要のないlinuxParametersを足さない。
WAF・多層防御まで含めた境界防御は AWS WAF 多層防御 を参照してください。
コスト最適化:従量課金を"使った分だけ"に寄せる
Fargateは割り当てたvCPUとメモリに対する秒単位課金(最低1分)です。EC2のように「インスタンスを買って使用率で薄める」のではなく、タスクが起動している間、割り当て量そのものに課金されます。だから最適化の方向は明確です。
- Right-sizing:Container Insightsで実使用を見て、過剰な割り当てを削る。前述の通りCPU/メモリは組み合わせ固定なので、"片方を上げると相方も上がる"前提で最小ペアを選ぶ。
- ARM64(Graviton)に寄せる:
cpuArchitecture: ARM64にするだけで、同等性能をx86より約20%低い単価で回せる(マルチアーキビルドが必要)。CPUバウンドな常駐サービスほど効く。 - Fargate Spot:中断耐性のあるワークロード(バッチ、ステートレスワーカー、開発環境)を大幅割引で実行。代償は「AWSが容量を返せと言ったら2分前の警告(SIGTERM)つきで中断される」こと。グレースフルシャットダウンを実装済みなら、これは十分に受け入れられるトレードオフです。
- Compute Savings Plans:常時動く本番サービスのベースライン分を1年/3年コミットして単価を下げる。Spotと併用し「ベースは割引コミット、バーストはオンデマンド/Spot」と層を分ける。
容量プロバイダ戦略:base と weight でSpotを安全に混ぜる
オンデマンドとSpotは容量プロバイダ戦略で混在させます(公式)。
base:そのプロバイダで最低限確保するタスク数(1つのプロバイダだけに設定可、既定0)。weight:base充足後、追加タスクを各プロバイダへ何対何の比で割り振るか。
# 「最低2タスクは必ずオンデマンドで確保。それを超える分は Spot:オンデマンド = 4:1 で割る」
default_capacity_provider_strategy {
capacity_provider = "FARGATE"
base = 2
weight = 1
}
default_capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
base = 0
weight = 4
}
これで可用性のベースラインはオンデマンドで守りつつ、スケールアウト分を安く調達できます。Spot中断時は、サービススケジューラが空き容量を見て自動で別タスクの起動を試みます(容量が枯渇していれば回復まで待つ)。中断はSpotInterruptionとしてEventBridgeのタスク状態変更イベントにも流れるので、監視に載せておきます。
FinOps全体の考え方(タグ・予算アラート・無駄の継続削減)は AWS スタートアップのコスト最適化 にまとめています。
本番リリース前チェックリスト
出荷前に、私が必ず確認する項目です。
- タスクサイズは計測値ベース。CPU/メモリの固定ペアで最小を選んだか
-
platform_versionはLATEST(=1.4.0)。cpuArchitectureをARM64にできないか検討したか -
awsvpc+ ALBtarget_type=ip。タスクSGはALBのSGからのみ受ける(0.0.0.0/0を開けていない) - プライベートサブネット配置。
assign_public_ip=false、外向きはNAT/VPCエンドポイント - 実行ロールとタスクロールを分離。タスクロールはサービス専用&最小権限
- **シークレットは
secrets[].valueFrom**で注入。environmentに平文を置いていない - 非root実行 +
readonlyRootFilesystem。ECRイメージスキャンを通過 - イメージはCommitSHAタグ(
latest非依存)。versionConsistencyが効いている - ローリング更新 +
deployment_circuit_breaker {rollback=true}を有効化 - SIGTERMハンドリングを実装し
drainMs < stopTimeout。initProcessEnabled: true -
healthCheckにstartPeriod、サービスにhealth_check_grace_period_seconds - Application Auto Scaling(ターゲット追跡)で
min/maxと非対称クールダウン設定 - Container Insights有効、構造化ログ+相関ID、
enable_execute_commandでブレークグラス可 - コスト:Spot + capacity provider strategy(base/weight)/Savings Plansの層分け
まとめ:Fargateは"サーバーを消して本番品質に集中する"ための道具
ECS on Fargateの本質は、サーバー管理という最大のコスト(人件費)を消し、本番品質そのものに集中できることです。そのために本稿で押さえた要点は4つでした。
- 設計:CPU/メモリは固定ペア。計測してright-sizingし、
awsvpc+ ALB(target_type=ip)で正しく繋ぐ。 - デプロイ:ローリング更新+サーキットブレーカーで自動ロールバック。版の一貫性をダイジェストで担保。
- 回復性:SIGTERMを握って
stopTimeout内に綺麗に終わる。冪等性と両輪で取りこぼし・二重処理を防ぐ。 - 安全とコスト:実行ロールとタスクロールを分離し最小権限。ARM64・Spot・Savings Plansで従量課金を使った分だけに寄せる。
私はこの型で、221本のエンドポイントを持つ受賞SaaSと、二重課金0件の決済基盤を、少人数で本番運用してきました。一人 × 生成AIでも、公式ドキュメントに忠実な型を崩さなければ、世界最高峰の堅牢さは再現できます。あなたのプロダクトのコンテナ基盤を、速く・安く・安全に本番へ載せたいときは、ぜひご相談ください。