目次
15 章

ECS と Fargate — コンテナのデプロイ

AWS 上にコンテナをどう載せるかを整理します。ECS の動作方式(vs EKS)、Cluster · Service · Task · Task Definition の4つの構成要素、EC2 launch type と Fargate の違い、Execution Role と Task Role の分離、ALB · VPC 連携、そして最初のデプロイから Auto Scaling · コストまでを扱います。

本書の終着点は ECS Fargate 上でフルスタックアプリを運用することです(6部 フルスタックアプリを AWS にデプロイする)。ですから本章は単なるサービス紹介ではなく、本書全体がなぜコンテナ優先で進むのかに答える章です。1部でアカウント · IAM · セキュリティ · CloudWatch の土台を固め、2部で 第8章 EC2/VPC から 第14章 CloudFront まで核となるリソースを身につけたなら、いよいよ EC2 1台に直接載せる方式から一段上がる番です。

直前の 第14章 CloudFront までが「資源を1つずつどう作るか」だったとすれば、本章からはそれらの資源の上に実際のアプリケーションをどう運用可能な形で載せるかへと視野が変わります。Docker で作ったイメージを AWS で動かす標準パターンが ECSFargate であり、4部 第22章 ECS Fargate デプロイの骨格 と6部のキャップストーンはすべて本章で固めたモデルの上に立てられます。

本章では ECS の動作方式と EKS との違い、4つの構成要素、EC2 と Fargate という2つの launch type、最もよく混同される2つの IAM ロール、最初のデプロイの全体の流れ、そしてデプロイ戦略 · Auto Scaling · コストまでを整理します。

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

第9章 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 は 第8章 EC2/VPC のような土台の上に k8s 自体の学習が終わったあとに先送りします。本書はコンテナ運用を ECS Fargate で扱い、EKS · Kubernetes 路線は Kubernetes 本の領域です。

ECS のもう一つのサービスとして App Runner もあります。ECS よりもさらに簡単です(イメージ → URL を一度に)。ただしオプションが狭いため、運用領域は ECS / Fargate が占めるのが現在の標準です。

ECS の4つの構成要素 #

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

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

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 がそれを参照するように変えるという形です。

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

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

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

Service — N 個を常に維持 #

「Task を一度立ち上げろ」だけだと、それが死んだら終わりです。Service はその上に付く役割です。

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

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

Cluster — まとまり #

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

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

Cluster は無料です(Cluster 自体にはコストがありません)。中で回る Task のリソースがコストです。ですから環境ごとに自由に分けても大丈夫です。

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 運用は不要です — 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 + 自由

本書の 3 ~ 4部と6部のキャップストーンはすべて Fargate を基準に進めます。運用負担を大きく減らし、学習曲線がなだらかだからです。

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

ECS 運用で最もよく混同される点です。

Execution Role #

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

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

基本的に1つのアカウントに 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 #

完全な流れを一度たどってみます。すでに Docker イメージがあると仮定します。

1) ECR にイメージを push #

第16章 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

コンソールでも一度のクリックで済みます。繰り返しますが無料です。

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 (第13章 ALB / NLB と ACM) をあらかじめ作っておいた状態で進めます。

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 (または 第12章 Route 53 のドメイン) で接続すれば終わりです。

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 hop の追加です。

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 一度だけで十分です。マイクロサービスが複数あるときに 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 / month  (ソウルリージョン基準でおおよそ)

加えて次がかかります。

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

NAT Gateway が意外に大きいです。1ヶ月 $30 水準です — 小さなサービスでは Fargate 自体より NAT コストが大きいことがあります。コスト最適化は 第27章 コスト最適化 で本格的に扱います。

コスト節約オプション #

  • 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章 CloudWatch 入門) で 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 権限を持っていないということです。詳しくは 第20章 Secrets Manager / Parameter Store で扱います。

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 間トラフィックコストを回避します。

練習問題 #

  1. 自分が運用しようとするサービスのトラフィックパターン(常に一定か、変動が大きいか、バッチ性か)を1行で書き、§「Launch Type — EC2 vs Fargate」の表からどの launch type が合うかを選んで理由をメモしておきましょう。第22章 ECS Fargate デプロイの骨格 で同じ選択を Terraform で改めて下すことになります。
  2. Execution Role と Task Role がそれぞれどんな動作に使われるかを見ずに1文ずつ書いてみましょう。そして自分のアプリが S3 と Secrets Manager の両方を使うとき、どの権限がどのロールに入るべきかを §「2つの IAM ロール」を根拠に結びつけておきましょう(第6章 セキュリティ基本 の最小権限とまとめて考えます)。
  3. §「コスト — どこから出るのか」の計算に沿って、1 vCPU + 2GB Fargate Task 2個を1ヶ月運用するときのおおよそのコストを自分で計算してみましょう。ここに ALB と NAT Gateway が加わるとしたら、どの項目を 第16章 ECR の VPC Endpoint で減らせるかを1行で書いておきましょう。

一行まとめ: ECS は AWS のマネージドコンテナオーケストレーターで、Cluster · Service · Task · Task Definition の4要素で構成される。launch type は運用負担が大きい EC2 と運用負担が最小の Fargate に分かれ、本書は Fargate 基準。Execution Role は ECS が Task を立ち上げる権限、Task Role はコードが AWS API を呼び出す権限で、絶対に混同してはいけない。最初のデプロイは ECR push → Cluster → Task Definition → Service(ALB 連携)の順で、rolling update が基本、コストは vCPU · メモリに ALB · NAT が加わるが NAT が意外に大きい。

次の章 #

次の 第16章 ECR では、ECS が立ち上げるイメージがどこから来るのかを扱います。private repo を作る、IAM 認証、push / pull、イメージスキャン、ライフサイクルポリシー、マルチアーキテクチャイメージまで — ECS の相棒であるイメージレジストリをまとめて整理します。

X