🧩 ECS 핵심 개념 이해하기
왜 ECS가 필요할까?
스프링부트 앱을 클라우드에 배포할 때, 컨테이너를 여러 대/여러 환경에서 안정적으로 실행·관리하는 오케스트레이션이 필요해진다.
- 컨테이너가 죽으면 누가 다시 띄워줄까?
- 트래픽이 늘면 어떻게 자동으로 확장할까?
- 새 버전 배포 시 다운타임 없이 어떻게 할까?
- 수십 개의 컨테이너를 어디에 어떻게 배치할까?
- 로그는 어떻게 모을까? (컨테이너는 재시작하면 사라짐)
오케스트레이션이란 여러 개의 컨테이너를 어디에 배치할지, 몇 개를 돌릴지, 죽으면 재시작할지 자동으로 관리하는 시스템이다.
(예: 쿠버네티스(K8s), ECS)
ECS가 해결하는 것
ECS는 단순한 컨테이너 실행 도구가 아니라 컨테이너의 생명주기 전체를 관리하는 운영 플랫폼이다.
| 문제 | ECS가 제공하는 해결 |
| 컨테이너를 단일 명령으로 실행하고 관리하고 싶다 | Task/Service 관리 |
| 서버 인프라 관리를 줄이고 싶다 | Fargate(서버리스) |
| 컨테이너 수평 확장이 필요하다 | Auto Scaling & Load Balancing |
| 변경된 버전 배포를 안전하게 하고 싶다 | Rolling update, Blue/Green |
ECS의 3대 핵심 요소
Task Definition
- 정의: 컨테이너 실행을 위한 도면(설계도)
- 왜 필요한가?
- 사람이 매번 수동으로 설정하면 실수 발생
- 자동 복구 시 동일한 환경 재현 필요
- 배포 자동화의 기반
- 포함 내용:
- 어떤 Docker 이미지? ( myapp:latest )
- CPU/메모리는? (512 CPU, 1GB RAM)
- 환경 변수는? ( SPRING_PROFILE=prod )
- 어떤 포트? (8080)
- 로그는 어디로? (CloudWatch)
- Spring Boot 예시
- Task Definition은 불변이므로 수정하려면 새 버전을 만들어야 한다.
- 도면(계획서)일 뿐이므로 실제로 실행되지 않는다.
{
"family": "spring-boot-app",
"containerDefinitions": [
{
"name": "app",
"image": "123456789.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:latest",
"cpu": 256,
"memory": 512,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{ "name": "SPRING_PROFILES_ACTIVE", "value": "prod" },
{ "name": "SERVER_PORT", "value": "8080" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/spring-boot-app",
"awslogs-region": "ap-northeast-2",
"awslogs-stream-prefix": "ecs"
}
}
}
],
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789:role/ecsTaskRole"
}
- Task: Task Definition을 기반으로 실행되는 컨테이너 그룹
- 하나의 Task = 하나 이상의 컨테이너 묶음
- 예: app + sidecar 형태도 가능
Service
- 정의: Task가 항상 N개 실행되도록 유지하는 관리자
- 왜 필요한가?
- 컨테이너는 언제든 죽을 수 있음
- 트래픽 변동에 따라 개수 조절 피룡
- 무중단 배포 필요
- Service가 하는 일
- 지정된 개수의 Task를 항상 유지: Desired Count = 3이면 죽으면 자동으로 다시 띄움
- Auto Scaling 연동: CPU 70% 넘으면 자동으로 Task 추가
- Load Balancer와 연결: 새로 생성된 Task를 ALB에 자동 등록
- 롤링 업데이트: 새 버전 배포 시 하나씩 교체
- Health Check: 비정상 Task는 자동 제거 후 재생성
- Service가 ECS의 자가 치유(self-healing) 기능을 수행한다.
- Service 설정: Desired Count = 3
- 현재 실행 중: Task A, Task B, Task C
- Task B가 갑자기 죽음 (메모리 부족)
- ECS Service: "어? 2개밖에 없네? Task D 생성!"
- 다시 3개 유지됨
Cluster
- 정의: Task를 실행할 인프라 환경
- 왜 필요한가?
- 컨테이너를 어딘가에는 올려야 함
- CPU/메모리 자원을 어디서 가져올지 결정
- EC2 기반 Cluster
- 장점: 비용 최적화 가능, 세밀한 제어 가능, 더 다양한 인스턴스 타입 선택
- 단점: EC2 관리 필요, 용량 계획 필요, 운영 부담 추가
- Fargate 기반 Cluster (추천!)
- 장점: Serverless, 사용한 만큼만 과금, Auto Scaling 단순화, 보안 강화 (Host 격리)
- 단점: EC2보다 약간 비쌈, CPU/Memory 제한, 네트워크 성능 제한
- 추천하는 이유: 서버 관리 없이 컨테이너에만 집중할 수 있기 때문
- 왜 CPU/Memory 조합이 제한되나: 서버리스 컨테이너 런타임이기 때문 (AWS 내부 인프라에 맞춰서 표준화된 조합으로 제한)
IAM Role 이해하기
Task Execution Role
- 목적: ECS가 Task를 시작하기 위해 필요한 권한
- 필요한 권한:
- ECR에서 이미지 pull
- CloudWatch Logs에 로그 전송
- Secrets Manager에서 시크릿 읽기
- 예시 정책
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:CreateLogGroup"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:*:*:secret:prod/*"
}
]
}
Task Role
- 목적: 컨테이너 내부의 애플리케이션이 AWS 서비스에 접근하기 위한 권한
- 필요한 권한 예시
- S3 버킷 읽기/쓰기
- DynamoDB 테이블 접근
- SQS 메시지 읽기
- SNS 토픽 발행
- 예시 정책 (S3 접근)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/*"
}
]
}
💡 Task Execution Role은 ECS 엔진이 사용(인프라 레벨), Task Role은 애플리케이션 코드가 사용(애플리케이션 레벨)
Scheduler
- 역할: 컨테이너를 어디에 배치할 지 자동으로 결정
- Scheduler가 하는 판단
- 어떤 서버에 여유 공간이 있는가?
- 네트워크 접근이 가능한가?
- 배치 제약 조건을 만족하는가?
- 가용 영역(AZ)을 분산해야 하는가?
- 배치 전략 (EC2 모드)
- binpack
- CPU나 메모리를 최대한 활용
- 인스턴스 수 최소화
- 비용 최적화에 유리
- spread
- 지정된 값(AZ, 인스턴스 ID)에 균등 분산
- 가용성 향상
- 한 AZ 장애 시 영향 최소화
- random
- 무작위 배치
- 특별한 요구사항 없을 때
- binpack
- Fargate는 spread 전략을 자동으로 사용한다.
Task 3개를 배치해야 함
→ Scheduler: "AZ-1에 1개, AZ-2에 1개, AZ-3에 1개 배치!"
→ 한 AZ 장애나도 서비스 계속 운영 가능
- ECS 스케쥴러는 어떻게 Task를 배포하는가?
- Fargate의 경우
- Task Definition의 CPU/Memory 스펙을 보고 그 스펙에 맞는 Fargate 실행 환경을 AWS가 자동 생성 (Fargate VM)
- 특정 서버에 배치하는 과정 없기 떄문에 사실상 '자동 배치'로 이해하면 됨
- EC2 기반의 경우
- ECS 스케줄러가 클러스터 내 EC2 인스턴스들을 살펴보고 Task가 필요로 하는 CPU/Memory가 남아 있는 인스턴스에 Task를 배치
- 포트 충돌, AZ 분배, capacity provider까지 고려해서 배포
- Fargate의 경우
🧩 ECS 구조 깊이 파고들기
네트워크 모드: awsvpc
문제 상황
- 옛날 방식: EC2 하나에 여러 컨테이너 → 포트 충돌
- 보안 그룹을 EC2 단위로만 설정 → 세밀한 제어 불가
awsvpc의 해결책
- 각 Task가 ENI(독립 네트워크 인터페이스)를 가짐
- Task마다 Private IP 할당 보안 그룹을 Task 단위로 설정 가능
💡 ENI는 가상의 NIC로, 우리가 컴퓨터에서 사용하는 LAN 카드와 같은 역할을 한다. EC2나 Fargate Task가 네트워크에 연결되기 위해 반드시 필요한 가상 네트워 크 인터페이스라고 이해하면 편하다.
과거 vs 현재 비교
- bridge 모드 (옛날 방식)
- EC2 하나의 보안 그룹
- Container1 (포트 8080)
- Container2 (포트 8081) ← 포트 매핑 필요
- Container3 (포트 8082)
- 모두 같은 보안 정책
- awsvpc 모드 (지금 방식)
- Task1 (독립 ENI, 독립 보안그룹, 10.0.1.10:8080)
- Task2 (독립 ENI, 독립 보안그룹, 10.0.1.20:8080)
- Task3 (독립 ENI, 독립 보안그룹, 10.0.1.30:8080)
- 포트 충돌 없음!
스프링부트에서의 의미
- 각 앱 인스턴스가 독립적인 네트워크 엔티티
- MSA 구조에 최적
- 서비스별 보안 정책 적용 가능
- 포트는 항상 8080 사용 가능 (포트 매핑 불필요)
awsvpc의 제약사항
- ENI 개수 제한 (서브넷의 IP 개수 제한)
- Task당 하나의 ENI 소모
- 큰 클러스터는 충분한 IP 주소 확보 필요
Health Check 이해하기
Container Health Check (Docker 레벨)
- 목적: 컨테이너 자체가 정상인지 확인
- 동작
- Task Definition에 정의
- Docker가 주기적으로 실행
- 실패 시 컨테이너 재시작
- 예시 (Task Definition)
# wget을 사용하려면 이미지에 설치되어 있어야 함
{
"healthCheck": {
"command": [
"CMD-SHELL",
"wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1"
],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
ALB Health Check (로드밸런서 레벨)
- 목적: Task가 트래픽을 받을 준비가 되었는지 확인
- 동작
- ALB Target Group에 정의
- Target Group은 ALB가 “어디로 보낼지” 알려주는 주소 목록
- Target Group은 Load Balancer가 전달한 요청을 실제로 처리할 대상(Target)들을 논리적으로 묶어 관리하는 단위(Resource Set)
- ALB는 Listener 규칙에 따라 특정 Target Group으로 트래픽을 라우팅하며, Target Group은 등록된 대상의 상태를 지속적으로 모니터링하고 트래픽 분배 정 책을 관리함
- ALB가 주기적으로 HTTP 요청
- 실패 시 해당 Task로 트래픽 전송 중단
- ALB Target Group에 정의
- 예시 (Target Group)
lbv2 create-target-group \
--name spring-boot-tg \
--protocol HTTP \
--port 8080 \
--vpc-id vpc-xxxxx \
--target-type ip \
--health-check-enabled \
--health-check-path /api/health \
--health-check-interval-seconds 30 \
--health-check-timeout-seconds 5 \
--healthy-threshold-count 2 \
--unhealthy-threshold-count 2
- Health Check Grace Period
- Service 설정
- Task 시작 후 이 기간 동안은 ALB Health Check 실패를 무시
- 애플리케이션 초기화 시간 고려
"healthCheckGracePeriodSeconds": 60
💡 Best Practice
- Container Health Check: 기본적인 생존 확인 (간단한 체크)
- ALB Health Check: 실제 트래픽 처리 준비 확인 (의존성 포함)
Load Balancer 통합
왜 ALB가 필요한가?
- 컨테이너의 특성:
- IP가 계속 바뀜 (재시작, Auto Scaling)
- ECS(Fargate/EC2) 컨테이너는 EIP(Elastic IP)를 직접 가질 수 없기 때문에 IP 고정이 불가능함
- 그래서 ALB나 Service Discovery 같은 라우팅 계층이 반드시 필요하다.
- EIP는 안정적인 Public Endpoint용인데, Task ENI는 너무 수명이 짧아서 부적합
- (Task가 생성되면 새 ENI가 만들어지고, Task가 종료되면 그 ENI는 삭제됨)
- 여러 개가 동시 실행됨
- 동적으로 생성/삭제됨
- IP가 계속 바뀜 (재시작, Auto Scaling)
- ALB의 역할
- 동적 라우팅
- Task 생성되면 자동으로 Target Group에 등록
- Task 삭제되면 자동으로 제거
- IP 기반 타게팅 (awsvpc 모드)
- Health Check
- 비정상 Task는 트래픽 안 보냄
- 정상화되면 다시 트래픽 전송
- Draining: 종료 예정 Task는 새 연결 받지 않음
- 무중단 배포
- 새 버전 Task 먼저 띄우기
- Health Check 통과하면 트래픽 전환
- 구 버전 Task 종료
- Connection Draining
- Task 종료 시 기존 연결 유지
- 새 연결만 차단
- 기본 300초 대기
- 동적 라우팅
- 실제 흐름: 사용자 요청 → ALB → Target Group → Health Check 확인 → 정상 Task들에만 분산
- Target Group의 등록 해제 지연(Deregistration Delay)
- 기본값: 300초
- Task 종료 시 이 시간 동안 기존 연결 처리
- 너무 길면 배포 느려짐 너무 짧으면 요청 실패
Auto Scaling
- 왜 필요한가?
- 평일 낮: 트래픽 많음 → 더 많은 Task 필요
- 주말 새벽: 트래픽 적음 → 비용 낭비 방지
- Target Tracking Scaling (권장)
- 자동으로 지표를 추적하여 조정
- 목표값 설정 및 CloudWatch 알람 자동 생성
- Scale Out/In 자동 실행
- Scaling 기준 예시
- CPU 기반
- 평균 CPU 70% 유지
- 대부분의 웹 애플리케이션에 적합 (가장 일반적)
- 메모리 기반
- 평균 메모리 80% 유지
- 메모리 집약적 앱에 적합
- ALB 기반
- Target당 요청 수 1000개 유지
- CPU보다 빠른 반응
- 웹 서비스에 이상적
- 커스텀 메트릭 (SQS)
- 큐 메시지 100개당 Task 1개
- 비동기 워커에 적합
- CPU 기반
- 설정 예시
- Min Capacity: 2 (최소 2개는 항상 실행)
- Max Capacity: 10 (최대 10개까지 확장)
- Target Value: 70% (목표 CPU 사용률)
- Cooldown 이해하기
- Scale Out Cooldown: Scale Out 후 다음 Scale Out까지 대기 시간 (기본 60초)
- Scale In Cooldown: Scale In 후 다음 Scale In까지 대기 시간 (기본 300초)
- Scale In이 더 긴 이유: 안정성 우선 (너무 빨리 줄이면 트래픽 급증 시 문제)
트래픽 급증 → 5개로 증가 → 60초 대기 → 필요시 추가 증가
트래픽 감소 → 300초 대기 → 안정적이면 축소
[참고자료]
- 내일배움캠프 AWS ECS 강의자료
'내일배움캠프' 카테고리의 다른 글
| [내일배움캠프] AWS ECS 실습 (AWS CLI) (0) | 2026.06.21 |
|---|---|
| [내일배움캠프] 무중단 배포 (0) | 2026.06.20 |
| [내일배움캠프] IaC와 Terraform (0) | 2026.06.20 |
| [내일배움캠프] Spring Batch (0) | 2026.06.15 |
| [내일배움캠프] RAG (0) | 2026.06.10 |