Use Cases

Claude Code × AWS ECS/Fargate 완전 가이드 | 컨테이너 배포 자동화하기

Claude Code로 AWS ECS/Fargate 배포를 자동화하세요. 태스크 정의부터 서비스 설정, Blue/Green 배포, CDK 인프라 구축까지 — Masa의 실무 경험을 바탕으로 해설합니다.

“AWS에서 컨테이너를 실행하고 싶은데, ECS 설정이 너무 복잡하다” — 이것은 많은 개발자가 처음 마주치는 벽입니다. 태스크 정의, 서비스, 클러스터, 로드 밸런서, 오토 스케일링… 설정 항목이 너무 많아서 어디서부터 손을 대야 할지 모릅니다.

저는 업무에서 ECS/Fargate를 사용한 서버리스 컨테이너 환경을 구축하고 있는데, Claude Code에 구성을 전달하는 것만으로 태스크 정의부터 CDK 코드까지 일괄 생성할 수 있게 된 이후로 배포 작업이 훨씬 편해졌습니다. 이 글에서는 그 실전 절차를 해설합니다.


ECS/Fargate 기초를 3분 만에 정리

클러스터:      ECS 컨테이너가 동작하는 "박스"
태스크 정의:   컨테이너의 설정 명세서 (이미지, CPU, 메모리, 환경 변수)
서비스:        태스크를 몇 대 기동할지의 설정 (오토 스케일링 포함)
Fargate:       서버 관리가 필요 없는 컨테이너 실행 엔진

EC2와 달리, Fargate는 서버 프로비저닝·패치 적용이 필요 없습니다. 컨테이너 설정에만 집중할 수 있습니다.


Step 1: 태스크 정의 자동 생성

claude -p "
다음 조건으로 ECS 태스크 정의를 JSON으로 생성해줘.

- 앱: Node.js API (포트 3000)
- CPU: 512 (0.5 vCPU)
- 메모리: 1024 MB
- 환경 변수: DATABASE_URL (Secrets Manager에서 취득), NODE_ENV=production
- 로그: CloudWatch Logs (로그 그룹 /ecs/myapp)
- 헬스 체크: /health 엔드포인트, 30초 간격
- 컨테이너 이미지: 123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest
- 실행 롤: ecsTaskExecutionRole
- 태스크 롤: myapp-task-role (DynamoDB·S3 접근 권한)
"

생성되는 태스크 정의:

{
  "family": "myapp-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789:role/myapp-task-role",
  "containerDefinitions": [
    {
      "name": "myapp",
      "image": "123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest",
      "portMappings": [{ "containerPort": 3000, "protocol": "tcp" }],
      "environment": [{ "name": "NODE_ENV", "value": "production" }],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:123456789:secret:prod/database-url"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/myapp",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}

Step 2: CDK로 ECS/Fargate 환경 구축

태스크 정의뿐만 아니라, 클러스터·서비스·ALB까지 CDK로 한 번에 생성합니다.

claude -p "
lib/ecs-stack.ts에 다음 ECS/Fargate 환경을 CDK TypeScript로 구현해줘.

- VPC: 기존 VPC import (vpcId를 환경 변수에서 취득)
- ECS 클러스터: Fargate만
- 태스크 정의: 위에서 생성한 것과 동일한 사양
- ALB: HTTPS (ACM 인증서 ARN을 환경 변수에서 취득), HTTP는 301 리다이렉트
- 서비스: 최소 2대, 최대 10대의 오토 스케일링
  - CPU 사용률 70%에서 스케일 아웃
  - 대상 추적 스케일링
- Blue/Green 배포 대응 (CodeDeploy 연계)
- CloudWatch 대시보드에 서비스 가동 상태 표시
"
// lib/ecs-stack.ts
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ecsp from "aws-cdk-lib/aws-ecs-patterns";
import * as acm from "aws-cdk-lib/aws-certificatemanager";

export class EcsStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 기존 VPC import
    const vpc = ec2.Vpc.fromLookup(this, "Vpc", {
      vpcId: process.env.VPC_ID!,
    });

    // ECS 클러스터
    const cluster = new ecs.Cluster(this, "Cluster", {
      vpc,
      clusterName: "myapp-cluster",
      containerInsights: true,  // CloudWatch Container Insights 활성화
    });

    // ACM 인증서
    const certificate = acm.Certificate.fromCertificateArn(
      this, "Certificate",
      process.env.CERTIFICATE_ARN!
    );

    // 태스크 정의
    const taskDef = new ecs.FargateTaskDefinition(this, "TaskDef", {
      cpu: 512,
      memoryLimitMiB: 1024,
    });

    const container = taskDef.addContainer("app", {
      image: ecs.ContainerImage.fromEcrRepository(
        ecr.Repository.fromRepositoryName(this, "Repo", "myapp")
      ),
      environment: { NODE_ENV: "production" },
      logging: ecs.LogDrivers.awsLogs({ streamPrefix: "ecs" }),
      healthCheck: {
        command: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
        interval: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(5),
        retries: 3,
        startPeriod: cdk.Duration.seconds(60),
      },
    });
    container.addPortMappings({ containerPort: 3000 });

    // ALB + Fargate 서비스 (패턴으로 간결하게)
    const service = new ecsp.ApplicationLoadBalancedFargateService(
      this, "Service", {
        cluster,
        taskDefinition: taskDef,
        desiredCount: 2,
        certificate,
        redirectHTTP: true,
        publicLoadBalancer: true,
      }
    );

    // 오토 스케일링 설정
    const scaling = service.service.autoScaleTaskCount({
      minCapacity: 2,
      maxCapacity: 10,
    });
    scaling.scaleOnCpuUtilization("CpuScaling", {
      targetUtilizationPercent: 70,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });

    // Output
    new cdk.CfnOutput(this, "LoadBalancerDns", {
      value: service.loadBalancer.loadBalancerDnsName,
    });
  }
}

Step 3: GitHub Actions로 CI/CD 파이프라인 구축

claude -p "
GitHub Actions로 다음 CI/CD 파이프라인을 만들어줘:

1. main 브랜치로의 push를 트리거
2. Docker 이미지를 빌드하여 ECR에 푸시
3. ECS 태스크 정의의 image 태그를 새로운 SHA로 업데이트
4. ECS 서비스에 새로운 태스크 정의를 배포
5. 배포 완료를 Slack에 알림

환경: ap-northeast-1, ECR 리포지토리명: myapp
"
name: Deploy to ECS

on:
  push:
    branches: [main]

env:
  AWS_REGION: ap-northeast-1
  ECR_REPOSITORY: myapp
  ECS_CLUSTER: myapp-cluster
  ECS_SERVICE: myapp-service
  CONTAINER_NAME: app

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image to ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

      - name: Download task definition
        run: |
          aws ecs describe-task-definition --task-definition myapp-task \
            --query taskDefinition > task-definition.json

      - name: Update ECS task definition with new image
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy to ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true

      - name: Notify Slack on success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {"text": "✅ Deployed to ECS: ${{ github.sha }}"}

Step 4: Blue/Green 배포 설정

claude -p "
ECS 서비스에서 Blue/Green 배포 (CodeDeploy)를 설정하기 위한
CDK 코드와 AppSpec 파일을 생성해줘.
롤백 조건: 배포 후 5분 이내에 CloudWatch 알람이 발생하면 자동 롤백
"
# appspec.yaml (CodeDeploy용)
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: app
          ContainerPort: 3000
        PlatformVersion: LATEST
Hooks:
  - BeforeAllowTraffic: "arn:aws:lambda:ap-northeast-1:123456789:function:health-check"
  - AfterAllowTraffic: "arn:aws:lambda:ap-northeast-1:123456789:function:smoke-test"

함정 5가지

1. 헬스 체크의 startPeriod를 깜빡하기

Fargate 컨테이너는 시작 직후에 헬스 체크가 실행되지만, 앱의 기동이 느리면 즉시 UNHEALTHY가 됩니다. startPeriod: 60 (초)를 설정하여 앱의 기동 시간을 확보합시다.

2. 태스크 롤과 실행 롤을 혼동하기

executionRole (ecsTaskExecutionRole): 컨테이너 기동에 필요한 권한
  → ECR에서 이미지 pull, CloudWatch Logs에 쓰기

taskRole (myapp-task-role): 앱이 사용하는 권한
  → DynamoDB, S3, SQS 등으로의 접근

어느 롤에 무엇을 추가할지 자주 틀립니다.

3. awsvpc 네트워크 모드에서의 보안 그룹 설정

Fargate는 반드시 awsvpc 네트워크 모드가 필요합니다. 컨테이너의 보안 그룹을 ALB에서만으로 제한하는 것을 잊으면, 컨테이너가 직접 외부에 공개됩니다.

4. Secrets Manager 취득에 실행 롤이 필요

secrets로 Secrets Manager의 값을 취득하려면 executionRolesecretsmanager:GetSecretValue 권한이 필요합니다. 누락되면 컨테이너가 기동하지 않는 경우가 많습니다.

5. 배포 시의 desired_count: 0 문제

오토 스케일링에서 최솟값이 0인 서비스는 심야에 모든 컨테이너가 꺼져서 아침의 기동이 느려집니다. 최솟값은 프로덕션에서 최소 2대로 설정합시다.


정리

태스크Claude Code의 기여
태스크 정의 생성요건을 전달하기만 해도 JSON 완성
CDK 구현클러스터·서비스·ALB·오토 스케일링 일괄 생성
CI/CD 설정GitHub Actions 워크플로우 생성
Blue/Green 배포AppSpec·CodeDeploy 설정을 자동 생성
트러블 해결에러 로그에서 원인과 수정 방법을 제시

ECS는 설정 항목이 많지만, Claude Code에 “이런 구성이 필요하다”고 전달하기만 하면 베스트 프랙티스에 맞는 설정이 일식으로 갖춰집니다. 우선 CDK부터 시작하는 것이 가장 편하고 추천합니다.

관련 기사

참고 자료

#claude-code #aws #ecs #fargate #container #devops

Claude Code 워크플로우를 한 단계 업그레이드하세요

지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.

무료 제공

무료 PDF: 5분 완성 Claude Code 치트시트

이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.

개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.

Masa

이 글을 작성한 사람

Masa

Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.