AWS上級 #1 ECS と Fargate — コンテナのデプロイ

読了 12分

AWS 基礎 7 編 でアカウント / IAM / セキュリティ / CloudWatch の土台を据え、AWS 中級 7 編 で EC2 / VPC / S3 / RDS / Route 53 / ALB / CloudFront に慣れたなら、いよいよ コンテナ へ一段上がります。

AWS 上級 7 編は EC2 1 台に直接載せる方式 を抜け出し、コンテナ / サーバーレス / メッセージ / シークレット / ワークフローのような 運用規模で出会う道具箱 を整理します。

今回はその出発点 — ECSFargateDocker で作ったイメージを AWS でどう動かすかの標準パターンを掴みます。

EC2 1 台に直接載せる方式の限界 #

中級 #2 EC2 運用 の流れ — EC2 インスタンスを作って SSH で入り、nginx / docker / コードを直接インストールして systemd で起動 — は シンプルな場合は十分 です。ただし規模が大きくなると渇きが来ます。

渇きEC2 直接運用
同じ環境の再現OS パッチ、依存ドリフトで毎回違う
スケールアウトAMI 作り → ASG → デプロイ — 分単位
無停止デプロイ複雑なシェルスクリプト / 別ツール
ロールバックスナップショット → 起動 → トラフィック移動
ヘルスチェック / 自動復旧systemd では限界

この渇きをコンテナが一気に解くのがモダンインフラの流れ。AWS でその入口が ECS です。

ECS が担うこと #

Amazon ECS (Elastic Container Service) は AWS のマネージドコンテナオーケストレーター。Docker イメージを受け取って どのマシンで、何個立ち上げ、トラフィックをどう送るか を決めておけば ECS が運用してくれます。

ECS vs EKS — 一行比較 #

ECSEKS
正体AWS 自体のオーケストレーターAWS が管理する Kubernetes
学習曲線浅い (AWS の中によく溶け込む)急 (k8s 自体の学習が必要)
他クラウド移植性低い (AWS 専用)高い (k8s 標準)
エコシステムAWS ツール + 一部コミュニティk8s 全エコシステム (Helm、ArgoCD 等)
運用負担低い高い (Control Plane コスト + 運用知識)
適した場合小〜中規模、AWS 依存 OK大規模、マルチクラウド、k8s 標準が必要

初めてコンテナ運用を始めるなら ECS から。EKS は 中級 #1 EC2/VPC のような土台 + k8s 自体の学習が終わった後で。

ECS のもう一人の友人として App Runner もあります。ECS よりさらにシンプル (イメージ → URL を一発)。ただしオプションが狭く、運用の領域を ECS / Fargate が占めるのが現在の標準です。

ECS の 4 つの構成要素 #

ECS を理解するには 4 つの構成要素 だけ覚えれば十分です。

ECS の 4 つの構成 — 上から下に
┌──────────────────────────────────────┐
│  Cluster — まとまりの単位             │
│  ┌────────────────────────────────┐  │
│  │ Service — 常に N 個維持         │  │
│  │  ┌────────────┐ ┌────────────┐ │  │
│  │  │  Task #1   │ │  Task #2   │ │  │
│  │  │ (コンテナ)  │ │ (コンテナ)  │ │  │
│  │  └────────────┘ └────────────┘ │  │
│  │  ↑ Task Definition (設計図)    │  │
│  └────────────────────────────────┘  │
└──────────────────────────────────────┘

1) Task Definition — コンテナの設計図 #

JSON 1 枚。何をどう立ち上げるか が全部入っています。

  • どのイメージ (123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1)
  • CPU / メモリ (512 / 1024 MB)
  • 環境変数 / Secrets
  • ポートマッピング
  • ログドライバ (通常 CloudWatch Logs)
  • IAM ロール (Task Role + Execution Role — 後で詳しく)
  • ヘルスチェック
task-definition.json (Fargate)
{
  "family": "myapp",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789012:role/myapp-task-role",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1",
      "essential": true,
      "portMappings": [{ "containerPort": 8000, "protocol": "tcp" }],
      "environment": [
        { "name": "ENV", "value": "production" }
      ],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:myapp/db-AbCdEf"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/myapp",
          "awslogs-region": "ap-northeast-2",
          "awslogs-stream-prefix": "web"
        }
      }
    }
  ]
}

Task Definition は revision (myapp:7 のような番号) で累積されます。新しいイメージをデプロイするには新しい revision を作って Service がそれを参照するように変える形。

2) Task — 実行中のインスタンス #

Task Definition を実際に立ち上げた 1 つのコンテナ (またはコンテナの束) です。EC2 のインスタンスに相当。

  • 1 Task = 1 つの Task Definition revision の実行
  • Task の中に コンテナが複数個 あることも (サイドカーパターン — メインアプリ + ログ収集等)
  • Task は自分の ENI (ネットワークインターフェース) + IP を持つ (awsvpc モード)

3) Service — N 個を常に維持 #

「Task を 1 回立ち上げよ」だけだとそれが死んだら終わり。Service はその上で次を担います:

  • 「この Task Definition の Task を 常に N 個 維持せよ」
  • 死んだら自動再起動
  • ALB / NLB と接続してトラフィックを受ける (中級 #6)
  • デプロイ戦略 (rolling、blue/green)
  • Auto Scaling (CPU / メモリ / リクエスト数ベース)

運用ワークロード (Web サーバー、API 等) はほとんど Service で立ち上げます。単発のバッチ作業だけ Service なしで Task を直接実行 (RunTask)。

4) Cluster — まとまり #

Service / Task が住む論理的なまとまり。通常は 環境単位 で分離:

  • prod-cluster
  • staging-cluster
  • dev-cluster

Cluster は 無料 です (Cluster 自体に費用はない)。中で動く Task のリソースが費用。だから環境別に自由に分けて OK。

Launch Type — EC2 vs Fargate #

ECS が Task を実際にどこに立ち上げるかを決める方式です。2 つのモード があります。

EC2 Launch Type #

私が EC2 インスタンスの束 (ASG) を運用し、ECS はその上にコンテナをスケジューリング。

EC2 Launch Type
ECS Service
   │ (スケジュール)
EC2 #1     EC2 #2     EC2 #3   ← 私が運用 (ASG、AMI、パッチ、セキュリティ)
 ▲          ▲          ▲
 コンテナ   コンテナ   コンテナ

長所:

  • インスタンスコスト = EC2 価格 (長期割引 / Reserved / Spot)
  • GPU / 大メモリ / 特殊インスタンスが自由

短所:

  • EC2 自体を運用しなければならない — AMI 最新化、OS セキュリティパッチ、ECS エージェント更新
  • インスタンスの詰め込み (binpacking) を意識する必要
  • 空のインスタンスが立ったままだとその時間分が無駄

Fargate Launch Type #

EC2 が見えません。Task の CPU / メモリだけを宣言 すれば AWS がそこに勝手にコンテナを立ち上げます。

Fargate Launch Type
ECS Service
   │ (スケジュール)
[AWS 管理領域 — 見えない]
コンテナ (Task)

長所:

  • EC2 運用 0 — OS パッチ、ASG、AMI すべて AWS がやる
  • Task 単位の課金 (分単位、vCPU + メモリ)
  • 空のインスタンスの無駄なし

短所:

  • 単価が EC2 より高い (管理コスト含む)
  • GPU / 特殊インスタンス / 一部ネットワークオプション不可
  • コンテナ当たり vCPU 0.25〜16、メモリ 0.5〜120GB の上限

どちらを選ぶか #

場合推奨
小〜中トラフィックFargate — 運用負担 0
コストが極めて大きい場合EC2 + Reserved / Spot
GPU / 特殊ワークロードEC2
変動トラフィック / バッチFargate Spot (最大 70% 割引)
k8s に慣れているが ECS 限定EC2 + 自由

このシリーズと 実践 6 編 は全て Fargate ベース で進めます。運用負担を大きく減らせて学習曲線が緩やかなため。

2 つの IAM ロール — Execution Role vs Task Role #

ECS 運用で最もよく混乱するところです。

Execution Role #

ECS エージェントが Task を立ち上げるのに必要な 権限。Task が始まる直前に AWS が使用。

  • ECR からイメージを pull
  • CloudWatch Logs グループ / ストリームを作成
  • Secrets Manager / Parameter Store から secret を取得 (Task 起動時点で注入)

基本的にアカウントに ecsTaskExecutionRole 1 つで十分 (AWS マネージドポリシー AmazonECSTaskExecutionRolePolicy を付与)。

Task Role #

コンテナの中のコード が AWS API を呼ぶときに使う権限。ランタイムで使用。

  • コードの中で boto3.client("s3").get_object(...) → S3 アクセス
  • コードの中で dynamodb.get_item(...) → DynamoDB アクセス

各アプリごとに 最小権限 の Task Role を別途作るのが原則。基礎 #6 セキュリティ基本 の最小権限パターン。

ロール分離
Execution Role  →  ECS が使用 (イメージ pull、ログ作成、secret 注入)
Task Role       →  私のコードが使用 (S3、DynamoDB、SQS 呼び出し等)

これら 2 つを混同して 1 つのロールに詰め込むとセキュリティ事故につながります。

最初のデプロイ — Hello, ECS #

完全なフローを 1 度追ってみます。すでに Docker イメージがあるとして。

1) ECR にイメージを push #

#2 ECR で詳しく扱いますが、先にフロー:

ECR push
# 認証
aws ecr get-login-password --region ap-northeast-2 \
  | docker login --username AWS --password-stdin \
    123456789012.dkr.ecr.ap-northeast-2.amazonaws.com

# ビルド + タグ + push
docker build -t myapp .
docker tag myapp:latest \
  123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1
docker push \
  123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1

2) Cluster を作る #

Cluster
aws ecs create-cluster --cluster-name prod-cluster

コンソールで 1 クリックでも。改めて言いますが無料。

3) Task Definition の登録 #

上の JSON をファイル (task-definition.json) として保存し:

登録
aws ecs register-task-definition \
  --cli-input-json file://task-definition.json

成功すれば myapp:1 の revision が作られます。

4) Service の作成 (ALB と一緒に) #

ALB の Target Group (中級 #6) を事前に作っておいた状態で:

Service
aws ecs create-service \
  --cluster prod-cluster \
  --service-name myapp \
  --task-definition myapp:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-aaa,subnet-bbb],securityGroups=[sg-xxx],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=web,containerPort=8000"

この行を実行する瞬間に ECS が:

  1. Fargate の上にコンテナを 2 個立ち上げ
  2. 各コンテナの ENI を Target Group に登録
  3. ALB がヘルスチェック通過後トラフィックをルーティング

ALB の DNS (または Route 53 (中級 #5) のドメイン) に接続すれば完了。

5) 新バージョンのデプロイ #

新バージョン
# 新イメージ push (myapp:v2)
docker tag myapp:v2 ...; docker push ...

# Task Definition の新 revision (イメージタグだけ変えて再登録)
aws ecs register-task-definition --cli-input-json file://task-definition-v2.json
# → myapp:2

# Service が新 revision を使うよう更新
aws ecs update-service \
  --cluster prod-cluster \
  --service myapp \
  --task-definition myapp:2

ECS が rolling update で勝手に — 新 Task 2 個を立ち上げてヘルスチェック通過したら旧 Task 2 個を終了。サービス停止なし。

Service のデプロイオプション #

デフォルトは rolling update ですが他に 2 つ。

Rolling Update (デフォルト) #

minimumHealthyPercent (デフォルト 100) と maximumPercent (デフォルト 200) の 2 つのつまみで調整。

  • minHealthy=100, maxPercent=200 → desired=2 のとき一瞬最大 4 個まで (新 2 + 旧 2)、旧を終了。無停止。
  • minHealthy=50, maxPercent=100 → 旧 1 個終了 → 新 1 個 → 旧 1 個終了 → 新 1 個。コスト節約。

Blue / Green (CodeDeploy 連携) #

新環境 (green) を丸ごと作って ALB の listener を一瞬で切り替える方式。ロールバックが即可能。

External (Spinnaker / 自前コントローラ) #

ECS に「どうデプロイするか」を外部ツールに委任。大きな組織でのみ。

Auto Scaling — トラフィックに合わせて増やす #

Service の上に Application Auto Scaling を載せて desired count を自動調整。

平均 CPU 60% を維持するよう
aws application-autoscaling register-scalable-target \
  --service-namespace ecs \
  --resource-id service/prod-cluster/myapp \
  --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 --max-capacity 10

aws application-autoscaling put-scaling-policy \
  --service-namespace ecs \
  --resource-id service/prod-cluster/myapp \
  --scalable-dimension ecs:service:DesiredCount \
  --policy-name cpu60 \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration file://cpu-60.json

cpu-60.json の中に PredefinedMetricSpecification: ECSServiceAverageCPUUtilizationTargetValue: 60.0

スケールトリガーの基本候補:

  • ECS Service の平均 CPU
  • ECS Service の平均メモリ
  • ALB の RequestCountPerTarget (リクエスト数ベース)

Service Connect — サービス間通信 #

複数のマイクロサービスが ECS の上で互いに呼び合う方式です。2 つのオプション:

1) ALB / NLB 経由 #

各サービスの前に ALB。サービス A → https://service-b.internal/ (Route 53 private hosted zone) → ALB → Service B。

長所: 標準 HTTP、外部と一貫。短所: ALB コスト、1 ホップ追加。

2) Service Connect (ECS 自体) #

ECS がコンテナの隣に proxy サイドカー (Envoy ベース) を自動で挟み込み mesh のように動作。DNS が Cluster の中で自動登録 (web.myapp.local)。

Service Connect 設定 (要約)
{
  "serviceConnectConfiguration": {
    "enabled": true,
    "namespace": "myapp",
    "services": [
      {
        "portName": "web",
        "discoveryName": "web",
        "clientAliases": [{ "port": 8000, "dnsName": "web" }]
      }
    ]
  }
}

小さなシステムでは ALB 1 つで十分。マイクロサービスが複数になったら Service Connect 検討。

コスト — どこから出るか #

Fargate ベース:

コスト = vCPU + メモリ + ネットワーク
時間当たり = (vCPU 時間) × $0.0506
            + (メモリ GB 時間) × $0.0055
            + (Data Transfer)

例: 0.5 vCPU + 1GB Fargate 1 個を 1 ヶ月 (730h)
   = 0.5 × 0.0506 × 730 + 1 × 0.0055 × 730
   = $18.5  +  $4.0
   = $22.5 / 月  (ソウルリージョン基準おおよそ)

加えて:

  • ALB: 時間当たり + LCU 単位
  • NAT Gateway (private subnet からインターネットに出るとき): 時間当たり + GB
  • CloudWatch Logs: ingest GB + storage GB

NAT Gateway が意外と大きい。1 ヶ月 $30 水準 — 小さなサービスでは Fargate 自体より NAT コストの方が大きい場合も。

コスト節約オプション #

  • Fargate Spot: 変動 / バッチワークロードに 70% 割引。突然終了することがあり stateless なワークロードでのみ
  • Compute Savings Plans: 1〜3 年契約で最大 50% 割引
  • Right-sizing: CloudWatch Container Insights で実使用量を確認後 vCPU / メモリを減らす — 最も効果が出る項目

よく出会う落とし穴 #

1) Task が死んでは再起動を繰り返す #

Service が自動再起動してくれるので表面的には「動いているように見えます」が、実は コンテナが起動直後に終了 しています。原因:

  • ヘルスチェック失敗 (アプリが遅く起動して ALB が unhealthy 判定)
  • コンテナの中でエラーで即時 exit
  • メモリ不足 (OOM killed)

CloudWatch Logs (基礎 #7) で stopped reason を確認:

aws ecs describe-tasks --cluster prod-cluster \
  --tasks <task-id> --query 'tasks[0].stoppedReason'

2) イメージ pull 権限不足 #

Task 起動直後に “CannotPullContainerError” → 99% は Execution Role の ECR 権限不足。AWS マネージドの AmazonECSTaskExecutionRolePolicy を付けたか確認。

3) Secret の注入ができない #

Task Definition の secrets が空で入る → Execution Role が Secrets Manager / Parameter Store の ARN に secretsmanager:GetSecretValue / ssm:GetParameter 権限がありません。詳細は #6

4) ALB Target が unhealthy #

デプロイは出来たのに ALB のヘルスチェックが失敗。よく見る原因:

  • ヘルスチェック path がアプリにない (/health エンドポイント忘れた)
  • Security Group が ALB → Task のトラフィックを塞ぐ
  • アプリが 0.0.0.0 でなく 127.0.0.1 にバインド (コンテナの外から接続不可)

5) Task Definition の revision が暴走 #

v1v2 → … → v847 のように際限なく溜まる。直接整理しないとコンソールが重くなります。運用ポリシーで 30 日以上未使用 revision を自動整理 または IaC が整理するように。

6) NAT Gateway コストの爆発 #

private subnet の Task が外部 API を頻繁に呼び出す → NAT Gateway の Data Processing 料金が EC2 料金を超える。代替:

  • VPC Endpoint (S3、ECR、Secrets Manager 等よく使うサービスに) — トラフィックが NAT を経由しない
  • 外部 API 呼び出しが多ければ同じ AZ の NAT から同じ AZ の Task → AZ 間トラフィックコスト回避

まとめ #

今回つかんだもの:

  • EC2 直接運用の限界 — 環境再現、スケール、無停止デプロイ、ロールバック、ヘルスチェックがコンテナで自然に解ける
  • ECS の役割 — AWS のマネージドコンテナオーケストレーター。EKS は k8s 標準が必要なとき
  • 4 つの構成要素 — Cluster (まとまり) / Service (N 個維持) / Task (実行中のコンテナ) / Task Definition (設計図)
  • Launch Type — EC2 (直接運用、コスト最適化) vs Fargate (運用 0、単価高い)。シリーズは Fargate ベース
  • 2 つの IAM ロール — Execution Role (ECS が Task を立ち上げる権限) vs Task Role (コードが AWS API を呼ぶ権限)。絶対に混同しないこと
  • 最初のデプロイの流れ — ECR push → Cluster → Task Definition → Service (ALB 接続)
  • デプロイ — rolling (デフォルト) / blue-green (CodeDeploy) / external
  • Auto Scaling — Application Auto Scaling で CPU / メモリ / リクエスト数ベース
  • Service Connect — Service 間通信を ALB なしで mesh に
  • コスト — vCPU + メモリ + ALB + NAT。NAT が意外と大きい。Spot、Savings Plans、Right-sizing
  • 落とし穴 — Task 無限再起動 (ヘルスチェック / OOM)、イメージ pull 権限、Secret 権限、ALB unhealthy、revision 暴走、NAT コスト

次回 — ECR #

ECS が立ち上げるイメージがどこから来るかという話です。Amazon ECR (Elastic Container Registry) に次回詳しく入ります。

#2 ECR — イメージレジストリ では private repo の作り方、認証、push / pull、イメージスキャン、ライフサイクルポリシー、マルチアーキテクチャイメージまで — ECS のパートナーを一気に整理します。

X