目次
6部 総合実習
  1. 32.フルスタックアプリを AWS にデプロイする — ECS Fargate キャップストーン
32 章

フルスタックアプリを AWS にデプロイする — ECS Fargate キャップストーン

1 ~ 31章のすべてのサービスを1つに織り上げる総合実習。modern-react の Next.js アプリと modern-python の FastAPI アプリを1つのアカウントに ECS Fargate + RDS + S3 + CloudFront + ALB + Secrets Manager + Terraform でデプロイし、ステップ別の Terraform コードと13ステップの PR の流れ、1か月約 $10 の最小コスト構成、そして Kubernetes 本の EKS デプロイとの比較までを整理します。

この本の終着点です。1 ~ 31章で別々に身につけたサービス — IAM · VPC · S3 · RDS · ALB · CloudFront · ECS Fargate · ECR · Secrets Manager · CloudWatch · Terraform — が、この章で 1つの動作するシステム の中へ入ってきます。ドメインは モダンPython 本と React 本で作ったのと同じ フルスタック Todo アプリ です。

このキャップストーンは Kubernetes 本の6部と対になります。同じアプリを同じドメインで デプロイしますが、Kubernetes 本は EKS 路線で、本書は ECS Fargate 路線で行きます。2つのキャップストーンを比べて読めば、「マネージドコンテナ vs Kubernetes」の運用上の違いがコードレベルで明確になります。

何をデプロイするか #

構成要素出所AWS 上の配置
フロントエンドReact の Next.js アプリECS Fargate (SSR) + ALB、静的アセットは S3 + CloudFront
バックエンド APIモダンPython の FastAPI アプリECS Fargate + ALB
データベースRDS PostgreSQL (Aurora Serverless v2)
シークレットSecrets Manager
ドメイン / TLSRoute 53 + ACM

目標アーキテクチャ #

ECS Fargate フルスタックアーキテクチャ
                  Route 53 (ドメイン)
              ┌────────┴─────────┐
              ▼                  ▼
        CloudFront          ALB (HTTPS, ACM)
       (静的アセット/S3)    ╱            ╲
                         ▼              ▼
                 ECS Fargate      ECS Fargate
                 (Next.js SSR)    (FastAPI API)
                      private subnet (2 AZ)
                          RDS PostgreSQL
                       (Aurora Serverless v2)
                          isolated subnet
                          Secrets Manager (DB 認証情報)

第28章 VPC 深掘り の 3-tier Subnet の上に載せます。ALB と CloudFront は public、Fargate Task は private、RDS は isolated 層です。

リポジトリ構成 #

すべてのインフラは 第25章 Terraform 入門 のモジュール構成でコード化します。コンソールクリックはありません。

terraform ディレクトリ
infra/
├── backend.tf          # 1ステップ: state の保存先
├── providers.tf        # provider + region
├── vpc.tf              # 2ステップ: 3-tier VPC
├── ecr.tf              # 3ステップ: イメージの保存先
├── rds.tf              # 4·5ステップ: Aurora Serverless v2 + シークレット
├── alb.tf              # 6ステップ: ALB + ACM + ターゲットグループ
├── ecs-api.tf          # 7·8ステップ: FastAPI Task/Service
├── ecs-web.tf          # 9ステップ: Next.js Task/Service
├── cdn.tf              # 10ステップ: S3 + CloudFront
├── dns.tf              # 11ステップ: Route 53
├── monitoring.tf       # 13ステップ: CloudWatch アラーム
└── variables.tf

1ステップ — Terraform バックエンド #

state をローカルではなく S3 に置き、DynamoDB で同時実行のロック(lock)をかけます。チームが同じインフラを安全に扱うための前提です。

backend.tf
terraform {
  required_version = ">= 1.9"
  backend "s3" {
    bucket         = "myapp-tfstate"
    key            = "capstone/terraform.tfstate"
    region         = "ap-northeast-2"
    dynamodb_table = "myapp-tflock"
    encrypt        = true
  }
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 6.0" }
  }
}

2ステップ — VPC 3-tier #

第28章 の 3-tier(public / private / isolated)設計をモジュールで載せます。database_subnets が isolated 層です。

vpc.tf
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "myapp"
  cidr = "10.0.0.0/16"
  azs  = ["ap-northeast-2a", "ap-northeast-2c"]

  public_subnets   = ["10.0.0.0/20", "10.0.128.0/20"]   # ALB, NAT
  private_subnets  = ["10.0.16.0/20", "10.0.144.0/20"]  # Fargate
  database_subnets = ["10.0.32.0/20", "10.0.160.0/20"]  # RDS (isolated)

  enable_nat_gateway = true
  single_nat_gateway = false   # AZ ごとに NAT (28章 推奨)
}

# S3 / ECR トラフィックは NAT ではなく Endpoint で (コスト削減)
module "vpc_endpoints" {
  source  = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints"
  version = "~> 5.0"
  vpc_id  = module.vpc.vpc_id
  endpoints = {
    s3   = { service = "s3", service_type = "Gateway", route_table_ids = module.vpc.private_route_table_ids }
    ecr_api = { service = "ecr.api", subnet_ids = module.vpc.private_subnets }
    ecr_dkr = { service = "ecr.dkr", subnet_ids = module.vpc.private_subnets }
  }
}

3ステップ — ECR リポジトリ #

2つのアプリのイメージを入れる 第16章 ECR の保存先です。

ecr.tf
resource "aws_ecr_repository" "api" {
  name                 = "myapp-api"
  image_tag_mutability = "MUTABLE"
  image_scanning_configuration { scan_on_push = true }
}

resource "aws_ecr_repository" "web" {
  name                 = "myapp-web"
  image_scanning_configuration { scan_on_push = true }
}

4·5ステップ — Aurora Serverless v2 とシークレット #

第11章 RDS の Aurora Serverless v2 を isolated subnet に置きます。manage_master_user_password = true にすると、RDS がパスワードを Secrets Manager に直接生成・ローテーション します(第20章)。私たちが平文のパスワードを触ることはありません。

rds.tf
resource "aws_rds_cluster" "main" {
  cluster_identifier = "myapp"
  engine             = "aurora-postgresql"
  engine_version     = "16.4"
  database_name      = "myapp"
  master_username    = "myapp"

  manage_master_user_password = true            # → Secrets Manager に自動生成
  db_subnet_group_name        = module.vpc.database_subnet_group_name
  vpc_security_group_ids      = [aws_security_group.rds.id]

  serverlessv2_scaling_configuration {
    min_capacity = 0       # 0 ACU 自動一時停止 (アイドル時のコンピューティング課金 0)
    max_capacity = 4.0
  }
  skip_final_snapshot = true   # 学習環境限定
}

resource "aws_rds_cluster_instance" "main" {
  cluster_identifier = aws_rds_cluster.main.id
  instance_class     = "db.serverless"
  engine             = aws_rds_cluster.main.engine
}

# RDS が作ったシークレット ARN — ECS Task がこれを参照
output "db_secret_arn" {
  value = aws_rds_cluster.main.master_user_secret[0].secret_arn
}

6ステップ — ALB と ACM #

第13章 ALB / NLB と ACM の HTTPS 終端です。ホストヘッダで api.example.com は API ターゲットグループ、それ以外は Next.js ターゲットグループへ送ります。

alb.tf (抜粋)
resource "aws_lb" "main" {
  name               = "myapp-alb"
  load_balancer_type = "application"
  subnets            = module.vpc.public_subnets
  security_groups    = [aws_security_group.alb.id]
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = 443
  protocol          = "HTTPS"
  certificate_arn   = aws_acm_certificate.main.arn
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.web.arn   # デフォルト = Next.js
  }
}

resource "aws_lb_listener_rule" "api" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 10
  action { type = "forward", target_group_arn = aws_lb_target_group.api.arn }
  condition { host_header { values = ["api.example.com"] } }
}

7·8ステップ — FastAPI Task とマイグレーション #

第15章 ECS / Fargate · 第22章 インフラ骨格 の Task 定義です。DB シークレットを secrets で注入 するので、環境変数に平文がありません。

ecs-api.tf (抜粋)
resource "aws_ecs_task_definition" "api" {
  family                   = "myapp-api"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = "512"
  memory                   = "1024"
  execution_role_arn       = aws_iam_role.ecs_exec.arn   # ECR pull, シークレット read
  task_role_arn            = aws_iam_role.app.arn        # アプリが使う権限

  container_definitions = jsonencode([{
    name         = "api"
    image        = "${aws_ecr_repository.api.repository_url}:latest"
    portMappings = [{ containerPort = 8000 }]
    secrets = [{
      name      = "DB_SECRET"   # RDS が作ったシークレット(ホスト・パスワードの JSON)
      valueFrom = aws_rds_cluster.main.master_user_secret[0].secret_arn
    }]
    logConfiguration = {
      logDriver = "awslogs"
      options = {
        "awslogs-group"         = "/ecs/myapp-api"
        "awslogs-region"        = "ap-northeast-2"
        "awslogs-stream-prefix" = "api"
      }
    }
  }])
}

resource "aws_ecs_service" "api" {
  name            = "myapp-api"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.api.arn
  desired_count   = 2                     # 2 AZ
  launch_type     = "FARGATE"
  network_configuration {
    subnets         = module.vpc.private_subnets
    security_groups = [aws_security_group.api.id]
  }
  load_balancer {
    target_group_arn = aws_lb_target_group.api.arn
    container_name   = "api"
    container_port   = 8000
  }
}

DB マイグレーション(第23章)はサービスではなく 単発の Task で回します。同じイメージでコマンドだけ変えます。

Alembic マイグレーションを一回限りの Fargate Task で
aws ecs run-task \
  --cluster myapp \
  --task-definition myapp-api \
  --launch-type FARGATE \
  --overrides '{"containerOverrides":[{"name":"api","command":["alembic","upgrade","head"]}]}' \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-...],securityGroups=[sg-...]}"

9·10·11ステップ — Next.js · 静的アセット · ドメイン #

Next.js SSR は API と同じ方式の Fargate サービスで載せます(ecs-web.tf)。ビルド成果の静的アセットは 第10章 S3 + 第14章 CloudFront でデプロイし、第12章 Route 53 でドメインを ALB / CloudFront に Alias で接続します。

dns.tf (抜粋)
resource "aws_route53_record" "web" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "example.com"
  type    = "A"
  alias {
    name                   = aws_cloudfront_distribution.web.domain_name
    zone_id                = aws_cloudfront_distribution.web.hosted_zone_id
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "api" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  alias {
    name                   = aws_lb.main.dns_name
    zone_id                = aws_lb.main.zone_id
    evaluate_target_health = true
  }
}

12ステップ — CI/CD #

第24章 CI/CD の GitHub Actions でイメージを ECR にプッシュし、ECS をローリングアップデートします。

.github/workflows/deploy.yml (抜粋)
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
    aws-region: ap-northeast-2
- uses: aws-actions/amazon-ecr-login@v2
- run: |
    docker build -t $ECR/myapp-api:$GITHUB_SHA ./api
    docker push $ECR/myapp-api:$GITHUB_SHA
- run: |
    aws ecs update-service --cluster myapp --service myapp-api \
      --force-new-deployment

GitHub には長期の認証情報ではなく OIDC ロールの引き受け(role-to-assume)を使います(第6章 セキュリティ基本の最小権限の原則)。

13ステップ — モニタリング・アラーム #

第26章 モニタリング — CloudWatch アラームと X-Ray のアラームをかけたうえで、ALB 5xx と Fargate CPU を監視します。

monitoring.tf (抜粋)
resource "aws_cloudwatch_metric_alarm" "alb_5xx" {
  alarm_name          = "myapp-alb-5xx"
  namespace           = "AWS/ApplicationELB"
  metric_name         = "HTTPCode_Target_5XX_Count"
  statistic           = "Sum"
  period              = 60
  evaluation_periods  = 5
  threshold           = 10
  comparison_operator = "GreaterThanThreshold"
  alarm_actions       = [aws_sns_topic.alerts.arn]   # 19章 SNS で通知
  dimensions          = { LoadBalancer = aws_lb.main.arn_suffix }
}

アラームは 第19章 EventBridge / SQS / SNS の SNS トピックに送って Slack・メールで受け取ります。

13ステップを一覧で #

#ステップファイル関連章
1Terraform バックエンドbackend.tf第25章
2VPC 3-tiervpc.tf第28章
3ECRecr.tf第16章
4·5Aurora + シークレットrds.tf第11章 · 第20章
6ALB + ACMalb.tf第13章
7·8FastAPI + マイグレーションecs-api.tf第15章 · 第22章 · 第23章
9Next.jsecs-web.tf第15章
10·11S3/CloudFront · Route 53cdn.tf · dns.tf第10章 · 第14章 · 第12章
12CI/CDdeploy.yml第24章
13モニタリング・アラームmonitoring.tf第26章 · 第19章

このコード化のおかげで、第30章 災害復旧 の Pilot Light DR が「同じコードを別のリージョンに apply」へと単純になります。

コスト — 1か月約 $10 でたどる #

学習用に最後までたどりながらコストを最小化する構成です。

  • Aurora Serverless v2 を最小 0 ACU(自動一時停止)に置き、アイドル時のコンピューティング課金を 0(ストレージのみ)にします。最初の接続に約 15秒の再開が付くので、prod は 0 ではなく 0.5+ に置きます。
  • Fargate Spot で Task を立ち上げて単価を下げます(学習限定、production は通常の Fargate と混合)。
  • NAT の代わりに VPC Endpoint — ECR / S3 / Secrets Manager は Endpoint で(第28章)NAT 処理コストを回避します。
  • 実習が終わったら terraform destroy ですぐに片付けます。

この構成で1か月約 $10 前後です。ALB · NAT · Aurora は付けておいた分だけ課金 されるので、実習が終わったら必ず片付け、第27章 コスト最適化 の請求アラートをあらかじめかけておきます。

実習終了時の片付け
terraform destroy

modern-kubernetes 本との比較 #

Kubernetes 本の6部が同じ Todo システムを EKS でデプロイします。同じドメインを2つのプラットフォームで実装したときの違いは次のとおりです。

観点本書 (ECS Fargate)Kubernetes 本 (EKS)
オーケストレーションECS Task 定義k8s Deployment / Service
デプロイ単位Terraform + ECS ローリングHelm + ArgoCD GitOps
コントロールプレーンのコストなし(Fargate)EKS コントロールプレーンの時間単位
学習曲線低い高い
移植性AWS 依存マルチクラウド可能
適する場面小さなチーム、単一ドメイン、速いリリースマルチドメイン、GitOps、マルチクラウドオプション

決定基準: 小さなチーム + 単一ドメインなら ECS Fargate がコントロールプレーンのコストも学習曲線も低く効率的です。マルチドメイン + GitOps + マルチクラウドの可能性 が必要になったら、そのとき EKS を検討します。二冊の6部が同じアプリを2つの路線で見せる理由です。

練習問題 #

  1. terraform apply を13ステップの順に一度にせず、小さな PR に分ける理由を一段落で書いてみてください。VPC(2ステップ)なしで ECS(7ステップ)を apply すると何が止まるかを §「リポジトリ構成」の依存関係で説明します。
  2. このキャップストーンの DB パスワードがコード · イメージ · Terraform state のどこにも平文で残らない経路を、manage_master_user_password と Task の secrets 注入を根拠にした流れで書いてみてください(4·5·7ステップ)。
  3. 自分のサービスを ECS Fargate と EKS のどちらでデプロイするかを §「modern-kubernetes 本との比較」表の基準で決め、根拠を一段落で書いてみてください。

一行まとめ: 6部キャップストーンは modern-react の Next.js と modern-python の FastAPI を1つのアカウントに ECS Fargate + Aurora Serverless v2 + S3 + CloudFront + ALB + Secrets Manager で、28章の 3-tier VPC の上に13ステップでデプロイする。すべてのステップが Terraform ファイル(backend / vpc / ecr / rds / alb / ecs / cdn / dns / monitoring)でコード化されてコンソールクリックがなく、このコード化が30章 Pilot Light DR の前提になる。DB パスワードは manage_master_user_password で RDS が Secrets Manager に作り、Task の secrets で注入されてどこにも平文で残らず、CI/CD は OIDC ロールの引き受けで長期キーなしにデプロイする。Aurora の最小 ACU + Fargate Spot + VPC Endpoint で1か月約 $10 でたどり、終わったら destroy する。同じアプリを EKS でデプロイする Kubernetes 本の6部と比べると、小さなチーム・単一ドメインには ECS Fargate、マルチドメイン・GitOps・マルチクラウドには EKS が分かれる。

次の章 #

本文はここで終わります。最後に 付録A CLF-C02 認定の橋渡し では、本書27章が AWS Cloud Practitioner(CLF-C02)試験範囲とどこで重なり、どこが空白かをマッピングします。実戦で身につけた内容を認定トラックへつなぎたい方のための橋です。

X