ECS on Fargate
Task 단위로 CPU와 memory를 지정하면 AWS가 실행 환경을 준비한다. EC2 인스턴스 선택, OS patch, cluster packing을 직접 하지 않는다.
팀이 작고, workload가 변동적이며, 서버 운영보다 배포 속도와 단순성이 중요할 때분류: Layer 3 - AWS 인프라 & 보안
EC2(Elastic Compute Cloud)는 AWS에서 빌려 쓰는 가상 서버다. ECS(Elastic Container Service)는 컨테이너를 원하는 개수와 상태로 유지하는 AWS의 컨테이너 오케스트레이션 서비스다.
그래서 “ECS vs EC2”는 엄밀히 말하면 같은 층의 비교가 아니다. EC2는 compute 자원이고, ECS는 그 compute 위에서 컨테이너를 배치하고 교체하고 배포하는 orchestration 계층이다. ECS는 EC2 인스턴스 위에서도 실행할 수 있고, Fargate처럼 서버 관리를 AWS에 맡기는 방식으로도 실행할 수 있다.
처음 읽을 때는 아래 문장을 고정한다.
EC2 직접 운영: "서버를 만들고, 그 서버에 직접 컨테이너를 띄운다."ECS + EC2: "서버 풀은 내가 관리하고, 컨테이너 배치와 유지는 ECS가 맡는다."ECS + Fargate:"서버 풀 관리까지 AWS에 맡기고, Task 단위만 선언한다."서비스가 장애 없이 배포되고, 트래픽에 맞춰 늘어나고, 죽은 프로세스가 자동으로 복구되는지는 애플리케이션 코드만으로 결정되지 않는다. 어디에 띄울지, 몇 개를 유지할지, 새 버전을 어떻게 교체할지, 로드밸런서가 어떤 대상에 요청을 보낼지를 누군가 계속 맞춰야 한다.
EC2만 직접 쓰면 이 책임이 대부분 운영자에게 남는다. ECS를 쓰면 “이 이미지의 Task를 3개 유지하라”는 원하는 상태(desired state)를 선언하고, ECS scheduler가 실제 상태(actual state)를 맞춘다. 이 차이를 모르면 배포 실패를 보고도 “서버가 죽었나”, “컨테이너가 죽었나”, “스케줄러가 새 Task를 못 띄우나”, “ALB health check가 막나”를 구분하기 어렵다.
이 문서의 목표는 ECS 설정을 모두 외우는 것이 아니다. EC2, ECS, Fargate, ALB, ENI, ECR, Task Definition, Service, Cluster가 어떤 층에서 만나는지를 그릴 수 있게 만드는 것이다.
ECS는 Docker 컨테이너를 EC2에 직접 띄우던 방식의 운영 한계에서 등장했다. 작은 서비스 하나는 EC2에 SSH로 접속해서 docker run을 실행해도 된다. 하지만 컨테이너가 여러 개, 인스턴스가 여러 대, 배포가 하루 여러 번으로 늘어나면 다음 문제가 반복된다.
| 수동 운영에서 깨지는 지점 | 실제로 생기는 문제 | ECS가 제공하는 해법 |
|---|---|---|
| desired state 부재 | Task 3개가 정상인지 사람이 콘솔이나 스크립트로 확인한다 | Service가 desired count를 유지한다 |
| 노드 장애 복구 | EC2 한 대가 죽으면 그 위 컨테이너를 어디에 다시 띄울지 사람이 정한다 | scheduler가 새 Task를 가능한 capacity에 배치한다 |
| 배포 순서 | 새 컨테이너를 먼저 띄울지, 기존 컨테이너를 먼저 내릴지 매번 절차화해야 한다 | deployment controller가 health check를 보며 rolling update를 수행한다 |
| 서비스 디스커버리와 트래픽 | 어떤 IP:port가 살아 있는지 ALB나 프록시에 계속 반영해야 한다 | Service가 Target Group에 Task를 등록하고 해제한다 |
| bin packing | EC2별 CPU/메모리 여유를 사람이 추정해 컨테이너를 배치한다 | ECS가 Task 요구 리소스와 capacity를 비교해 배치한다 |
핵심 전환은 명령형에서 선언형으로 바뀐다는 점이다.
명령형: "이 EC2에 접속해서 docker run을 실행한다."선언형: "이 Task Definition을 Service desiredCount=3으로 유지한다."ECS의 lineage는 여기서 나온다. 컨테이너를 띄우는 명령 하나가 아니라, 원하는 상태와 실제 상태를 계속 비교해 복구하는 관리 루프가 필요해졌고, ECS는 그 루프를 AWS 서비스로 제공한다. Fargate는 여기서 한 단계 더 나아가 “컨테이너를 올릴 EC2 인스턴스 풀” 관리까지 AWS에 넘긴 실행 방식이다.
아래 용어는 뒤에서 계속 나온다. 외부 prerequisite로 외울 필요는 없고, 여기서 첫 정의를 잡고 본문 예시로 반복해서 확인하면 된다.
| 용어 | 첫 정의 | 처음 잡아야 할 경계 |
|---|---|---|
| EC2 | Elastic Compute Cloud. 사용자가 빌려 쓰는 VM 서버다. | OS 패치, 인스턴스 타입, 디스크, 에이전트, capacity를 직접 관리한다. |
| ECS | Elastic Container Service. 컨테이너 Task를 배치하고 유지하는 orchestration 서비스다. | 서버 자체가 아니라 서버 또는 Fargate capacity 위에서 컨테이너 lifecycle을 관리한다. |
| ECR | Elastic Container Registry. Docker image를 저장하는 AWS registry다. | ECS는 image를 직접 만들지 않는다. Task 시작 시 ECR 같은 registry에서 image를 pull한다. |
| ALB | Application Load Balancer. HTTP/HTTPS 요청을 target으로 분산하는 L7 로드밸런서다. | ECS Service 앞의 public entrypoint 역할을 맡고, Task는 보통 private subnet에 둔다. |
| ENI | Elastic Network Interface. VPC 리소스에 붙는 네트워크 카드다. | Fargate awsvpc 모드에서는 Task마다 ENI와 private IP가 붙는다. |
| Task Definition | 컨테이너 실행 스펙이다. image, CPU, memory, port, env, log 설정을 담는다. | 실행 중인 인스턴스가 아니라 blueprint다. revision이 쌓인다. |
| Task | Task Definition을 실제로 실행한 단위다. 하나 이상의 컨테이너를 담을 수 있다. | 죽고 새로 생기는 runtime instance다. 영구 서버 이름처럼 보면 안 된다. |
| Service | 같은 Task를 원하는 개수만큼 유지하고 배포하는 단위다. | 웹/API처럼 계속 떠 있어야 하는 workload에 맞다. 1회성 batch는 standalone Task도 가능하다. |
| Cluster | Service와 Task가 속한 논리 그룹이다. | capacity가 어디서 오는지는 Fargate, EC2, capacity provider 설정으로 결정된다. |
| desired state | 운영자가 선언한 목표 상태다. 예: Task 3개 유지. | ”지금 실제로 3개 떠 있다”가 아니라 시스템이 맞춰야 할 목표다. |
| scheduler | desired state를 만족하도록 Task를 배치하고 교체하는 ECS 내부 주체다. | 애플리케이션 버그를 고쳐 주지는 않는다. 실행 가능한 capacity와 상태를 맞춘다. |
| health check | Task가 트래픽을 받아도 되는지 판단하는 검사다. | 컨테이너가 RUNNING이어도 ALB health check가 실패하면 traffic target이 아니다. |
| launch type | Task가 어떤 infrastructure에서 실행되는지 가리키는 선택이다. | FARGATE와 EC2가 대표적이고, capacity provider로 더 유연하게 섞을 수 있다. |
| capacity provider | Service가 사용할 capacity 공급원을 정의하고 비중을 조절하는 방식이다. | Fargate, Fargate Spot, EC2 Auto Scaling Group 같은 공급원을 조합할 때 쓴다. |
| Fargate | 서버와 cluster capacity 관리를 AWS에 맡기는 serverless container 실행 방식이다. | ECS의 대체물이 아니라 ECS Task를 실행하는 capacity option이다. |
ECS를 공부할 때는 화면 메뉴보다 lifecycle을 먼저 잡는다.
Docker image를 ECR 같은 registry에 저장한다. ECR은 Elastic Container Registry의 약자다.
image URI, CPU, memory, port, environment, secret, log 설정을 revision으로 고정한다.
이 Task Definition revision을 몇 개 유지할지 선언한다. 예: desiredCount=3.
ECS scheduler가 Fargate 또는 EC2 capacity 중 조건을 만족하는 곳에 Task를 배치한다.
컨테이너나 ALB health check가 실패하면 Service가 새 Task를 시작해 desired state를 복원한다.
Task Definition은 “서버 한 대”가 아니라 “컨테이너를 어떻게 실행할지”를 담은 스펙이다. 아래 예시는 Fargate에서 API 컨테이너 하나를 실행하는 최소 감각을 보여준다.
{ "family": "my-api", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole", "taskRoleArn": "arn:aws:iam::123456789012:role/myApiTaskRole", "containerDefinitions": [ { "name": "api", "image": "123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-api:2026-07-08", "portMappings": [{ "containerPort": 3000, "protocol": "tcp" }], "environment": [{ "name": "NODE_ENV", "value": "production" }], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/my-api", "awslogs-region": "ap-northeast-2", "awslogs-stream-prefix": "ecs" } } } ]}여기서 IAM 문서와 연결되는 지점이 중요하다.
executionRoleArn: ECS 플랫폼이 Task를 시작할 때 쓰는 Role이다. ECR image pull, CloudWatch Logs 전송, secret 주입에 필요하다.taskRoleArn: 컨테이너 안의 애플리케이션 코드가 AWS API를 호출할 때 쓰는 Role이다. S3 업로드, SQS 전송, Secrets Manager 조회 권한은 여기에 붙는다.반례를 하나 잡자. 애플리케이션이 S3에 파일을 올려야 하는데 executionRoleArn에만 s3:PutObject를 붙이면 Task 시작과 로그 전송은 정상일 수 있다. 하지만 코드가 S3를 호출하는 순간 AccessDenied가 난다. 이때 필요한 것은 “ECS 권한을 더 넓게”가 아니라 Task Role과 Execution Role을 구분해 올바른 Role에 권한을 붙이는 것이다.
ECS Service의 핵심은 desired state reconciliation이다. reconciliation은 “목표 상태와 실제 상태를 비교해 다시 맞춘다”는 뜻이다.
초기 상태:Service desiredCount = 3실행 중 Task = A, B, C
장애:Task B가 exit code 1로 종료
ECS 관찰:desired = 3actual = 2
ECS 행동:새 Task D 시작ALB health check 통과 후 target 등록
복원:실행 중 Task = A, C, DdesiredCount = 3 충족이 예시는 단순하지만 운영 사고의 기준점이다. Task가 죽었을 때 “ECS가 새 Task를 띄우고 있는가”와 “새 Task가 떠도 health check를 통과하지 못하는가”는 다른 문제다. 전자는 scheduler와 capacity 문제이고, 후자는 애플리케이션 부팅, 포트, health endpoint, 네트워크 문제일 가능성이 크다.
ECS의 실행 위치를 고르는 질문은 “ECS냐 EC2냐”가 아니라 ECS가 사용할 capacity를 누가 관리할 것인가다.
Task 단위로 CPU와 memory를 지정하면 AWS가 실행 환경을 준비한다. EC2 인스턴스 선택, OS patch, cluster packing을 직접 하지 않는다.
팀이 작고, workload가 변동적이며, 서버 운영보다 배포 속도와 단순성이 중요할 때내가 관리하는 EC2 인스턴스 풀 위에 ECS가 Task를 배치한다. 인스턴스 타입, AMI, agent, patch, Auto Scaling Group을 관리한다.
Task 수가 많고 예측 가능하거나 GPU, privileged container, custom AMI 같은 요구가 있을 때ECS 없이 EC2에 직접 process나 container를 실행한다. orchestration, 배포, 복구 자동화를 직접 만든다.
아주 단순한 실험, 특수한 OS 제어, 또는 orchestration 도입 전 단계AWS 공식 문서는 ECS capacity를 Fargate, EC2 instances, ECS Managed Instances 같은 infrastructure option으로 설명한다. 다만 첫 회독에서는 Fargate와 EC2 launch type의 차이만 정확히 잡아도 충분하다. Managed Instances는 “서버 관리를 어느 정도 AWS가 대신하되 EC2 계층의 제어를 일부 유지하려는 capacity option”으로 나중에 읽으면 된다.
Fargate는 각 Task에 전용 실행 환경을 제공한다. AWS 문서 기준으로 Fargate Task는 다른 Task와 underlying kernel, CPU, memory, ENI를 공유하지 않는 격리 경계를 가진다. 같은 Task 안의 여러 컨테이너는 localhost와 Task 리소스를 공유할 수 있지만, Task 바깥과는 분리된다.
이 모델의 학습 포인트는 세 가지다.
awsvpc 모드에서 Task마다 ENI와 private IP가 붙으므로 Security Group을 Task 단위로 적용할 수 있다.Fargate가 “무조건 더 안전하고 빠르다”는 뜻은 아니다. 서버 관리를 덜 하는 대신, 실행 환경을 세밀하게 만지는 권한도 줄어든다.
Fargate는 아무 CPU/memory 조합이나 고를 수 없다. Task Definition에 허용된 조합만 넣을 수 있고, 조합이 틀리면 등록 시점에 실패한다. 아래 표는 학습용 요약이다. 실제 설계 전에는 공식 문서의 최신 표와 리전별 quota를 확인한다.
| CPU | memory 조합 감각 | 처음 읽을 때의 해석 |
|---|---|---|
| 0.25 vCPU | 512 MiB, 1 GB, 2 GB | 작은 API, worker, sidecar 없는 입문용 Task |
| 0.5 vCPU | 1~4 GB | 낮은 트래픽의 API나 가벼운 batch |
| 1 vCPU | 2~8 GB | 일반적인 소규모 서비스 시작점 |
| 2 vCPU | 4~16 GB | 동시 요청이 늘거나 런타임 memory가 큰 서비스 |
| 4 vCPU | 8~30 GB | CPU와 memory를 모두 더 쓰는 API/worker |
| 8 vCPU | 16~60 GB | 큰 batch, 무거운 런타임, 높은 동시 처리 |
| 16 vCPU | 32~120 GB | 메모리 집약 workload의 상위 조합 |
| 32 vCPU | 60 GB, 120 GB, 244 GB | 매우 큰 workload. quota, 비용, cold start를 따로 검토 |
이 표에서 얻어야 할 결론은 “큰 조합이 있으니 다 Fargate로 가능하다”가 아니다. 조합이 커질수록 Task 하나의 비용과 시작 시간이 커지고, 장애가 났을 때 한 Task가 잃는 처리량도 커진다. 반대로 너무 작은 Task를 많이 띄우면 ENI/IP, log stream, DB connection, downstream 호출 수가 먼저 문제가 될 수 있다.
launch type은 Task Definition이나 Service가 사용할 실행 환경을 직접 고르는 방식이다. 대표 값은 FARGATE와 EC2다.
capacity provider는 이 선택을 한 단계 추상화한다. 예를 들어 FARGATE와 FARGATE_SPOT을 1:3 비중으로 섞거나, EC2 Auto Scaling Group capacity와 Fargate capacity를 Service 전략 안에서 조합할 수 있다.
{ "capacityProviderStrategy": [ { "capacityProvider": "FARGATE", "weight": 1, "base": 1 }, { "capacityProvider": "FARGATE_SPOT", "weight": 3 } ]}이 예시는 “최소 1개 Task는 On-Demand Fargate에 두고, 나머지는 가능한 한 Spot으로 둔다”는 의도다. 단, Spot은 중단될 수 있다. 로그 ingestion, 결제 처리, 긴 transaction처럼 중단되면 데이터 손실이나 중복 처리가 어려운 workload에는 비용 절감률보다 중단 처리 설계가 먼저다.
Fargate는 awsvpc 네트워크 모드만 지원한다. awsvpc에서는 Task마다 ENI(Elastic Network Interface)와 private IP를 받는다. 이 말은 Task가 단순 process가 아니라 VPC 안의 네트워크 주체가 된다는 뜻이다.
외부 HTTP 요청은 보통 ALB(Application Load Balancer)를 거쳐 ECS Task로 들어간다.
sequenceDiagram participant User as 인터넷 사용자 participant ALB as ALB participant TG as Target Group participant Service as ECS Service participant Task as Task ENI/IP User->>ALB: HTTPS 요청 ALB->>TG: healthy target 선택 TG->>Service: 등록된 Task IP 확인 Service->>Task: containerPort로 전달 Task-->>User: 응답 반환
ALB는 Public Subnet의 진입점이고, Task는 보통 Private Subnet에서 Target Group을 통해서만 접근된다.
Fargate Task를 Target Group에 등록할 때는 보통 target type을 ip로 둔다. Task가 EC2 instance ID가 아니라 ENI에 붙은 private IP로 식별되기 때문이다. 그래서 ALB 문제를 볼 때는 “Task가 RUNNING인가”와 “Target Group에서 healthy인가”를 분리해야 한다.
Task가 늘면 CPU와 memory만 쓰는 것이 아니다. awsvpc에서는 Task마다 private IP를 하나씩 소비한다.
Private Subnet A: /27표기상 주소 수: 32AWS 예약 후 대략 사용 가능: 27
현재 Task: 20개rolling update 중 신규 Task: 20개순간 필요 IP: 40개
결과:Task를 모두 띄우기 전에 Subnet IP가 부족해질 수 있다.반대로 /24 subnet은 AWS 예약분을 제외해도 약 251개를 사용할 수 있다. 같은 20 -> 20 rolling update를 훨씬 여유 있게 흡수한다. VPC 문서에서 배운 /24와 /27의 차이가 ECS에서는 “배포 중 Task가 안 뜨는가”로 바로 드러난다.
실패 신호는 다음처럼 나타난다.
| 증상 | 의미 | 먼저 볼 개념 |
|---|---|---|
| Service desiredCount까지 Task가 올라가지 않는다 | capacity 또는 IP가 부족할 수 있다 | Subnet free IP, Service events |
이벤트에 RESOURCE:ENI, insufficient free addresses가 보인다 | Task에 붙일 ENI/IP를 확보하지 못했다 | VPC CIDR, Subnet 크기 |
| 특정 AZ에만 Task가 몰린다 | 한쪽 Subnet capacity가 부족하거나 배치 제약이 다르다 | Multi-AZ subnet, capacity provider |
전형적인 웹 서비스 구성을 한 번에 그리면 ECS가 다른 L3 토픽과 어떻게 연결되는지 보인다.
인터넷 -> ALB (Public Subnet, HTTPS 443) -> Target Group (target type = ip, health check = /health) -> ECS Service (Private App Subnet A/B) -> Task 2개 이상 (Fargate, awsvpc, SG = sg-api)
Task에서 나가는 연결: -> RDS (DB Subnet, SG inbound source = sg-api, port 5432) -> S3 (Gateway Endpoint 또는 NAT 경유) -> ECR / CloudWatch Logs / Secrets Manager (Interface Endpoint 또는 NAT 경유) -> 외부 결제 API (NAT Gateway 경유)이 구성에서 public한 것은 ALB뿐이다. ECS Task와 RDS는 private subnet에 둔다. 하지만 private이라는 말이 “아무 데도 나갈 수 없음”은 아니다. Task가 ECR에서 image를 받고, CloudWatch로 logs를 보내고, 외부 API를 호출하려면 NAT Gateway나 VPC Endpoint 같은 outbound 경로가 필요하다.
작은 수치 예시를 붙이면 설계 감각이 더 선명해진다.
desiredCount = 6rolling update maximumPercent = 200배포 중 최대 Task = 12
Private Subnet A/B를 /27로 만들면:AZ별 사용 가능 IP가 약 27개라 다른 리소스와 같이 쓰는 순간 빠듯하다.
Private Subnet A/B를 /24로 만들면:AZ별 사용 가능 IP가 약 251개라 Task, ENI, Endpoint 확장을 더 여유 있게 흡수한다.따라서 ECS Service의 최대 Task 수는 ECS 화면 안에서만 정하지 않는다. VPC CIDR, Subnet 크기, RDS 연결 수, NAT/Endpoint 비용까지 같이 계산한다.
ECS Service rolling update는 새 버전 Task를 띄우고, health check를 통과한 뒤, 기존 Task를 내리는 흐름이다.
image tag, environment, resource 설정 변경이 새 revision으로 고정된다.
Service가 새 revision Task를 시작한다. maximumPercent가 동시에 실행 가능한 Task 상한을 정한다.
신규 Task가 ALB health check를 통과하기 전까지 기존 Task가 계속 traffic을 받는다.
신규 Task가 healthy가 되면 ALB가 새 Task IP에도 요청을 분산한다.
minimumHealthyPercent를 지키면서 기존 target을 deregister하고 연결을 drain한 뒤 Task를 종료한다.
두 설정이 배포 중 capacity 요구량을 크게 바꾼다.
| 설정 | 의미 | 예시 |
|---|---|---|
maximumPercent | 배포 중 desiredCount 대비 최대 몇 %까지 Task를 동시에 띄울 수 있는가 | desired=10, maximum=200이면 최대 20개까지 공존 |
minimumHealthyPercent | 배포 중 최소 몇 %의 healthy Task를 유지해야 하는가 | desired=10, minimum=100이면 최소 10개는 healthy여야 함 |
반례를 보자. desiredCount가 10이고 minimumHealthyPercent=100, maximumPercent=100이면 새 Task를 띄울 여유도 없고 기존 Task를 먼저 내릴 수도 없다. 설정만 보면 안정적으로 보이지만, 실제로는 rolling update가 진행될 수 없다. 보통은 새 Task가 먼저 올라올 여유를 주거나, capacity provider와 subnet IP 여유를 함께 확보해야 한다.
ALB 502가 배포 직후 낮은 비율로만 보이면 평균 에러율에서는 묻힌다. 하지만 원리는 단순하다. Task 종료, ALB target deregistration, 애플리케이션 shutdown 시간이 서로 맞지 않으면 긴 요청만 잘린다.
ALB deregistration delay >= 애플리케이션의 긴 요청 p99 처리 시간ECS stopTimeout >= 애플리케이션이 SIGTERM 후 정리하는 시간애플리케이션 shutdown hook은 새 요청을 거절하고 진행 중 요청을 마무리한다예를 들어 파일 업로드나 외부 API 대기가 p99 25초인데 deregistration delay가 10초라면 짧은 요청은 정상이고 긴 요청만 502가 된다. 이 패턴은 “서비스가 대부분 정상”처럼 보여서 늦게 발견된다. 본문에서 기억할 것은 명령어가 아니라 배포와 scale-in은 정상 동작 중인 연결을 끊을 수 있는 사건이라는 점이다.
Task가 RUNNING으로 바뀌었다고 해서 애플리케이션이 traffic을 받을 준비를 마친 것은 아니다. NestJS 같은 서버가 DB 연결, migration check, module loading, cache warmup을 수행하는 동안 /health가 아직 200을 반환하지 못할 수 있다. 이때 ALB health check가 너무 빨리 실패를 누적하면 ECS가 새 Task를 비정상으로 보고 교체한다.
애플리케이션 완전 부팅 시간: 35~45초ALB health check interval: 30초unhealthy threshold: 2healthCheckGracePeriodSeconds: 0
결과:Task가 정상 부팅 중인데도 첫 health check 실패가 누적되어 교체될 수 있다.학습 포인트는 “grace period를 무조건 길게”가 아니다. 부팅 시간이 40초라면 grace period를 60초처럼 여유 있게 시작하고, health endpoint가 DB 같은 외부 의존성까지 검사해야 하는지 분리해서 본다. /live는 process 생존, /ready는 traffic 가능 상태처럼 역할을 나누면 배포 중 불필요한 교체를 줄일 수 있다.
ECS Service는 Application Auto Scaling으로 desiredCount를 늘리거나 줄일 수 있다. 가장 흔한 방식은 target tracking이다.
목표:ECSServiceAverageCPUUtilization = 60%
현재:Task 4개, 평균 CPU 85%
Scaling 판단:Task 수를 늘려 평균 CPU가 목표값 근처로 내려가게 한다.실무에서 시작점으로 자주 쓰는 감각은 CPU 60%, memory 70% 부근이다. 이것은 정답이 아니라 초기 가설이다. latency가 CPU보다 먼저 나빠지는 서비스라면 ALB request count per target, queue depth, p95 latency 같은 서비스 메트릭을 같이 봐야 한다.
최소/최대 Task 수는 단순히 “트래픽이 많다”로 정하면 안 된다.
| 값 | 잡는 기준 |
|---|---|
| 최소 Task 수 | Multi-AZ를 위해 보통 2개 이상에서 시작한다. cold start 동안 버틸 baseline도 포함한다. |
| 최대 Task 수 | Subnet IP 수, Fargate service quota, RDS max_connections, downstream rate limit을 같이 본다. |
| scale-out cooldown | 급증 트래픽에 너무 늦지 않게 짧게 시작하되, cold start와 image pull 시간을 감안한다. |
| scale-in cooldown | 플래핑을 막기 위해 더 길게 둔다. scale-in은 연결 drain과 graceful shutdown을 동반한다. |
RDS 문서에서 배운 connection pool 계산은 ECS scaling 상한과 직접 연결된다.
RDS max_connections >= ECS Task 수 x 애플리케이션 pool max
예시:RDS max_connections = 90pool max = 10
안전한 Task 수 상한:90 / 10 = 9개
Service maxCapacity를 20으로 두면:트래픽 spike 때 Task 20개 x pool 10 = 200 연결 시도=> DB는 CPU보다 먼저 "too many connections"로 실패할 수 있다.이 예시는 “ECS autoscaling은 애플리케이션만 보는 기능이 아니다”라는 점을 보여준다. Task를 늘리면 IP, DB 연결, cache connection, 외부 API 호출량도 같이 늘어난다. 그래서 autoscaling 최대값은 compute capacity가 아니라 가장 먼저 포화되는 downstream 자원을 기준으로 제한해야 한다.
RDS Proxy를 쓰면 계산이 바뀐다. Proxy가 모든 문제를 없애지는 않지만, 애플리케이션 Task 수와 실제 DB backend connection 수를 분리할 수 있다.
Proxy 없음:Task 40개 x pool max 5 = DB 연결 200개
Proxy 있음:Task 40개 -> Proxy client 연결 200개Proxy -> RDS backend 연결 40개 수준으로 multiplex 가능단, transaction을 오래 잡거나 session variable, temporary table 같은 pinning 조건이 많으면 multiplexing 효율이 떨어진다. 그래서 ECS scaling 설계에서 RDS Proxy는 “연결 수를 줄이는 도구”이지, “무제한 scale-out 허가증”이 아니다.
Fargate는 새 Task를 시작할 때 실행 환경 준비, image pull, 컨테이너 부팅, health check 통과 과정을 거친다. workload와 image 크기에 따라 시간이 달라지며, 30~60초 수준을 측정 시작값으로 잡는 서비스도 있다. SOCI 같은 image lazy loading 옵션으로 image pull 시간을 줄일 수 있지만, “새 Task가 즉시 traffic을 받는다”는 가정은 위험하다.
반례를 보자.
평상시 필요 Task: 4개갑작스러운 피크에서 필요한 Task: 12개새 Task가 health check 통과까지 걸리는 시간: 45초
Auto Scaling이 바로 반응해도:첫 45초는 기존 4개 Task가 피크 대부분을 감당한다.이때 해결책은 무조건 maxCapacity를 키우는 것이 아니다. 예측 가능한 피크가 있으면 baseline Task를 피크의 70~80% 수준으로 미리 올려 두거나, scheduled scaling을 쓰거나, queue를 앞에 두어 burst를 흡수한다. 비용을 아끼려고 baseline을 지나치게 낮추면 scale-out이 시작되기 전에 ALB 5xx나 latency spike가 먼저 온다.
Fargate와 EC2 launch type의 비용 차이는 “어느 쪽이 싸다”가 아니라 과금 단위가 다르다는 데서 시작한다.
| 항목 | Fargate | ECS on EC2 |
|---|---|---|
| 과금 단위 | Task가 요청한 vCPU와 memory 사용 시간 | EC2 인스턴스, EBS, 데이터 전송, 운영 비용 |
| 빈 capacity | Task가 없으면 compute 비용도 줄어든다 | 인스턴스가 켜져 있으면 빈 capacity도 비용이다 |
| bin packing | AWS가 처리하지만 task 단위 과금 프리미엄이 있다 | 여러 Task를 한 인스턴스에 채워 비용을 낮출 수 있다 |
| 운영 비용 | OS patch, agent, AMI 관리 부담이 작다 | 인스턴스 보안 패치, scaling, draining을 직접 다룬다 |
실제 설계에서는 반드시 현재 리전의 공식 요금표로 다시 계산해야 한다. 아래 숫자는 계산 방법을 익히기 위한 학습용 기준점이며, 리전, CPU 아키텍처, OS, pricing 갱신 시점에 따라 달라질 수 있다.
Task 1개, 0.25 vCPU / 0.5 GB, 24시간 x 30일
| 항목 | 계산 | 월 비용 감각 |
|---|---|---|
| Fargate vCPU | 0.25 x $0.04048 x 720h | 약 $7.29 |
| Fargate memory | 0.5 x $0.004445 x 720h | 약 $1.60 |
| Fargate 합계 | vCPU + memory | 약 $8.89 |
| t3.small On-Demand | $0.0208/h x 720h | 약 $14.98 |
이 예시만 보면 Fargate가 싸 보인다. 하지만 Task 하나가 아니라 Task 10개를 계속 띄우면 그림이 바뀐다.
Task 10개, 각 0.5 vCPU / 1 GB
| 방식 | 월 비용 감각 | 해석 |
|---|---|---|
| Fargate x 10 Task | 약 $177 | Task마다 vCPU/memory 과금이 붙는다 |
| t3.large x 2대 | 약 $120 | 인스턴스에 여러 Task를 집약할 수 있다. 대신 운영 비용이 붙는다 |
결론은 “10개부터 EC2”처럼 외우는 것이 아니다. 팀이 인스턴스 patch, capacity planning, draining, 보안 기준을 안정적으로 운영할 수 있는지까지 합쳐서 본다. 작은 팀의 변동성 큰 서비스는 Fargate 비용이 운영 단순성을 살 수 있고, 큰 팀의 예측 가능한 workload는 EC2 launch type의 bin packing이 유리해질 수 있다.
비용 최적화에서 가장 흔한 실수는 평균 CPU만 보고 Task size를 줄이는 것이다. 평균 CPU가 20%라도 배포 직후 warmup, 매시 정각 batch, GC, image processing에서 peak가 90%까지 튈 수 있다. 메모리는 CPU보다 더 보수적으로 본다. memory limit을 넘으면 throttling이 아니라 OOMKilled로 바로 종료될 수 있기 때문이다.
| 관찰 | 성급한 결론 | 더 나은 판단 |
|---|---|---|
| 평균 CPU 15% | CPU를 절반으로 줄인다 | p95/p99 CPU와 burst 시간대를 먼저 본다 |
| 평균 memory 45% | memory를 절반으로 줄인다 | peak memory와 GC, image 처리, model warmup을 본다 |
| Task 1개 비용이 낮다 | Task 수를 계속 늘린다 | ENI/IP, RDS connection, log volume, downstream rate limit을 같이 본다 |
| Fargate가 EC2보다 비싸다 | EC2 launch type으로 옮긴다 | 운영 인력 비용, patch 책임, incident 대응 시간을 포함한다 |
right-sizing의 좋은 순서는 관측 -> 가설 -> 작은 조정 -> 배포 후 비교다. 운영 중 Task CPU/memory를 한 번에 크게 줄이면 비용은 줄 수 있지만, autoscaling이 더 자주 발생하고 cold start가 사용자 latency로 노출될 수 있다.
Fargate Spot은 spare capacity를 할인받는 대신 중단을 받아들인다. “중단 가능”은 단순히 stateless라는 말보다 더 엄격하다.
| workload | Spot 적합성 | 이유 |
|---|---|---|
| 이미지 thumbnail 재생성 batch | 높음 | 실패한 작업을 queue에서 재시도하기 쉽다 |
| 개발/스테이징 API | 중간 | 비용 절감은 크지만 중단 시 팀 workflow 영향을 본다 |
| 결제 callback 처리 | 낮음 | 중복 처리와 손실 방지 설계가 먼저다 |
| 로그 ingestion writer | 낮음 | 중단 시 데이터 유실 또는 backlog 폭증이 생길 수 있다 |
Compute Savings Plans는 예측 가능한 사용량에 약정을 거는 방식이다. 먼저 right-sizing과 baseline 분석을 한 뒤, 계속 켜져 있는 Fargate나 EC2 사용량에 적용하는 순서가 낫다.
Public IPv4 비용도 작은 서비스에서는 눈에 띌 수 있다. Task마다 public IP를 붙이는 구조보다 ALB만 public subnet에 두고 Task는 private subnet에 두면 노출면과 비용을 함께 줄일 수 있다. 다만 private subnet Task가 외부로 나가야 한다면 NAT Gateway나 VPC Endpoint 비용이 생기므로, “public IP 제거 = 항상 비용 절감”으로 단정하지 않는다.
Fargate는 단순하지만 모든 workload에 맞지는 않는다. AWS 공식 CPU/memory 조합표 기준으로 Fargate는 정해진 조합 안에서만 Task size를 고를 수 있다. 학습용 스냅샷으로 보면 Linux Fargate는 작은 쪽으로 0.25 vCPU/512 MiB부터, 큰 쪽으로 32 vCPU/244 GB 조합까지 제공된다. 숫자는 서비스 업데이트로 바뀔 수 있으므로 설계 전에는 공식 표를 다시 확인한다.
처음 공부할 때 중요한 것은 최대 숫자 자체보다 제어권의 경계다.
| 요구 | Fargate에서의 경계 | EC2 launch type이 필요한 신호 |
|---|---|---|
| GPU | 일반 ECS Fargate workload에서 GPU를 직접 고르지 않는다 | ML inference, CUDA, video processing |
| privileged container | Fargate는 privileged access를 지원하지 않는다 | eBPF agent, Docker-in-Docker, host namespace 접근 |
| custom AMI/host tuning | underlying host에 접근하지 않는다 | 커널 모듈, 특수 보안 에이전트, OS-level tuning |
| 매우 큰 memory/CPU | 정해진 Fargate CPU/memory 조합 안에서만 선택한다 | 조합을 넘거나 특정 instance family가 필요 |
| predictable high density | Task 단위 과금이 커질 수 있다 | 많은 Task를 EC2에 packed 배치해 비용을 낮출 수 있음 |
또 하나의 경계는 debugging 방식이다. EC2에 직접 들어가던 습관으로 Fargate host에 SSH 접속할 수는 없다. 컨테이너 안에서 짧게 확인해야 할 때는 ECS Exec을 설정해 shell을 열 수 있지만, 이것도 IAM 권한, SSM 경로, audit log를 전제로 한 진단 도구다. 장기적으로는 “들어가서 고친다”보다 로그, metric, health endpoint, config validation으로 문제를 밖에서 관측 가능하게 만드는 것이 맞다.
임베딩 추론 서비스가 큰 모델을 메모리에 올리고 batch size를 키우면 Task당 working set이 빠르게 늘어난다. 처음에는 Fargate의 더 큰 CPU/memory 조합으로 올리면 해결될 것처럼 보인다. 하지만 모델 warmup, 동시 요청 queue, GC peak가 겹치면 OOMKilled가 반복될 수 있다.
선택지는 둘이다.
이 반례는 “Fargate가 나쁘다”가 아니라 serverless container의 단순성이 host-level 제어권을 줄인 대가라는 점을 보여준다.
초기에 Fargate launch type으로 Service를 만들고 운영하다가 나중에 EC2 launch type으로 바꾸려 하면, 일부 속성은 직접 flip하듯 바뀌지 않는다. 새 capacity provider, 새 Service, Target Group 연결, DNS 또는 traffic cutover 계획이 필요할 수 있다.
처음부터 capacity provider 전략으로 설계하면 전환 비용이 줄어든다. 특히 장기적으로 Fargate와 EC2를 섞을 가능성이 있으면 “launch type 하나를 고정”하기보다 capacity provider를 학습 모델의 기본값으로 보는 편이 낫다.
| 질문 | EC2 직접 운영 | ECS on EC2 | ECS on Fargate |
|---|---|---|---|
| 컨테이너 자동 복구 | 직접 스크립트/프로세스 매니저 구성 | ECS Service가 담당 | ECS Service가 담당 |
| 서버 patch와 AMI | 직접 | 직접 | AWS에 대부분 위임 |
| bin packing | 직접 | ECS가 인스턴스 pool 안에서 수행 | AWS가 task 단위로 처리 |
| 세밀한 host 제어 | 가장 높음 | 높음 | 낮음 |
| 운영 난이도 | 높음 | 중간~높음 | 낮음 |
| 비용 최적화 여지 | 높지만 직접 구현 | 높음 | workload가 작거나 변동적일 때 유리 |
첫 서비스라면 Fargate로 시작해 개념과 배포 흐름을 익히는 것이 단순하다. 하지만 높은 사용률로 24시간 많은 Task를 돌리고, 팀이 인스턴스 운영 역량을 갖췄으며, 특수 hardware나 host 제어가 필요하다면 ECS on EC2가 더 합리적일 수 있다.
ECS와 EKS는 둘 다 컨테이너 orchestration 계층이다. EC2와 ECS의 관계보다 이쪽이 더 진짜 “vs”에 가깝다.
| 선택 기준 | ECS가 유리한 경우 | EKS가 유리한 경우 |
|---|---|---|
| AWS 단일 환경 | AWS 서비스 통합과 운영 단순성이 중요하다 | Kubernetes 표준 API가 꼭 필요하지 않다 |
| 멀티클라우드/온프레미스 | 요구가 약하다 | Kubernetes 생태계와 이식성이 중요하다 |
| 팀 전문성 | Kubernetes 전담 운영자가 없다 | cluster upgrade, add-on, policy 운영을 감당할 수 있다 |
| 생태계 | ALB, IAM, CloudWatch 중심이면 충분하다 | Istio, Argo CD, OPA, KEDA 같은 CNCF 도구가 필요하다 |
EKS는 EC2의 대체물이 아니라 Kubernetes control plane을 AWS에서 관리해 주는 서비스다. ECS를 이해한 뒤 EKS를 읽을 때는 “Task Definition은 Pod spec에 가깝고, Service desiredCount는 Deployment replicas에 가깝다” 정도로 옮겨 가면 된다.
긴 CLI 런북보다 먼저 증상과 개념을 연결한다.
| 증상 | 의미 | 먼저 볼 개념 |
|---|---|---|
Task가 STOPPED로 반복 재시작 | 컨테이너가 시작 후 종료된다 | Stopped reason, container logs, env validation |
CannotPullContainerError | image를 pull하지 못한다 | Execution Role, ECR 권한, NAT 또는 ECR/S3/Logs endpoint |
PROVISIONING에서 오래 멈춤 | Task 실행 준비에 필요한 capacity나 network path가 부족하다 | Subnet IP, ENI, NAT, VPC endpoint |
OOMKilled 또는 exit code 137 | Task memory 한도를 넘었다 | Task Definition memory, application peak, Node heap |
배포가 계속 in progress | 새 Task가 healthy가 되지 못하거나 교체 여유가 없다 | ALB health check, minimum/maximum percent, capacity |
| ALB 502가 배포 직후 낮은 비율로 발생 | drain과 shutdown 시간이 맞지 않는다 | deregistration delay, stopTimeout, graceful shutdown |
| Task는 RUNNING인데 S3 접근만 실패 | 앱 코드 권한이 없다 | Task Role과 Execution Role 구분, IAM policy |
| 환경변수 오타가 조용히 기본값으로 대체 | 앱은 뜨지만 잘못된 외부 시스템을 호출한다 | Config validation, Task Definition revision diff |
scale-out 후 DB too many connections | Task 수 x pool max가 DB 한도를 넘었다 | RDS max_connections, pool max, RDS Proxy |
| 특정 AZ에서만 Task가 부족 | subnet/capacity가 AZ별로 다르다 | subnet free IP, capacity provider, placement |
Task Definition에 DATABASE_URL 대신 DATABSE_URL을 넣어도 ECS는 key 이름의 의미를 모른다. 애플리케이션이 process.env.DATABASE_URL || fallback 같은 패턴을 쓰면 정상 부팅하고 ALB health check도 통과할 수 있다. 하지만 실제 트래픽은 fallback DB, 잘못된 callback URL, 비활성 feature flag로 흐른다.
이 문제의 해법은 콘솔에서 환경변수를 눈으로 찾는 것이 아니라 부팅 시 필수 설정 검증을 실패시키는 것이다. 설정 누락을 loud failure로 만들면 ECS Service가 잘못된 Task를 healthy로 받아들이지 않는다.
전체 요청 중 0.05%만 502라면 평균 대시보드에서는 노이즈처럼 보인다. 그러나 배포 직후나 scale-in 직후에만 spike가 생기고, ALB access log에서 target_processing_time = -1 또는 target status code 누락이 반복된다면 긴 요청이 drain 전에 잘렸을 수 있다.
이때는 “ALB가 가끔 이상하다”가 아니라 다음 관계를 본다.
긴 요청 p99< ALB deregistration delay<= ECS stopTimeout과 애플리케이션 shutdown 처리 시간의 조합정확한 숫자는 서비스마다 다르지만, 부등식이 깨지면 짧은 요청은 정상이고 긴 요청만 실패하는 silent failure가 된다.
본문을 이해하는 데 필요한 것은 위의 원리다. 실제 콘솔에서 확인할 때는 다음 순서로 좁힌다.
Stopped reason과 container exit code를 본다.CannotPullContainerError면 애플리케이션 로그가 아니라 Execution Role과 ECR/network path를 본다.OOMKilled면 코드보다 Task memory와 peak 사용량을 먼저 본다.minimumHealthyPercent와 maximumPercent가 새 Task를 띄울 여유를 주는지 본다.ECS를 제대로 이해하면 Kubernetes나 Nomad를 처음 볼 때도 질문을 옮겨 갈 수 있다. 이 절은 hard prerequisite가 아니라 선택 심화다.
컨테이너 image, resource, port, env 같은 실행 스펙을 정의한다.
새 workload의 실행 단위를 읽을 때정의된 스펙이 실제로 떠 있는 runtime instance다. 죽고 새로 만들어질 수 있다.
로그, 상태, 종료 원인을 추적할 때원하는 개수를 유지하고, health와 traffic routing을 관리한다.
무중단 배포와 트래픽 전환을 볼 때애플리케이션 코드가 외부 API나 cloud API를 호출할 때 쓰는 identity다.
S3, SQS, Secret 접근 권한을 점검할 때살아 있는 process와 traffic을 받아도 되는 endpoint를 구분한다.
배포가 멈추거나 반복 재시작할 때새 오케스트레이터를 보면 네 질문만 먼저 던진다.
이 네 질문에 답하면 이름이 달라도 큰 구조는 읽힌다.
executionRoleArn과 taskRoleArn을 구분해 본다.max_connections와 subnet IP를 넘지 않는지 손으로 계산한다.| 개념 A | 개념 B | 차이점 |
|---|---|---|
| EC2 | ECS | EC2는 compute 서버, ECS는 컨테이너 orchestration 서비스다. |
| ECS | Fargate | ECS는 orchestration 계층, Fargate는 ECS Task를 실행하는 serverless capacity다. |
| ECS on EC2 | EC2 직접 운영 | 둘 다 EC2를 쓰지만, 전자는 ECS가 Task 배치/복구/배포를 맡는다. |
| Task Definition | Task | Definition은 실행 스펙, Task는 그 스펙으로 실제 실행된 instance다. |
| Task | Service | Task는 실행 단위, Service는 Task를 원하는 개수로 유지하는 관리 단위다. |
| Task Role | Execution Role | Task Role은 앱 코드 권한, Execution Role은 ECS가 Task를 시작하는 권한이다. |
| ALB target healthy | Task RUNNING | RUNNING이어도 health check 실패 시 traffic을 받지 않는다. |
| capacity 부족 | 애플리케이션 버그 | 새 Task를 못 띄우는 문제와 Task가 떠서 바로 죽는 문제는 다르다. |
ECR, ALB Target Group, Fargate Spot, Capacity Provider, Service Auto Scaling, Blue/Green Deployment, ECS Exec, CloudWatch Container Insights, EKS, Kubernetes Deployment, RDS Proxy