
ECS 완전 정복: 컨테이너 하나는 쉽다, 수백 개를 관리하는 건 다른 문제다
Docker로 컨테이너를 만드는 건 쉽다. 진짜 문제는 수십~수백 개의 컨테이너를 안정적으로 운영하는 것이다. 컨테이너 오케스트레이션이 왜 필요한지, AWS ECS가 어떻게 이 문제를 해결하는지, 실전 아키텍처까지 풀어본다.

Docker로 컨테이너를 만드는 건 쉽다. 진짜 문제는 수십~수백 개의 컨테이너를 안정적으로 운영하는 것이다. 컨테이너 오케스트레이션이 왜 필요한지, AWS ECS가 어떻게 이 문제를 해결하는지, 실전 아키텍처까지 풀어본다.
이전 글에서 Docker가 "내 컴퓨터에서는 되는데?" 문제를 해결했다고 설명했다. 컨테이너 하나를 만들고 실행하는 것은 docker run 한 줄이면 된다.
하지만 현실의 프로덕션 환경을 상상해 보자. 당신이 운영하는 서비스가 성장했다. 이제 다음이 필요하다:
총 53개의 컨테이너가 여러 대의 서버(EC2 인스턴스)에 흩어져 돌아간다. 이제 이런 질문들이 쏟아진다:
docker run을 53번 치는 것으로는 이 문제를 풀 수 없다. 이것이 바로 컨테이너 오케스트레이션(Container Orchestration) 이 필요한 이유이고, AWS가 이를 위해 만든 서비스가 ECS(Elastic Container Service)다.
컨테이너 오케스트레이션의 역사는 Google 내부 시스템에서 시작한다.
2003~2004년, Google은 폭발적으로 성장하는 검색·메일·지도 서비스를 관리하기 위해 Borg라는 내부 클러스터 관리 시스템을 개발했다. Borg의 역할: 수만 대의 서버에서 수십만 개의 작업(컨테이너)을 자동으로 배치·관리·복구하는 것.
2015년, Google은 Borg의 설계와 10년간의 운영 경험을 "Large-scale cluster management at Google with Borg" (Verma 등, EuroSys 2015) 논문으로 공개했다. 이 논문은 컨테이너 오케스트레이션 분야의 사실상의 교과서가 됐다.
Borg 논문의 핵심 설계 원칙들:
Google이 Borg의 아이디어를 2014년 쿠버네티스(Kubernetes) 로 오픈소스화하면서, 컨테이너 오케스트레이션 시장에 전쟁이 시작됐다.
| 시기 | 도구 | 주도 | 특징 |
|---|---|---|---|
| 2013 | Docker Swarm | Docker Inc. | Docker 네이티브, 간단함 |
| 2014.6 | Kubernetes | Google (→ CNCF) | Borg 기반, 유연하지만 복잡 |
| 2014.11 | ECS | AWS | AWS 네이티브, 관리형 서비스 |
| 2016 | Mesos/Marathon | Apache | 대규모 클러스터 특화 |
| 2017~ | Kubernetes가 사실상 표준으로 수렴 |
각 진영이 "컨테이너를 대규모로 어떻게 관리할 것인가"라는 같은 문제를 다른 철학으로 풀었다. 오픈소스 진영에서는 쿠버네티스가 승리했지만, AWS 생태계 안에서는 ECS가 독자적인 위치를 확립했다.
ECS(Elastic Container Service)는 AWS에서 Docker 컨테이너를 실행·관리하는 완전관리형 컨테이너 오케스트레이션 서비스다. 2014년 11월에 출시됐다.
핵심 가치: 컨테이너를 실행하는 인프라(EC2 클러스터)를 직접 관리하면서, 그 위에서 컨테이너의 배치·스케일링·복구를 자동화해 주는 것이다.
쉽게 말하면:
ECS를 이해하려면 4가지 핵심 개념을 알아야 한다. 처음엔 용어가 낯설지만, 각각의 역할은 명확하다.
하나씩 살펴보자.
태스크 정의는 **"어떤 컨테이너를 어떻게 실행할 것인가"**를 정의하는 JSON 문서다. Docker의 docker run 명령에 들어가는 모든 옵션을 구조화한 것이다.
{
"family": "my-web-app",
"containerDefinitions": [
{
"name": "web",
"image": "my-app:1.0",
"cpu": 512,
"memory": 1024,
"portMappings": [
{ "containerPort": 3000, "protocol": "tcp" }
],
"environment": [
{ "name": "NODE_ENV", "value": "production" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-web-app",
"awslogs-region": "ap-northeast-2"
}
}
}
],
"cpu": "512",
"memory": "1024",
"networkMode": "awsvpc"
}
태스크 정의는 버전 관리된다. my-web-app:1, my-web-app:2처럼 리비전이 자동으로 증가한다. 문제가 생기면 이전 리비전으로 즉시 롤백할 수 있다.
태스크는 태스크 정의를 기반으로 실제 실행 중인 컨테이너 인스턴스다. Docker에서 "이미지 : 컨테이너" 관계가 ECS에서는 "태스크 정의 : 태스크" 관계다.
하나의 태스크에는 여러 컨테이너가 포함될 수 있다. 예를 들어 웹 앱 컨테이너 + 사이드카 로그 수집기 컨테이너를 하나의 태스크로 묶어 항상 같이 실행되게 할 수 있다.
서비스는 **"태스크를 원하는 개수만큼 항상 유지하라"**는 선언이다.
"웹 서버를 5개 실행하라"고 서비스를 정의하면:
이것이 Borg 논문의 "선언적 배포" 개념이다. "어떻게 실행할지"가 아니라 "무엇을 원하는지"를 선언하면, 시스템이 현재 상태를 원하는 상태로 맞춰간다.
클러스터는 태스크가 실행되는 논리적 그룹이다. 하나의 클러스터에는 여러 EC2 인스턴스가 속하고, ECS는 클러스터 안의 인스턴스들에 태스크를 자동 배치한다.
EC2 인스턴스가 10대 있다. 새 컨테이너(CPU 2코어, 메모리 4GB 필요)를 실행해야 한다. 어떤 인스턴스에 놓을까?
수동으로 하면:
ECS의 스케줄러(Scheduler) 가 이 모든 것을 자동으로 한다. bin-packing 전략(남는 공간을 최소화하는 방식으로 배치)이나 spread 전략(가능한 한 분산 배치) 중 선택할 수 있다.
컨테이너는 프로세스다. 프로세스는 죽을 수 있다. 메모리 누수, 예외 처리되지 않은 에러, 의존 서비스 장애 — 원인은 다양하다.
Docker만으로는 컨테이너가 죽었는지 알 수 있지만, 자동으로 재시작하고 원하는 개수를 유지하는 것은 별도의 관리 체계가 필요하다.
ECS 서비스는 desired count(원하는 태스크 수) 를 항상 유지한다. 태스크가 죽으면 수 초 내에 새 태스크를 시작한다. 헬스체크를 통해 애플리케이션 수준의 장애(프로세스는 살아있지만 응답하지 않는 경우)도 감지한다.
가장 흔한 배포 시나리오: 웹 앱 v1.0을 v1.1로 업그레이드해야 한다. 5개의 컨테이너가 돌고 있다. 전부 동시에 교체하면 순간적으로 서비스가 중단된다.
ECS는 롤링 업데이트(Rolling Update) 를 기본 지원한다:
minimumHealthyPercent와 maximumPercent 파라미터로 배포 속도를 제어할 수 있다. 예를 들어 minimumHealthyPercent: 50, maximumPercent: 200이면 기존 태스크의 절반까지 줄이면서 동시에 새 태스크를 추가로 실행할 수 있어 배포가 빨라진다.
ECS 서비스에 Application Auto Scaling을 붙이면, 지정한 메트릭에 따라 태스크 수를 자동으로 조절한다:
ECS는 awsvpc 네트워크 모드를 사용하면 각 태스크에 독립된 ENI(Elastic Network Interface)와 프라이빗 IP를 부여한다. 각 컨테이너가 VPC 안에서 자체 IP를 갖는 것이다.
서비스 간 통신에는 여러 방법이 있다:
http://api-service:8080)실제 프로덕션에서 가장 흔한 ECS 아키텍처를 살펴보자:
이 아키텍처에서 ECS가 담당하는 것:
ECS가 담당하지 않는 것:
Docker Hub의 AWS 버전이 ECR(Elastic Container Registry)다. ECS와 함께 거의 항상 사용된다.
CI/CD 파이프라인의 전형적인 흐름:
ECR의 이점:
이것은 ECS 도입을 고려하는 모든 팀이 묻는 질문이다.
ECS를 선택해야 하는 경우:
EKS(쿠버네티스)를 선택해야 하는 경우:
언어 학습 앱 Duolingo는 ECS를 핵심 인프라로 사용한다. 매일 수백만 명의 동시 사용자가 실시간으로 학습 세션을 진행하며, 사용자 트래픽은 시간대별로 크게 변동한다.
ECS의 오토스케일링으로 아침 출근 시간과 저녁 시간에 자동 확장, 새벽에는 자동 축소하여 비용을 최적화한다.
삼성의 IoT 플랫폼 SmartThings는 수억 대의 기기에서 발생하는 이벤트를 처리한다. 마이크로서비스 아키텍처를 ECS에서 운영하며, 이벤트 폭증 시 자동 스케일링으로 대응한다.
CNN을 운영하는 Turner Broadcasting은 2017년 ECS로 마이그레이션하며 인프라 비용을 50% 절감하고, 배포 시간을 일 단위에서 분 단위로 단축했다.
10~50명 규모의 스타트업에서 ECS가 인기 있는 이유:
별도의 인프라팀 없이 개발자가 직접 컨테이너 오케스트레이션을 운영할 수 있다는 것이 ECS의 가장 큰 매력이다.
실제로 ECS에서 서비스를 시작하는 과정을 단계별로 살펴보자.
이전 글에서 설명한 대로 Dockerfile을 작성하고 이미지를 빌드한다.
docker build -t my-api:1.0 .
# ECR 레포지토리 생성
aws ecr create-repository --repository-name my-api
# 로그인
aws ecr get-login-password | docker login --username AWS \
--password-stdin 123456789.dkr.ecr.ap-northeast-2.amazonaws.com
# 태그 & 푸시
docker tag my-api:1.0 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-api:1.0
docker push 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-api:1.0
AWS 콘솔 또는 CLI로 ECS 클러스터를 생성한다. EC2 런치 타입을 선택하면 ECS가 EC2 인스턴스를 프로비저닝한다.
이미지 URI, CPU/메모리 할당, 포트 매핑, 환경변수, 로그 설정을 정의한다.
태스크 정의를 기반으로 서비스를 생성한다. 원하는 태스크 수, 배포 전략, 로드밸런서를 설정한다.
aws ecs create-service \
--cluster my-cluster \
--service-name my-api-service \
--task-definition my-api:1 \
--desired-count 3 \
--launch-type EC2 \
--load-balancers targetGroupArn=arn:aws:...,containerName=web,containerPort=3000
# 스케일링 타겟 등록
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/my-cluster/my-api-service \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 2 \
--max-capacity 20
# CPU 70% 타겟 트래킹 정책
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/my-cluster/my-api-service \
--scalable-dimension ecs:service:DesiredCount \
--policy-name cpu-tracking \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
}
}'
이 설정이 완료되면, ECS가 CPU 사용률 70%를 유지하도록 태스크 수를 2~20개 범위에서 자동 조절한다.
healthCheckGracePeriodSeconds를 반드시 설정하라.ECS 태스크의 로그는 CloudWatch Logs로 자동 전송할 수 있다. 태스크 정의에서 awslogs 로그 드라이버를 설정하면 된다.
핵심 모니터링 메트릭:
| 메트릭 | 의미 | 알람 기준 예시 |
|---|---|---|
| CPUUtilization | 서비스의 평균 CPU 사용률 | > 80% 5분 지속 |
| MemoryUtilization | 서비스의 평균 메모리 사용률 | > 85% 5분 지속 |
| RunningTaskCount | 실행 중인 태스크 수 | < desired count |
| HealthyHostCount (ALB) | 정상 태스크 수 | < 원하는 최소 수 |
EC2 런치 타입에서의 비용 최적화 전략:
컨테이너 오케스트레이션의 본질은 이렇다:
"나는 이 서비스가 항상 N개 실행되고, 장애 시 자동 복구되고, 트래픽에 따라 확장되고, 무중단으로 배포되기를 원한다."
이 선언을 하면, ECS가 현실을 그 선언에 맞춰간다. 컨테이너 하나를 실행하는 것은 Docker로 충분하다. 하지만 수십~수백 개의 컨테이너를 안정적으로 운영하려면 오케스트레이션이 필요하고, AWS 생태계에서 가장 간단한 선택이 ECS다.
정리하면:
다음 글에서는 EC2 인스턴스 관리마저 필요 없는 서버리스 컨테이너, AWS Fargate를 다뤄보겠다. ECS의 모든 장점을 유지하면서, 서버 관리라는 마지막 부담까지 없애는 방법이다.