본문으로 건너뛰기
글 목록으로 돌아가기
이 글 목차

ECR/ECS 배포 워크플로우

Amazon ECR과 ECS를 사용한 컨테이너 배포 전체 과정 — 인증부터 롤링 업데이트, 트러블슈팅까지 정리했어요.

첫 ECS 배포는 GitHub Actions 로그에서 완벽해 보였어요. 초록색 체크 표시가 쭉 떠 있었죠. 그런데 실제로 도는 서비스를 확인해 보니 여전히 예전 이미지를 서빙하고 있더라고요. task definition은 등록됐는데 정작 ECS 서비스한테 그걸 쓰라고 아무도 안 알려준 거예요. 안정적인 컨테이너 배포에 이르기까지 겪은 “되는 것 같은데 실은 안 되는” 순간들, 그 첫 번째였어요.

Docker 컨테이너를 AWS에 배포하면서 docker build부터 자동 롤백이 붙은 무중단 롤링 업데이트까지 전체 파이프라인을 이해하고 싶다면, 이 글이 단계마다 짚어줘요. 문서가 미리 경고해 주지 않는 함정까지요.

문제

컨테이너화된 애플리케이션을 AWS에 배포하려면 여러 서비스를 조율해야 해요 — 이미지 저장용 ECR, 오케스트레이션용 ECS, 컴퓨팅용 Fargate — 여기에 인증 플로우, 이미지 태깅 전략, 배포 설정이 필요해요. 명확한 엔드투엔드 워크플로우가 없으면 배포가 에러 투성이가 돼요. 잘못된 저장소에 이미지가 푸시되고, task definition이 오래된 이미지를 참조하고, 롤링 업데이트가 다운타임을 일으키고, 실패한 배포에 자동 롤백이 없어요.

직접 겪은 어려움

이것들 전부 프로덕션에서 저를 힘들게 했어요:

  • ECR 인증은 세션 기반이에요. Docker 로그인 토큰이 12시간 뒤 만료돼요. CI/CD 파이프라인이 푸시 전에 토큰을 갱신하지 않으면 “no basic auth credentials”라는 암호 같은 에러와 함께 조용히 실패해요.
  • Task definition 버전 관리가 헷갈려요. ECS는 register-task-definition을 호출할 때마다 새 revision을 만들지만 서비스가 자동으로 최신 revision을 선택하지 않아요. 새 revision ARN으로 서비스를 명시적으로 업데이트해야 해요.
  • 롤링 업데이트 퍼센트 계산이 직관적이지 않아요. minimum_healthy_percentmaximum_percent는 절대값이 아니라 desired_count에 대한 상대값이에요. 배포 중 실제 task 수는 세 값의 조합에 따라 달라져요.
  • Health check 타이밍 갭이 배포를 죽여요. Health check grace period가 너무 짧으면 아직 시작 중인 task를 ECS가 종료해요(특히 JVM이나 NestJS처럼 콜드 스타트가 느린 앱). 무한 배포 루프가 생겨요.
  • Circuit breaker가 기본으로 활성화되어 있지 않아요. deployment_circuit_breaker 없이는 잘못된 이미지가 ECS로 하여금 실패하는 task를 끝없이 재시도하게 만들어요. 직접 개입할 때까지 Fargate 비용이 계속 나가요.
  • --force-new-deployment는 새로 푸시한 태그를 절대 배포하지 않아요. 지금 task definition이 고정해 둔 이미지 참조를 그대로 다시 당겨올 뿐이에요. :v1.0.6을 푸시하고 강제 재배포하면 새 태그를 고정한 task-def revision을 새로 등록하고 update-service --task-definition <반환된 ARN>을 호출하지 않는 한 조용히 예전 이미지가 다시 떠요. register-task-definition이 돌려주는 ARN을 꼭 붙잡으세요. 다음 revision 번호를 넘겨짚으면 안 돼요.
  • batch-get-image로 manifest를 캡처할 때는 assertion이 없으면 fail-open이에요. 맞지 않는 manifest는 failures[]에 들어가는데 명령은 그대로 0으로 끝나요. 그래서 단순하게 --query 'images[0].imageManifest'로 캡처하면 비어 있거나 null인 롤백 아티팩트를 성공한 것처럼 남겨요. set -euo pipefail 아래에서 jq -e 복합 assertion 하나로 캡처를 막아 두세요(failures가 비어 있고, 이미지가 정확히 하나이고, imageManifest가 null 아닌 mediaType을 가진 문자열일 것). 한 가지 주의할 점은 --accepted-media-types가 세 값만 허용한다는 거예요(Docker manifest v1/v2, OCI image manifest v1). manifest-list나 OCI-index 타입은 플래그 값으로 거부되니, index 기반 태그는 이 플래그 없이 다시 실행하세요.
  • 롤백 태그 복원: aws ecr put-image --image-tag <tag> --image-manifest file://saved.json --image-manifest-media-type "$(jq -r '.mediaType' saved.json)"로 미리 캡처해 둔 manifest에서 덮어쓴 mutable 태그를 되살려요. 고정된 태그를 덮어쓰는 푸시 전에 반드시 먼저 캡처해 두세요.

사용하면 좋을 때

Docker 컨테이너를 AWS에서 관리형 오케스트레이션으로 배포하거나, Kubernetes 복잡성 없이 AWS 네이티브 CI/CD를 원하거나, 무중단 롤링 배포가 필요하거나, 이미 Terraform으로 AWS 인프라를 관리하고 있을 때 적합해요.

쓰지 않는 게 좋을 때

단일 정적 사이트(S3 + CloudFront 사용), 멀티 클라우드 요구사항(Kubernetes 사용), 매우 짧은 배치 작업(Lambda 사용), 로컬 개발(Docker Compose 사용), 예산 제한이 있는 사이드 프로젝트(Docker가 설치된 t3.micro EC2가 더 저렴)에는 건너뛰세요.

이런 함정들을 염두에 두고, 아래에서 아키텍처와 배포 파이프라인의 각 단계를 살펴볼게요.

아키텍처 개요

배포 파이프라인은 로컬 코드에서 Docker 빌드를 거쳐 AWS 서비스로 흘러가요:

Loading diagram...

ECR: 이미지 저장소

ECR은 AWS의 관리형 Docker 컨테이너 레지스트리예요. 여기에 빌드한 이미지가 올라가 있다가, ECS가 컨테이너로 실행하려고 당겨가요.

ECR 저장소 만들기

resource "aws_ecr_repository" "app" {
  name                 = "my-app"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true  # 보안 스캐닝
  }
}

scan_on_push를 활성화하면 자동 취약점 스캐닝을 받을 수 있어요. OS 패키지의 알려진 CVE를 검사하고, 의존성 취약점을 스캔하고, AWS Console이나 API에서 결과를 확인할 수 있어요.

Push 워크플로우

모든 푸시는 인증, 빌드, 태그, 푸시 네 단계를 따라요:

# 1. Docker를 ECR에 인증
aws ecr get-login-password --region ap-northeast-2 | 
  docker login --username AWS --password-stdin 
  ${ACCOUNT_ID}.dkr.ecr.ap-northeast-2.amazonaws.com

# 2. 이미지 빌드
docker build -t my-app .

# 3. ECR용 태그
docker tag my-app:latest 
  ${ACCOUNT_ID}.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:latest

# 4. ECR에 푸시
docker push 
  ${ACCOUNT_ID}.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:latest

이미지가 ECR에 올라가면 이제 ECS가 배포를 넘겨받아요. 새 task definition을 등록하고 그걸 쓰라고 ECS 서비스에 알려주는 흐름이에요.

ECS 배포 흐름

전체 배포 파이프라인

코드 푸시부터 실행 중인 task까지의 전체 배포 순서예요:

Loading diagram...

수동 배포 단계

수동으로 배포해야 할 때(디버깅, 핫픽스) 이 세 명령어면 돼요:

# 1. 이미지 빌드 및 푸시 (위 참조)

# 2. 새 task definition 등록
aws ecs register-task-definition 
  --cli-input-json file://task-definition.json

# 3. 새 task definition으로 서비스 업데이트
aws ecs update-service 
  --cluster my-cluster 
  --service my-service 
  --task-definition my-task:NEW_REVISION 
  --force-new-deployment

마지막 명령어의 플래그 하나는 짚고 넘어갈 함정이에요. --force-new-deployment는 새로 푸시한 태그를 배포하지 않아요. 지금 task definition이 이미 고정해 둔 이미지 참조를 그대로 다시 당겨올 뿐이에요. :v1.0.6을 푸시하고 강제 재배포하면 조용히 예전 이미지가 다시 떠요. 이 글 맨 앞의 “초록 체크, 그런데 옛날 서비스” 실패와 똑같은 거예요. 모양만 살짝 다를 뿐이죠.

새 태그를 정말로 배포하려면, 그 태그를 고정한 task-definition revision을 새로 등록하고 update-service --task-definition <반환된 ARN>을 호출해야 해요. 다음 revision 번호가 비어 있다고 넘겨짚지 말고, register-task-definition이 돌려주는 ARN을 붙잡으세요. 동시에 도는 다른 배포가 그 번호를 먼저 채갈 수도 있거든요. 엉뚱한 revision을 가리키는 건 CI는 통과하고 프로덕션에서 터지는, 딱 그런 조용한 실수예요.

위 수동 단계는 원리를 보여줘요. 하지만 프로덕션에서는 무중단 배포가 필요하죠. 그게 바로 롤링 업데이트가 등장하는 지점이에요.

롤링 업데이트

동작 방식

ECS가 task를 하나씩 교체해서 무중단을 보장해요. 핵심은 이거예요. 이전 task를 드레인하고 종료하기 전에, 새 task가 먼저 시작해서 health check를 통과해요. desired_count = 2일 때는 이렇게 흘러가요:

Time     | Old v1.0 | New v2.0 | Total | Status
---------|----------|----------|-------|------------------
00:00    | 2        | 0        | 2     | Deploy starts
00:30    | 2        | 1        | 3     | New task starting
01:30    | 1        | 1        | 2     | First old removed
02:00    | 1        | 2        | 3     | Second new starting
03:00    | 0        | 2        | 2     | Complete

업데이트 중에 총 task 수가 desired_count를 초과하는 게 보이시죠. 이게 maximum_percent 설정이 하는 일이에요.

롤링 업데이트 프로세스 (3 Task)

Task가 3개인 경우 교체 과정을 시각화하면 이렇게 돼요:

Loading diagram...

롤링 업데이트 타임라인

각 task가 시작, health check, 드레인, 종료를 거치는 시간 흐름이에요:

Loading diagram...

각 Task 교체의 핵심 단계

  1. Starting (60-90초) — ECR에서 새 Docker 이미지 풀, 컨테이너 시작, 애플리케이션 초기화
  2. Health Checks (30-60초) — ALB health check 통과 필요, 설정된 포트에서 응답해야 하고, 여러 번 성공해야 함
  3. Draining (30-300초) — 이전 task에 새 요청 전송 중단, 기존 요청 완료 대기, graceful shutdown 기간
  4. Termination — 이전 task 완전 종료, 리소스 해제, 새 task 완전 가동

배포 설정

resource "aws_ecs_service" "app" {
  name            = "my-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = 2

  # 배포 동작
  deployment_minimum_healthy_percent = 100  # desired 미만 불가
  deployment_maximum_percent         = 200  # 일시적으로 2배 가능

  # 자동 롤백을 위한 circuit breaker
  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }
}

퍼센트 값은 desired_count에 대한 상대값이에요. desired_count = 3일 때:

  • minimum_healthy_percent = 100은 항상 최소 3개 task 유지를 의미해요
  • maximum_percent = 200은 배포 중 최대 6개 task까지 가능하다는 의미예요

전략 선택

전략Min %Max %속도위험도사용 사례
보수적100150느림낮음프로덕션
균형100200보통낮음대부분의 앱
공격적50200빠름보통스테이징

프로덕션에는 균형 전략을 추천해요. 배포 전체에 걸쳐 풀 용량을 유지하면서 이전 task가 드레인되기 전에 새 task가 시작될 충분한 여유를 줘요.

Circuit Breaker와 자동 롤백

Circuit breaker를 활성화하면 ECS가 실패한 배포를 감지해서 자동으로 마지막 안정 버전으로 롤백해요:

Loading diagram...

deployment_circuit_breaker 없이는 잘못된 이미지가 ECS로 하여금 실패하는 task를 끝없이 재시도하게 만들어요. 직접 개입할 때까지 Fargate 비용이 계속 나가요. 프로덕션 서비스에는 반드시 활성화하세요:

deployment_circuit_breaker {
  enable   = true
  rollback = true
}

자주 나오는 질문이 하나 있어요. 배포 도중에 auto-scaling이 끼어들면 어떻게 될까요? ECS가 알아서 잘 처리해요.

Auto-Scaling과 배포

Auto-scaling은 배포 중에도 멈추지 않아요. 계속 동작하고 그 상호작용을 이해해 두면 좋아요:

Loading diagram...

배포 중 auto-scaling이 활성화되어 있을 때의 핵심 동작:

  • Auto-scaling이 배포 중 task를 추가하면 새 task는 최신 버전을 받아요
  • Auto-scaling이 task를 제거하면 ECS가 이전 버전 task를 우선 제거해요
  • 배포 완료 후에도 scale 상태가 유지돼요

Auto-Scaling 상호작용 시나리오

시나리오발생하는 일결과
배포 중 CPU 스파이크Auto-scaling이 task 추가새로 추가된 task 포함 모두 배포 업데이트됨
배포 중 CPU 하락Auto-scaling이 task 제거더 적은 task로 배포 계속 진행
배포 중 메모리 이슈Auto-scaling 트리거신구 task 모두 스케일링 가능
배포 실패Task가 현재 버전에 유지Auto-scaling 정상 동작 계속

배포 중 CPU 스파이크 (2에서 3으로 확장)

Time     | Old v1.0 | New v2.0 | Total | Event
---------|----------|----------|-------|------------------
00:00    | 2        | 0        | 2     | Deployment starts
00:30    | 2        | 1        | 3     | New task starting
01:00    | 2        | 1        | 3     | CPU SPIKE -- auto-scale triggered
01:30    | 2        | 2        | 4     | Scale-out adds v2.0 task
02:00    | 1        | 2        | 3     | Remove one old task
02:30    | 1        | 3        | 4     | Add final new task
03:00    | 0        | 3        | 3     | All tasks now v2.0

Auto-Scaling 충돌 해결

Auto-scaling이 배포와 충돌하면 배포 중 일시적으로 스케일링을 중지할 수 있어요:

# 배포 중 auto-scaling 일시 중지
aws application-autoscaling register-scalable-target 
  --service-namespace ecs 
  --resource-id service/my-cluster/my-service 
  --scalable-dimension ecs:service:DesiredCount 
  --suspended-state 
    '{"DynamicScalingInSuspended": true, "DynamicScalingOutSuspended": true}'

# 배포 완료 후 다시 활성화
aws application-autoscaling register-scalable-target 
  --service-namespace ecs 
  --resource-id service/my-cluster/my-service 
  --scalable-dimension ecs:service:DesiredCount 
  --suspended-state 
    '{"DynamicScalingInSuspended": false, "DynamicScalingOutSuspended": false}'

GitHub Actions 워크플로우

다음 워크플로우는 main에 푸시하면 Docker 이미지를 빌드하고, ECR에 푸시하고, task definition을 업데이트하고, ECS에 배포하는 것까지 한 번에 처리해요:

name: Deploy to ECS

on:
  push:
    branches: [main]

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: ap-northeast-2

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

      - name: Build and push image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/my-app:$IMAGE_TAG .
          docker push $ECR_REGISTRY/my-app:$IMAGE_TAG
          docker tag $ECR_REGISTRY/my-app:$IMAGE_TAG $ECR_REGISTRY/my-app:latest
          docker push $ECR_REGISTRY/my-app:latest

      - name: Update ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: my-app
          image: ${{ steps.login-ecr.outputs.registry }}/my-app:${{ github.sha }}

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

wait-for-service-stability: true 플래그가 중요해요. 이거 없으면 워크플로우가 배포가 시작되자마자 성공으로 처리돼요. 배포가 끝났을 때가 아니라요. 배포가 실패하면 CI도 실패해야 하잖아요. 파이프라인을 자동화했으니, 이제 배포를 오래도록 안정적으로 유지해 주는 실천들을 볼게요.

모범 사례

이미지 태깅

추적성을 위해 여러 태그를 사용하세요:

권장 태그:
- Git SHA: my-app:abc123def  (고유, 추적 가능)
- 환경: my-app:prod-latest  (현재 프로덕션)
- 시맨틱: my-app:v1.2.3  (릴리스)

Git SHA 태그가 가장 유용해요. 프로덕션에서 뭔가 깨졌을 때, 각 task에서 실행 중인 정확한 커밋을 추적할 수 있어요.

ECR 수명 주기 정책

오래된 이미지가 빠르게 쌓여요. 수명 주기 정책을 설정해서 자동으로 정리하세요:

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep last 30 images",
      "selection": {
        "tagStatus": "any",
        "countType": "imageCountMoreThan",
        "countNumber": 30
      },
      "action": {
        "type": "expire"
      }
    }
  ]
}

롤백: 덮어쓴 태그 복원

Circuit breaker는 ECS 서비스를 이전 task definition으로 되돌려줘요. 하지만 ECR에서 mutable 태그를 이미 덮어썼다면 손을 쓸 수 없어요. 그 시점엔 예전 이미지 참조가 그냥 사라진 거예요. 되살리려면 덮어쓰는 푸시 전에 이미지 manifest를 캡처해 두고, 나중에 복원해야 해요. 순서가 전부예요. 고정된 태그를 덮어쓰는 푸시가 있기 전에 캡처해야 해요. 덮어쓴 다음엔 캡처할 게 남아 있지 않으니까요.

캡처가 발목을 잡는 부분이기도 해요. aws ecr batch-get-image는 fail-open이에요. 맞지 않는 manifest는 failures[] 배열에 들어가는데 명령은 여전히 0으로 끝나요. 그래서 단순하게 --query 'images[0].imageManifest'를 쓰면 비어 있거나 null인 아티팩트를 남기는데 정작 필요해지는 순간까지는 성공한 것처럼 보여요. set -euo pipefail 아래에서 jq -e 복합 assertion 하나로 캡처를 막아 두세요. failures 배열이 비어 있고, 이미지가 정확히 하나 돌아왔고, imageManifest가 null 아닌 mediaType을 가진 문자열이어야 해요. 또 하나 날카로운 부분은 --accepted-media-types인데, 세 값만 허용해요(Docker manifest v1, Docker manifest v2, OCI image manifest v1). manifest-list와 OCI-index 타입은 플래그 값으로 거부되니, index 기반 태그는 이 플래그 없이 다시 실행하면 돼요.

덮어쓰기 전에 검증된 manifest를 저장해 뒀다면, 태그 복원은 명령어 하나예요:

aws ecr put-image 
  --image-tag <tag> 
  --image-manifest file://saved.json 
  --image-manifest-media-type "$(jq -r '.mediaType' saved.json)"

Health Check

적절한 health check가 잘못된 배포가 서비스를 다운시키는 걸 막아요:

resource "aws_lb_target_group" "app" {
  # ...
  health_check {
    path                = "/health"
    healthy_threshold   = 2
    unhealthy_threshold = 3
    timeout             = 5
    interval            = 30
    matcher             = "200"
  }
}

시작이 느린 애플리케이션에는 health check grace period를 넉넉하게 잡으세요. 부팅에 30초 걸리는 NestJS 앱은 최소 60초의 grace period가 필요해요. 그렇지 않으면 ECS가 시작이 끝나기 전에 task를 종료해요.

Graceful Shutdown

롤링 업데이트 중 task를 종료하기 전에 ECS가 SIGTERM을 보내요. 애플리케이션이 이 시그널을 처리해야 진행 중인 요청을 드롭하지 않아요:

// Graceful shutdown 처리 (Node.js)
process.on("SIGTERM", async () => {
  console.log("SIGTERM received, starting graceful shutdown");

  // 새 요청 수신 중단
  server.close(() => {
    console.log("HTTP server closed");
  });

  // 데이터베이스 연결 종료
  await database.close();

  // 진행 중인 요청 완료 대기 (최대 30초)
  setTimeout(() => {
    process.exit(0);
  }, 30000);
});

// Health check 엔드포인트 (배포 추적을 위해 버전 포함)
app.get("/health", (req, res) => {
  res.status(200).json({
    status: "healthy",
    version: process.env.APP_VERSION,
  });
});

배포 모니터링

롤링 업데이트 중에 이상 징후를 조기에 감지하려면 이 시그널들을 모니터링하세요:

Loading diagram...
# 배포 진행 상황 실시간 확인
watch -n 5 'aws ecs describe-services 
  --cluster my-cluster 
  --services my-service 
  --query "services[0].deployments"'

# 최근 배포 이벤트 확인
aws ecs describe-services 
  --cluster my-cluster 
  --services my-service 
  --query 'services[0].events[0:5]'

트러블슈팅

배포가 멈췄을 때

배포가 멈추면 이 두 가지부터 확인하세요:

# Task 중지 이유 확인
aws ecs describe-tasks 
  --cluster my-cluster 
  --tasks $(aws ecs list-tasks --cluster my-cluster --query 'taskArns[0]' --output text) 
  --query 'tasks[0].stoppedReason'

# 서비스 이벤트 확인
aws ecs describe-services 
  --cluster my-cluster 
  --services my-service 
  --query 'services[0].events[:5]'

자주 발생하는 문제

문제원인해결 방법
Task health check 실패앱이 아직 준비 안 됨Health check grace period 늘리기
메모리 부족컨테이너에 더 많은 RAMTask 메모리 늘리기
IP 없음서브넷 가득 참더 큰 서브넷이나 멀티 AZ 사용
이미지 풀 실패ECR 인증 만료ECR 토큰 갱신
느린 배포보수적인 min/max %max % 올리거나 min % 내리기
자동 롤백 없음Circuit breaker 미설정deployment_circuit_breaker 활성화
Auto-scaling 충돌스케일링이 배포와 충돌배포 중 auto-scaling 일시 중지

배포 멈춤 의사결정 트리

배포가 멈췄을 때 빠르게 원인을 파악하는 흐름이에요:

Loading diagram...

실전 정리

ECR/ECS 배포는 기술 문제라기보다 조율 문제예요. 이미지 레지스트리, task definition, 서비스 업데이트, health check — 하나씩 떼어 놓으면 다 잘 동작해요. 어려운 건 이걸 안정적으로 함께 굴러가게 만드는 거예요. 가장 중요한 것들을 꼽아 볼게요.

  1. Circuit breaker는 항상 켜 두세요. deployment_circuit_breaker 없이는 잘못된 이미지가 ECS를 실패하는 task 재시도의 무한 루프에 빠뜨려요. 직접 개입할 때까지 Fargate 비용이 계속 나가고요. Terraform 한 줄이면 되는데 새벽 3시 호출을 막아줘요.
  2. 이미지는 latest가 아니라 git SHA로 태그하세요. :latest는 편하지만 어떤 버전이 도는지 알 수 없어서 롤백이 괴로워져요. git SHA 태그(my-app:abc123def)를 쓰면 실행 중인 task에서 소스 커밋까지 바로 추적돼요.
  3. Health check grace period는 넉넉하게 잡으세요. 앱이 시작하는 데 30초가 걸리면(JVM이나 NestJS 앱이면 흔해요) grace period 10초는 무한 배포 루프를 만들어요. ECS가 task를 띄우고, 준비되기 전에 죽이고, 또 띄우고, 또 죽여요. 최악의 시작 시간의 2배 이상으로 잡으세요.
  4. 앱에서 SIGTERM을 처리하세요. ECS는 롤링 업데이트 중 task를 종료하기 전에 SIGTERM을 보내요. 앱이 이 시그널을 처리하지 않으면 진행 중이던 요청이 그냥 끊겨요. 위의 Node.js graceful shutdown 패턴은 10줄이면 되고 배포 중 데이터 손실을 막아줘요.

이 글의 GitHub Actions 워크플로우는 그대로 가져다 쓸 수 있는 프로덕션 출발점이에요. 클론해서 cluster와 service 이름만 바꾸면, 실패 시 자동 롤백이 붙은 무중단 배포가 완성돼요.

참고 자료

댓글

글 목록으로 돌아가기
enko