ECS vs EC2
분류: Layer 3 - AWS 인프라 & 보안
1. 한 줄 정의
섹션 제목: “1. 한 줄 정의”EC2는 AWS에서 빌려 쓰는 가상 서버이고, ECS는 Docker 컨테이너를 자동으로 배포/관리해주는 컨테이너 오케스트레이션 서비스이다.
2. 왜 중요한가
섹션 제목: “2. 왜 중요한가”“우리 서비스가 어디서 돌아가는지”를 이해하는 핵심 질문이다. EC2 위에서 직접 서버를 관리하는 것과, ECS로 컨테이너를 관리하는 것은 운영 방식이 근본적으로 다르다.
3. 핵심 개념
섹션 제목: “3. 핵심 개념”EC2 (Elastic Compute Cloud)
섹션 제목: “EC2 (Elastic Compute Cloud)”AWS에서 빌려 쓰는 가상 서버(VM). 리눅스/윈도우 OS가 깔려 있고, SSH로 접속해서 직접 세팅한다.
ECS (Elastic Container Service)
섹션 제목: “ECS (Elastic Container Service)”Docker 컨테이너를 실행/관리해주는 서비스. “이 이미지를, 몇 개, 어떤 리소스로 실행해줘”라고 정의하면 ECS가 알아서 배포하고 관리한다.
비유로 이해하면: “EC2 vs ECS를 선택한다”는 것은 “핸들 vs 자동차를 선택한다”와 같다. EC2는 서버(자동차)이고, ECS는 그 서버를 운전하는 방식(핸들)이다. ECS + Fargate 조합에서는 자동차 자체도 AWS가 제공한다.
📖 더 보기: Amazon ECS vs Amazon EC2: Complete Comparison Guide — EC2와 ECS의 역할 차이, 비용 모델, 선택 기준을 실무 관점에서 정리
ECS 내부 동작 원리
섹션 제목: “ECS 내부 동작 원리”- Task Definition 등록: “어떤 이미지를 어떤 설정으로 실행할지” JSON으로 정의
- Service 생성: “이 Task를 N개 유지해줘”라고 ECS에 지시
- Scheduler 동작: ECS 스케줄러가 어떤 인프라(Fargate or EC2)에서 실행할지 결정
- Container Agent 실행: Fargate는 AWS가, EC2 모드는 EC2 인스턴스 위의 ECS Agent가 컨테이너를 시작
- Health Check 모니터링: ALB Health Check 또는 컨테이너 Health Check 결과에 따라 비정상 Task를 자동으로 교체
Fargate 격리 모델 — 보안과 성능
섹션 제목: “Fargate 격리 모델 — 보안과 성능”Fargate는 각 Task가 고유한 마이크로VM(Firecracker 기술 기반) 위에서 실행된다. 즉, Task끼리 커널, CPU, 메모리를 공유하지 않는다. 이는 EC2 launch type(여러 컨테이너가 같은 EC2 인스턴스 커널을 공유)과 근본적으로 다른 격리 수준이다.
실무에서 의미하는 것:
- Fargate는 다른 테넌트의 컨테이너로부터 격리가 강함 → 금융/의료 데이터 등 규정 준수 환경에 유리
- 각 Task가 별도 ENI를 가짐 → Security Group을 Task 단위로 적용 가능
- 단, 격리 오버헤드로 인해 동일 메모리 기준 EC2 launch type보다 약간 느릴 수 있음
ECS 실행 모드 (2가지)
섹션 제목: “ECS 실행 모드 (2가지)”- EC2 launch type: EC2 인스턴스 위에 컨테이너를 올림. 서버를 직접 관리해야 하지만 비용 효율적.
- Fargate launch type: 서버를 아예 신경 안 써도 됨. “컨테이너만 정의하면 AWS가 알아서 서버 관리.”
ECS 네트워킹 모드 (awsvpc)
섹션 제목: “ECS 네트워킹 모드 (awsvpc)”Fargate는 awsvpc 모드만 지원한다. 이 모드에서는 각 Task가 고유한 ENI(Elastic Network Interface)와 Private IP를 받는다.
- 장점: Security Group을 Task 단위로 적용 가능
- 장점: 포트 충돌 없음 (Task마다 독립된 네트워크 인터페이스)
- 주의: Task 수가 많으면 VPC의 IP 주소 소진 가능 → Subnet CIDR 범위를 여유 있게 설계
ECS 핵심 용어
섹션 제목: “ECS 핵심 용어”- Task Definition: 컨테이너 실행 스펙 (어떤 이미지, CPU/메모리, 포트, 환경변수 등)
- Task: Task Definition을 실행한 인스턴스 (= 실행 중인 컨테이너 묶음)
- Service: Task를 원하는 개수만큼 유지해주는 단위. 1개가 죽으면 자동으로 새로 띄움.
- Cluster: Service와 Task를 묶는 논리적 그룹.
Task Definition 예시 (Fargate)
섹션 제목: “Task Definition 예시 (Fargate)”{ "family": "my-nest-app", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "containerDefinitions": [ { "name": "my-nest-app", "image": "123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-nest-app:latest", "portMappings": [ { "containerPort": 3000, "protocol": "tcp" } ], "environment": [{ "name": "NODE_ENV", "value": "production" }], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/my-nest-app", "awslogs-region": "ap-northeast-2", "awslogs-stream-prefix": "ecs" } } } ]}📖 더 보기: AWS 공식 - ECS Fargate 시작하기 — Task Definition 생성부터 Service 배포까지 공식 튜토리얼
외부 트래픽 흐름: ALB → ECS
섹션 제목: “외부 트래픽 흐름: ALB → ECS”인터넷 → ALB(로드밸런서) → Target Group → ECS Service → Task(컨테이너)ALB 없이는 외부에서 ECS 컨테이너에 직접 접근할 수 없다.
ECS Service 생성 시 ALB와 Target Group을 연결하는 설정이 필요하다.
배포 흐름 — Rolling Update 내부 동작
섹션 제목: “배포 흐름 — Rolling Update 내부 동작”ECS Service의 Rolling Update 방식 배포 과정:
- 새 Task Definition 등록 (이미지 태그 변경 등)
- ECS Service가 새 Task Definition으로 신규 Task 시작
- 신규 Task가 ALB Target Group에 등록되고 Health Check 통과 대기
- 신규 Task 정상 확인 → ALB가 신규 Task로 트래픽 전환
- 구버전 Task를 ALB에서 제거(Deregister) → Drain 후 종료
이 과정에서 minimumHealthyPercent(최소 유지 Task 비율)와 maximumPercent(최대 Task 비율) 설정으로 무중단 배포를 보장한다.
ECS Auto Scaling — 트래픽에 따른 자동 Task 수 조절
섹션 제목: “ECS Auto Scaling — 트래픽에 따른 자동 Task 수 조절”ECS Service는 Application Auto Scaling을 통해 Task 수를 자동으로 늘리거나 줄인다. 실무에서 주로 사용하는 두 가지 방식이 있다.
Target Tracking Scaling (권장): 특정 메트릭의 목표값을 유지하도록 Task 수를 자동 조절한다. 예를 들어 “CPU 사용률 60%를 유지”로 설정하면, 사용률이 60%를 넘으면 Task를 늘리고 낮아지면 줄인다. 설정이 간단하고 AWS가 스케일링 계산을 대신 해주는 것이 장점이다.
// Target Tracking 정책 예시: CPU 60% 목표{ "TargetValue": 60.0, "PredefinedMetricSpecification": { "PredefinedMetricType": "ECSServiceAverageCPUUtilization" }, "ScaleInCooldown": 300, "ScaleOutCooldown": 60}// ScaleOutCooldown: 60초 — 급격한 트래픽 증가에 빠르게 반응// ScaleInCooldown: 300초 — Scale In은 천천히 (플래핑 방지)Step Scaling: 메트릭 값의 구간(Step)별로 Task 수 변화량을 직접 지정한다. 예를 들어 “CPU 70~85%이면 Task +2, 85% 이상이면 Task +4”처럼 세밀한 제어가 필요할 때 사용한다. 예측 가능한 트래픽 패턴이 있는 서비스에 적합하다.
실무 기준값: CPU 60%, Memory 70%를 Scale Out 기준으로 설정하는 것이 일반적이다. 최소 Task 수는 Multi-AZ를 위해 2개 이상, 최대 Task 수는 VPC Subnet의 가용 IP 수와 RDS max_connections를 고려해 설정한다.
# Auto Scaling 설정 등록 (ECS Service에 Scalable Target 연결)aws application-autoscaling register-scalable-target \ --service-namespace ecs \ --resource-id service/my-cluster/my-service \ --scalable-dimension ecs:service:DesiredCount \ --min-capacity 2 \ --max-capacity 20
# Target Tracking 정책 생성aws application-autoscaling put-scaling-policy \ --service-namespace ecs \ --resource-id service/my-cluster/my-service \ --scalable-dimension ecs:service:DesiredCount \ --policy-name cpu-target-tracking \ --policy-type TargetTrackingScaling \ --target-tracking-scaling-policy-configuration file://scaling-policy.jsonECS 비용 최적화 전략
섹션 제목: “ECS 비용 최적화 전략”Fargate 비용은 vCPU/시간 + Memory GB/시간으로 청구된다. 잘못 설정된 Task 스펙은 가장 큰 낭비 원인이다.
-
Right-Sizing (태스크 스펙 최적화)
AWS Compute Optimizer가 실제 사용량을 분석해 CPU/메모리 권고안을 제시한다. 적절히 조정하면 30~70% 비용 절감이 가능하다.
Terminal window # CloudWatch에서 실제 CPU/메모리 사용량 확인aws cloudwatch get-metric-statistics \--namespace AWS/ECS \--metric-name CPUUtilization \--dimensions Name=ClusterName,Value=my-cluster Name=ServiceName,Value=my-service \--start-time 2024-01-01T00:00:00Z \--end-time 2024-01-15T00:00:00Z \--period 3600 \--statistics Average# → 평균 CPU 사용률이 20% 미만이면 Task CPU를 절반으로 줄일 수 있음 -
Fargate Spot 활용
상태 비저장(Stateless), 중단 가능한 배치 작업이나 개발 환경은 Fargate Spot을 사용하면 최대 70% 절감된다.
// capacityProviderStrategy에서 Spot 비중 설정{"capacityProviders": [{ "capacityProvider": "FARGATE", "weight": 1, "base": 1 },{ "capacityProvider": "FARGATE_SPOT", "weight": 3 }]}// → 4개 Task 중 1개는 On-Demand(안정), 3개는 Spot(70% 저렴) -
Compute Savings Plans
1년 또는 3년 약정으로 Fargate 비용을 최대 52% 절감. EC2, Lambda와 통합 적용된다. 예측 가능한 프로덕션 워크로드에 적합하다.
-
Public IP 비용 제거
ECS Task에 Public IP를 할당하면 IP당 시간 과금이 발생한다 (2024년부터 AWS 유료화). Private Subnet + ALB 구조로 전환하면 비용 절감과 보안 강화를 동시에 달성한다.
4. 실무에서 어디에 쓰이나
섹션 제목: “4. 실무에서 어디에 쓰이나”- 웹 서비스 배포 (ECS Service + ALB로 컨테이너 운영)
- 배치 작업 실행 (ECS Task로 1회성 실행)
- 개발/스테이징/프로덕션 환경 분리 (Cluster 단위)
- Auto Scaling (트래픽에 따라 컨테이너 수 자동 조절)
5. 현재 내 업무와 연결점
섹션 제목: “5. 현재 내 업무와 연결점”- 팀 서비스가 ECS로 배포되고 있다면, 배포 구조를 이해하는 데 필수
- 배포 실패 시 Task 상태, 로그, Service 이벤트를 확인해야 함
- “서버 리소스가 부족하다”는 이슈 대응 시 Task Definition의 CPU/메모리 확인
- 새 서비스 배포 시 ECS 설정을 이해하고 있어야 함
⚠️ Fargate에서는 docker exec이 안 된다
컨테이너에 직접 접속하려면 AWS ECS Exec을 써야 한다. 자주 환경변수를 확인하거나 내부 상태를 디버깅할 때 필요하다.
aws ecs execute-command \ --cluster my-cluster \ --task <task-id> \ --container my-container \ --interactive --command "/bin/sh"5.5 컨테이너 오케스트레이션 원리의 전이
섹션 제목: “5.5 컨테이너 오케스트레이션 원리의 전이”ECS를 이해하면 Kubernetes(K8s)나 Nomad 같은 다른 오케스트레이터로 전이하기 쉽다. 핵심 개념이 대응되기 때문이다.
ECS ↔ Kubernetes 핵심 개념 대응 표
섹션 제목: “ECS ↔ Kubernetes 핵심 개념 대응 표”| ECS 개념 | Kubernetes 대응 | 역할 |
|---|---|---|
| Task Definition | Pod Spec (manifest의 spec 섹션) | 컨테이너 이미지, CPU/메모리, 포트, 환경변수 등 실행 스펙 정의 |
| Task | Pod | 실제 실행 중인 컨테이너 인스턴스 |
| Service | Deployment + Service | Desired State 유지(Deployment) + 네트워크 엔드포인트 노출(Service) |
| Cluster | Namespace + Node 그룹 | 워크로드를 묶는 논리적 단위 |
| Task Role | ServiceAccount + RBAC | 앱 코드가 AWS/Cloud 서비스에 접근할 때 사용하는 권한 ID |
| Execution Role | kubelet 자격증명 | 컨테이너 런타임이 이미지 Pull, 로그 전송 등 플랫폼 작업 수행 시 사용 |
| Service Discovery | CoreDNS + Service ClusterIP | 서비스 이름으로 내부 통신 (ECS: <svc>.<namespace>.svc.cluster.local) |
| Health Check (ALB/컨테이너) | Liveness/Readiness Probe | 비정상 컨테이너 자동 교체 트리거 |
| Fargate | Knative / Fargate for EKS | 서버 프로비저닝 없이 컨테이너만 정의하는 서버리스 레이어 |
📖 참고: ECS Terminology for Kubernetes Users (Medium) — K8s 배경 엔지니어가 ECS로 전환할 때 참고
Desired State Reconciliation — 공통 원리
섹션 제목: “Desired State Reconciliation — 공통 원리”ECS와 Kubernetes 모두 Desired State Reconciliation 방식으로 동작한다. 운영자가 “Service를 3개 유지”라고 선언(Declare)하면, 컨트롤 플레인이 실제 상태(Actual State)를 주기적으로 체크해 선언된 상태(Desired State)로 맞춘다.
[ECS 흐름]Service desiredCount=3 선언→ ECS Scheduler가 실행 중 Task 수 확인→ Task 1개 죽음 → Scheduler가 새 Task 1개 시작 → desiredCount=3 복원
[K8s 흐름]Deployment replicas=3 선언→ ReplicaSet Controller가 실행 중 Pod 수 확인→ Pod 1개 죽음 → Controller가 새 Pod 1개 스케줄 → replicas=3 복원이 원리를 이해하면 ECS뿐 아니라 K8s, Nomad 어디서도 같은 사고방식을 적용할 수 있다.
5.7 ECS vs EKS 선택 매트릭스
섹션 제목: “5.7 ECS vs EKS 선택 매트릭스”ECS와 EKS는 모두 컨테이너 오케스트레이터이지만 선택 기준이 명확히 다르다.
| 선택 기준 | ECS 유리 | EKS 유리 |
|---|---|---|
| 팀 규모 / DevOps 전문성 | 소~중규모, 전담 SRE 없음 | 중~대규모, Kubernetes 전문 SRE 보유 |
| 운영 복잡도 감내 수준 | 낮은 운영 부담 선호 | 복잡도를 감내하고 세밀한 제어 필요 |
| 멀티클라우드 요구 | AWS 단일 환경 | GCP/Azure 혼용 또는 온프레미스 병행 (K8s 표준 활용) |
| 오픈소스 생태계 필요 | 불필요 (AWS 관리형으로 충분) | Istio, Argo CD, OPA, KEDA 등 CNCF 생태계 활용 필요 |
| 클러스터 업그레이드 부담 | 낮음 (AWS가 컨트롤 플레인 관리) | 높음 (K8s 연 3회 릴리스, 버전 만료 관리 필요) |
| 멀티테넌시 / Namespace 격리 | 제한적 (Cluster 단위) | 강력한 Namespace 기반 격리 지원 |
| AWS 서비스 통합 간편성 | 매우 높음 (IAM, ALB, ECR 네이티브 통합) | 높음 (추가 설정 필요할 수 있음) |
| 추가 비용 | ECS 자체 무료 (Fargate/EC2 과금) | EKS 클러스터당 $0.10/시간 (~$73/월) 추가 과금 |
실무 판단 기준: 팀이 Kubernetes 전문가 없이 AWS 워크로드를 빠르게 운영해야 한다면 ECS가 답이다. 멀티클라우드 이식성이나 CNCF 생태계 도구(Istio, Argo CD 등)가 필요하다면 EKS를 선택한다. AWS도 공식적으로 “대부분의 고객은 두 서비스를 혼용한다”고 밝히고 있으며, 선택은 단방향 결정이 아니다.
5.9 Fargate vs EC2 launch type 비용 비교
섹션 제목: “5.9 Fargate vs EC2 launch type 비용 비교”Fargate는 서버 관리 비용을 없애는 대신 컨테이너 리소스에 프리미엄이 붙는다. 실제 비용을 파악하지 않으면 예상보다 훨씬 높은 청구서를 받을 수 있다.
과금 구조
섹션 제목: “과금 구조”| 항목 | Fargate | EC2 launch type |
|---|---|---|
| 과금 단위 | vCPU/시간 + GB/시간 (초 단위 과금) | EC2 인스턴스 시간 + EBS 볼륨 |
| 최소 과금 | 1분 | 인스턴스 타입에 따라 다름 |
| 서버 관리 | 불필요 (AWS 관리) | OS 패치, 에이전트 업데이트 직접 관리 |
| 인스턴스 공유 | 불가 (Task별 격리) | 여러 Task가 인스턴스 공유 가능 |
비용 예시: 단일 Task 기준 (ap-northeast-2, Linux/x86)
섹션 제목: “비용 예시: 단일 Task 기준 (ap-northeast-2, Linux/x86)”AWS Fargate 공식 요금 (참고: AWS Fargate Pricing)
- vCPU: 약 $0.04048/시간 (서울 리전 기준, 환율/리전에 따라 변동)
- 메모리: 약 $0.004445/GB/시간
Task 스펙: 0.25 vCPU / 0.5 GB — 24시간 × 30일 운영
| 항목 | 계산 | 월 비용(USD) |
|---|---|---|
| Fargate vCPU | 0.25 × $0.04048 × 720h | ~$7.29 |
| Fargate 메모리 | 0.5 × $0.004445 × 720h | ~$1.60 |
| Fargate 합계 | ~$8.89 | |
| t3.small On-Demand (2 vCPU, 2 GB) | ~$0.0208/시간 × 720h | ~$14.98 |
- Fargate 0.25vCPU/0.5GB는 최소 스펙이므로, 인스턴스 비용만 보면 t3.small보다 저렴하다.
- 그러나 실무에서는 Task 여러 개를 EC2 1개에 집약(bin packing)하므로, Task 수가 많아질수록 EC2 launch type이 유리해진다.
Task 10개 운영 시 비교 (0.5 vCPU / 1 GB per Task)
| 방식 | 월 비용 추정 |
|---|---|
| Fargate × 10 Task | (0.5 × $0.04048 + 1 × $0.004445) × 720h × 10 ≈ $177 |
| t3.large × 2대 (EC2) | $0.0832/h × 720h × 2 ≈ $120 (서버 관리 비용 별도) |
💡 실무 결론: Task 수가 적고 팀이 작다면 Fargate의 운영 편의성이 비용 차이를 상쇄한다. Task 수가 20개 이상이고 워크로드가 예측 가능하다면 EC2 launch type + Reserved Instance를 검토할 만하다.
📖 참고: AWS Blog - Theoretical cost optimization by Amazon ECS launch type: Fargate vs EC2
6. 자주 헷갈리는 개념 비교
섹션 제목: “6. 자주 헷갈리는 개념 비교”| 개념 A | 개념 B | 차이점 |
|---|---|---|
| EC2 | ECS | EC2는 서버 자체, ECS는 컨테이너 관리 서비스 |
| ECS + EC2 | ECS + Fargate | EC2 모드는 서버 직접 관리, Fargate는 서버리스 |
| Task | Service | Task는 1회 실행 단위, Service는 Task를 원하는 수만큼 유지 |
| ECS | EKS | ECS는 AWS 전용 오케스트레이션, EKS는 Kubernetes 기반 |
| Task Role | Execution Role | Task Role은 앱이 AWS 서비스 접근할 때, Execution Role은 ECS가 태스크 시작할 때 |
6.5 트러블슈팅
섹션 제목: “6.5 트러블슈팅”🔧 Task가 계속 STOPPED 상태로 반복 재시작되는 경우
섹션 제목: “🔧 Task가 계속 STOPPED 상태로 반복 재시작되는 경우”증상: ECS Service 이벤트 탭에서 Task stopped (Exit Code: 1) 또는 Task stopped (Essential container exited) 메시지가 반복됨
원인: 컨테이너 애플리케이션이 시작 직후 오류로 종료되고 있음. 환경변수 누락, DB 연결 실패, 포트 바인딩 실패 등이 원인
해결:
- ECS 콘솔 → 해당 Service → Tasks 탭 → Stopped 상태의 Task 클릭
Stopped Reason필드 확인 (예:"Essential container exited")- CloudWatch Logs에서 컨테이너 로그 확인 (
/ecs/<task-definition-family>로그 그룹) - 로그에서 실제 오류 메시지 확인 후 애플리케이션 수정
경로: CloudWatch → Log Groups → /ecs/my-nest-app → 최신 로그 스트림예상 출력 (NestJS 환경변수 누락 시):Error: Config validation error: "DATABASE_URL" is required at Object.<anonymous> (/app/dist/main.js:1:...)🔧 “CannotPullContainerError” — 이미지 Pull 실패
섹션 제목: “🔧 “CannotPullContainerError” — 이미지 Pull 실패”증상: Task가 시작되지 않고 CannotPullContainerError: failed to resolve... access denied 에러로 멈춤
원인 1: Task Definition에 설정된 Execution Role에 ECR Pull 권한(ecr:GetAuthorizationToken, ecr:BatchGetImage 등)이 없음
원인 2: Fargate Task가 Private Subnet에 있는데 NAT Gateway 또는 ECR VPC Endpoint가 없어서 ECR에 접근 불가
해결:
- Task Definition의
taskRoleArnvsexecutionRoleArn구분 확인 (ECR Pull은executionRoleArn) AmazonECSTaskExecutionRolePolicy관리형 정책이 Execution Role에 붙어있는지 확인- Fargate가 Private Subnet이면 NAT Gateway 또는 ECR/CloudWatch VPC Endpoint 설정
🔧 “OOMKilled” — 메모리 부족으로 컨테이너 종료
섹션 제목: “🔧 “OOMKilled” — 메모리 부족으로 컨테이너 종료”증상: Task가 exit code 137 또는 OOMKilled로 종료됨. 트래픽이 몰릴 때 또는 서비스 시작 직후 발생
원인: Task Definition에 설정된 메모리 한도(Memory)보다 컨테이너가 더 많은 메모리를 사용하려 할 때 Linux 커널이 프로세스를 강제 종료
해결:
- CloudWatch → ECS 메트릭 →
MemoryUtilization으로 최대 사용량 확인 - Task Definition의
memory값을 현재 최대 사용량보다 20~30% 여유 있게 증가 - NestJS의 경우
--max-old-space-sizeNode.js 옵션 조정 검토
🔧 Fargate Task가 PROVISIONING 상태에서 멈추는 경우
섹션 제목: “🔧 Fargate Task가 PROVISIONING 상태에서 멈추는 경우”증상: ECS Task가 PROVISIONING 상태에서 수 분이 지나도 RUNNING으로 넘어가지 않음
원인: Private Subnet에 배치된 Fargate Task가 ECR 이미지를 Pull하거나 Secrets Manager에 접근하기 위한 인터넷 경로(NAT Gateway 또는 VPC Endpoint)가 없음
해결:
확인 경로:1. VPC → Route Tables → Private Subnet 연결된 Route Table → 0.0.0.0/0 → NAT Gateway 경로 존재 여부 확인2. 또는 VPC Endpoint 확인: → VPC → Endpoints → com.amazonaws.ap-northeast-2.ecr.dkr → com.amazonaws.ap-northeast-2.ecr.api → com.amazonaws.ap-northeast-2.logs (CloudWatch) 위 세 Endpoint가 없으면 NAT Gateway 없이는 이미지 Pull 불가🔧 ECS Service의 배포가 진행되지 않고 멈추는 경우
섹션 제목: “🔧 ECS Service의 배포가 진행되지 않고 멈추는 경우”증상: 새 Task Definition으로 배포를 시작했는데 서비스가 오랫동안 in progress 상태. 구버전 Task는 계속 실행 중이고 신버전 Task가 교체되지 않음
원인 1: 신규 Task의 ALB Health Check가 통과되지 않아 ECS가 구버전을 제거하지 못함
원인 2: minimumHealthyPercent가 100%로 설정되어 새 Task가 올라올 여유 공간이 없음
원인 3: 새 Task 자체가 시작 직후 실패하고 있어 ECS가 계속 새 Task를 시도하다 포기하지 못함
해결:
# 1. Service 이벤트 탭에서 배포 상태 확인# ECS → Clusters → <클러스터> → Services → <서비스> → Events 탭# 예상 출력:# service my-service has started 1 tasks: task abc123.# service my-service (port 3000) is unhealthy in target-group ...due to (reason Health checks failed)
# 2. ALB Target Group 상태 확인aws elbv2 describe-target-health \ --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-2:123456789:targetgroup/my-tg/xxx# → "State": "unhealthy", "Description": "Health checks failed" 나오면 Health Check 경로/포트 확인
# 3. Health Check 설정 점검# ALB → Target Groups → <그룹> → Health checks 탭# 확인: Health check path가 NestJS에서 실제 200을 반환하는 경로인지 (예: /health)🔧 ALB 502 Bad Gateway 에러 — 프로덕션에서 간헐적으로 발생
섹션 제목: “🔧 ALB 502 Bad Gateway 에러 — 프로덕션에서 간헐적으로 발생”증상: 하루에 수십 건 ALB에서 502 에러 반환. CloudWatch에서 HTTPCode_ELB_5XX_Count 지표가 산발적으로 올라감. 트래픽이 많지 않을 때도 발생
원인: ECS Task가 종료(스케일 다운 또는 배포)될 때 ALB가 아직 해당 Target을 De-register하기 전에 요청이 들어오는 경우. 또는 NestJS 앱이 응답 전에 연결을 닫는 경우
해결:
# 1. ALB Access Log 활성화 → 502 발생 시간과 Target IP 확인# ALB → Attributes → Access logs → S3 버킷 지정
# 2. Target Group의 Deregistration delay 확인 (기본 300초)aws elbv2 describe-target-group-attributes \ --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-2:123456789:targetgroup/my-tg/xxx# → "deregistration_delay.timeout_seconds": "300"# 너무 길면 배포가 느려지고, 너무 짧으면 502 발생 가능
# 3. NestJS Graceful Shutdown 구현 (핵심 해결책)# main.ts에 추가:// main.ts — NestJS Graceful Shutdownasync function bootstrap() { const app = await NestFactory.create(AppModule);
// ALB가 De-register 신호 보낸 후 실제 연결이 드레인될 시간 확보 app.enableShutdownHooks();
await app.listen(3000);}
// app.module.ts 또는 별도 모듈@Injectable()export class AppService implements OnApplicationShutdown { async onApplicationShutdown(signal: string) { console.log(`Shutting down on signal: ${signal}`); // 진행 중인 DB 트랜잭션, SQS 처리 등을 완료할 시간 확보 await new Promise((resolve) => setTimeout(resolve, 5000)); }}# 4. ECS Task Definition에 Stop Timeout 설정 (Fargate 기본 30초)# Task Definition → stopTimeout: 60 (최대 120초)# → SIGTERM 수신 후 60초 동안 Graceful Shutdown 시간 부여AWS 권고: ALB Deregistration delay(300초)와 ECS stopTimeout(120초 이하), NestJS Graceful Shutdown 3가지를 모두 맞춰야 502가 사라진다.
🔧 HealthCheckGracePeriod 미설정으로 Task 반복 재시작
섹션 제목: “🔧 HealthCheckGracePeriod 미설정으로 Task 반복 재시작”증상: 새로 배포한 NestJS Task가 시작 직후 ALB Health Check에서 실패 → ECS가 Task를 교체 → 새 Task도 실패를 반복. 로그에는 정상 부팅 메시지가 찍히는데 Task가 계속 교체됨
원인: NestJS 앱이 완전히 부팅되기 전(DB 연결 초기화, 모듈 로딩 등)에 ALB Health Check가 먼저 실패 판정. ECS가 unhealthy로 판단해 Task를 교체
해결:
# ECS Service에 HealthCheckGracePeriod 설정 (초 단위)# 앱이 완전히 부팅될 때까지 Health Check 실패를 무시하는 시간
aws ecs update-service \ --cluster my-cluster \ --service my-service \ --health-check-grace-period-seconds 60# → NestJS가 DB 연결 포함 완전 부팅에 30~45초 걸리면 60초 이상 설정
# 확인: Service 설정에서 healthCheckGracePeriodSeconds 값aws ecs describe-services \ --cluster my-cluster \ --services my-service \ --query 'services[0].healthCheckGracePeriodSeconds'# 예상 출력: 60ALB Health Check 자체 설정도 함께 조정:
ALB → Target Groups → Health checks- Healthy threshold: 2 (2회 연속 성공 시 healthy)- Unhealthy threshold: 3 (3회 연속 실패 시 unhealthy)- Interval: 30초- Timeout: 5초→ unhealthy 판정까지 최소 90초 소요 → NestJS 부팅 시간 커버 가능7. 체크리스트
섹션 제목: “7. 체크리스트”- EC2와 ECS의 차이를 한 문장으로 설명할 수 있다
- Fargate와 EC2 launch type의 차이를 설명할 수 있다
- Task Definition, Task, Service, Cluster의 관계를 설명할 수 있다
- 인터넷 → ALB → ECS로 트래픽이 흐르는 구조를 설명할 수 있다
- 팀 서비스의 배포 방식이 EC2인지 ECS인지 말할 수 있다
- Task가 STOPPED되었을 때 로그를 어디서 확인하는지 안다
8. 추가 학습 키워드
섹션 제목: “8. 추가 학습 키워드”EKS(Kubernetes), Fargate Spot, Auto Scaling, ALB(Application Load Balancer), Target Group, Blue-Green 배포, Rolling 배포
📚 추천 리소스
섹션 제목: “📚 추천 리소스”- 📖 Amazon ECS 공식 문서 - Fargate 시작하기 — Task Definition 생성부터 Service 배포까지 단계별 공식 가이드 (입문)
- 📖 AWS ECS vs EC2 Complete Comparison Guide — 비용·운영 부담·사용 사례 기준으로 두 서비스를 실무 관점에서 비교 (입문)
- 📖 ECS Fargate 실전 트러블슈팅 핸드북 — Private Subnet 네트워크 문제, 이미지 Pull 실패 등 Fargate 흔한 함정 정리 (중급)
- 📖 ECS 중단된 태스크 오류 해결 — Task Stopped Reason과 Exit Code별 원인 및 해결 방법 공식 문서 (중급)
- 📖 AWS Fargate 비용 최적화 전략 — Right-Sizing, Fargate Spot, Savings Plans 적용 방법 AWS 공식 블로그 (중급)
9. 내가 직접 확인해볼 것
섹션 제목: “9. 내가 직접 확인해볼 것”-
AWS 콘솔에서 ECS Cluster 목록 확인
경로: AWS 콘솔 → ECS → Clusters확인: 클러스터 이름, 실행 중인 서비스/태스크 수 -
팀 서비스의 Task Definition 열어보기 (이미지, CPU/메모리, 환경변수 확인)
경로: ECS → Task Definitions → <이름> → 최신 버전 클릭확인: CPU/Memory 할당, 컨테이너 이미지 주소, 환경변수, 로그 설정 -
ECS Service의 이벤트 탭에서 배포 이력 확인
경로: ECS → Clusters → <클러스터> → Services → <서비스> → Events 탭예상 출력:2024-01-15 10:30:01 service my-service has reached a steady state.2024-01-15 10:29:45 service my-service has started 1 tasks: task abc123def.2024-01-15 10:28:30 service my-service deregistered 1 targets in target group arn:aws:... -
배포 시 ECS에서 무슨 일이 일어나는지 흐름 파악
10. 5줄 요약
섹션 제목: “10. 5줄 요약”- EC2는 가상 서버(인프라), ECS는 Docker 컨테이너 오케스트레이션(관리 방식)이다 — 둘은 비교 대상이 아니라 함께 쓰이는 계층이다
- ECS는 Fargate(서버리스, 비용 효율), EC2 launch type(고성능·특수 요구), Managed Instances(새 옵션, 특수 워크로드) 세 가지 실행 모드가 있다
- Task Definition(스펙) → Task(실행 인스턴스) → Service(개수 유지·배포) → Cluster(논리 그룹) 4계층 구조이다
- 외부 트래픽은 ALB → Target Group → ECS Service → Task 순으로 흐르며, 배포는 신규 Task 헬스체크 통과 후 구버전 교체 순서로 진행된다
- 배포 실패 시 확인 순서: ECS Events 탭 → CloudWatch 로그(컨테이너 오류) → ALB Target Group 헬스체크 → Task 메모리/CPU 설정
프론트엔드 → 플랫폼 브릿지
섹션 제목: “프론트엔드 → 플랫폼 브릿지”Docker Compose를 써봤다면 ECS Task Definition과 비교하기
로컬 개발에서 docker-compose.yml을 사용해봤다면 ECS Task Definition이 매우 친숙하게 느껴질 것이다:
# docker-compose.yml (로컬 개발)version: "3"services: api: image: my-nest-app:latest ports: - "3000:3000" environment: - NODE_ENV=development - DATABASE_URL=postgresql://... depends_on: - postgres postgres: image: postgres:14 environment: POSTGRES_DB: mydb// ECS Task Definition (프로덕션){ "family": "my-nest-app", "cpu": "256", "memory": "512", "containerDefinitions": [{ "name": "api", "image": "123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-nest-app:latest", "portMappings": [{"containerPort": 3000}], "secrets": [ {"name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:...:prod/db-url"} ], "logConfiguration": {"logDriver": "awslogs", ...} }]}차이점 요약:
| docker-compose | ECS Task Definition |
|---|---|
image: | "image": (ECR URI) |
ports: | "portMappings": |
environment: | "environment": + "secrets": (Secrets Manager 참조) |
depends_on: | ECS Service + RDS (별도 리소스) |
| 로컬 볼륨 | S3, EFS (ECS에서 관리) |
| 로그: 터미널 출력 | CloudWatch Logs (awslogs 드라이버) |
핵심 차이: 환경변수 값을 직접 넣지 않고 Secrets Manager ARN으로 참조한다. 그리고 RDS는 compose의 postgres 서비스가 아니라 별도 AWS 리소스로 분리된다.
프론트에서 배포 결과를 확인하는 흐름
CI/CD로 코드를 push했을 때 서비스에 반영되는 과정:
git push → GitHub Actions → Docker build → ECR push → ECS Service Update Deployment 시작 → 새 Task 시작 (PROVISIONING → RUNNING) → ALB Health Check 통과 대기 → 신규 Task에 트래픽 전환 → 구버전 Task STOPPED
배포 확인 방법:ECS → Clusters → Services → <서비스> → Events 탭 "service reached a steady state" 메시지 = 배포 완료
로그 확인:CloudWatch → Log Groups → /ecs/<task-family> → 최신 로그 스트림실무 아키텍처 패턴 — ECS 프로덕션 구성
섹션 제목: “실무 아키텍처 패턴 — ECS 프로덕션 구성”[일반적인 프로덕션 ECS 아키텍처]
인터넷 └─ ALB (Public Subnet, HTTPS 443) └─ Target Group (Health Check: /health) └─ ECS Service (Private Subnet) ├─ Task (Fargate, 2개 이상 Multi-AZ) │ └─ NestJS 컨테이너 (Port 3000) └─ Auto Scaling Policy (CPU 60% 이상 시 Scale Out)
[주요 연결 서비스]Task → RDS (Private Subnet, SG 허용)Task → ElastiCache (Private Subnet, SG 허용)Task → S3 (VPC Endpoint 경유, NAT 없이)Task → CloudWatch (VPC Endpoint 경유)Task → Secrets Manager (VPC Endpoint 경유, 환경변수 주입)2025년 신기능 — ECS Managed Instances: re:Invent 2025에서 발표된 ECS의 새 컴퓨팅 옵션이다. Fargate처럼 서버 관리를 AWS에 맡기면서도, Fargate에서 지원하지 않는 GPU 워크로드, eBPF 기반 보안 에이전트, 특권 컨테이너(privileged container), 120GB 이상 메모리 작업에 사용할 수 있다. 2025년 신기능 — Fargate 태스크 종료 시 Stop Signal 지원: Fargate가 이제 컨테이너 이미지에 정의된
STOPSIGNAL지시를 그대로 전달한다. 기존에는 SIGTERM만 보냈지만, 이제 SIGQUIT, SIGINT 등 컨테이너별 커스텀 종료 신호를 보내 Graceful Shutdown을 더 정확하게 구현할 수 있다.