목차
6부 종합 실습
  1. 32.풀스택 앱 AWS 배포하기 — ECS Fargate 캡스톤
32 장

풀스택 앱 AWS 배포하기 — ECS Fargate 캡스톤

1~31장의 모든 서비스를 하나로 엮는 종합 실습. modern-react의 Next.js 앱과 modern-python의 FastAPI 앱을 한 계정에 ECS Fargate + RDS + S3 + CloudFront + ALB + Secrets Manager + Terraform으로 배포하고, 단계별 Terraform 코드와 13단계 PR 흐름, 한 달 약 $10의 최소 비용 구성, 그리고 쿠버네티스 책의 EKS 배포와의 비교까지 정리합니다.

이 책의 종착점입니다. 1~31장에서 따로 익힌 서비스 — IAM · VPC · S3 · RDS · ALB · CloudFront · ECS Fargate · ECR · Secrets Manager · CloudWatch · Terraform — 가 이 챕터에서 하나의 동작하는 시스템 안으로 들어옵니다. 도메인은 모던 파이썬 책과 리액트 책에서 만든 것과 같은 풀스택 Todo 앱입니다.

이 캡스톤은 쿠버네티스 책 6부와 짝을 이룹니다. 같은 앱을 같은 도메인으로 배포하되, 쿠버네티스 책은 EKS 노선으로, 본 책은 ECS Fargate 노선으로 갑니다. 두 캡스톤을 비교해 읽으시면 “매니지드 컨테이너 vs 쿠버네티스"의 운영상 차이가 코드 수준에서 분명해집니다.

무엇을 배포하는가 #

구성 요소출처AWS 배치
프런트엔드리액트의 Next.js 앱ECS Fargate (SSR) + ALB, 정적 자산은 S3 + CloudFront
백엔드 API모던 파이썬의 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 리포지터리 #

두 앱의 이미지를 담을 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 토픽으로 보내 슬랙·이메일로 받습니다.

13단계 한눈에 #

#단계파일연관 챕터
1Terraform 백엔드backend.tf25장
2VPC 3-tiervpc.tf28장
3ECRecr.tf16장
4·5Aurora + 시크릿rds.tf11장 · 20장
6ALB + ACMalb.tf13장
7·8FastAPI + 마이그레이션ecs-api.tf15장 · 22장 · 23장
9Next.jsecs-web.tf15장
10·11S3/CloudFront · Route 53cdn.tf · dns.tf10장 · 14장 · 12장
12CI/CDdeploy.yml24장
13모니터링·알람monitoring.tf26장 · 19장

이 코드화 덕분에 30장 재해 복구의 Pilot Light DR이 “같은 코드를 다른 리전에 apply"로 단순해집니다.

비용 — 한 달 약 $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로 즉시 정리.

이 구성으로 한 달 약 $10 내외입니다. ALB · NAT · Aurora는 켜둔 만큼 과금 되니 실습이 끝나면 반드시 정리하고, 27장 비용 최적화의 청구 알림을 미리 걸어 둡니다.

실습 종료 시 정리
terraform destroy

modern-kubernetes 책과의 비교 #

쿠버네티스 책 6부가 같은 Todo 시스템을 EKS로 배포합니다. 같은 도메인을 두 플랫폼으로 구현했을 때의 차이는 다음과 같습니다.

본 책 (ECS Fargate)쿠버네티스 책 (EKS)
오케스트레이션ECS Task 정의k8s Deployment / Service
배포 단위Terraform + ECS 롤링Helm + ArgoCD GitOps
컨트롤 플레인 비용없음(Fargate)EKS 컨트롤 플레인 시간당
학습 곡선낮음높음
이식성AWS 종속멀티 클라우드 가능
적합작은 팀, 단일 도메인, 빠른 출시멀티 도메인, GitOps, 멀티 클라우드 옵션

결정 기준: 작은 팀 + 단일 도메인이면 ECS Fargate가 컨트롤 플레인 비용도 학습 곡선도 낮아 효율적입니다. 멀티 도메인 + GitOps + 멀티 클라우드 가능성이 필요해지면 그때 EKS를 검토합니다. 두 책의 6부가 같은 앱을 두 노선으로 보여 주는 이유입니다.

연습문제 #

  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를 한 계정에 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로 한 달 약 $10에 따라하고 끝나면 destroy 한다. 같은 앱을 EKS로 배포하는 쿠버네티스 책 6부와 비교하면 작은 팀·단일 도메인엔 ECS Fargate, 멀티 도메인·GitOps·멀티 클라우드엔 EKS가 갈린다.

다음 챕터 #

본문은 여기서 끝납니다. 마지막으로 부록 A CLF-C02 자격증 가교에서는 본 책 27장이 AWS Cloud Practitioner(CLF-C02) 시험 범위와 어디서 겹치고 어디가 공백인지를 매핑합니다. 실전으로 익힌 내용을 자격증 트랙으로 잇고 싶은 분을 위한 다리입니다.

X