도입 배경
ECS Fargate 환경에서 OOM(Out of Memory) 문제가 발생한 이력이 있었습니다.
CloudWatch에서는 CPU 40%, 메모리 60%로 이상 없어 보였지만, 실제로는 다음과 같은 문제가 숨어 있었습니다.
- Full GC 20초 이상 반복
- Thread 수 지속 증가
- 원인: Thread Leak + Heap Pressure
Fargate처럼 리소스가 고정되어 있고 메트릭 확인이 불편한 환경에서는 정량적 JVM 상태를 실시간으로 추적하지 않으면 문제를 조기에 발견하거나 예방할 수 없습니다. 현재 AWS에서는 ECS에 대한 JVM 수준 메트릭을 제공하지 않습니다.
아키텍처
ECS Fargate Tasks (Spring Boot Actuator /prometheus endpoint)
↓
ecs-discovery (Task 목록 자동 수집 → ecs_file_sd.yml 생성)
↓
Prometheus (메트릭 수집 및 저장)
↓
Grafana (시각화 대시보드)
모든 컴포넌트는 운영 환경 내 EC2 인스턴스에서 Docker로 실행됩니다.
수집 가능한 JVM 지표
| 지표 | 설명 | 탐지 대상 |
|---|---|---|
| Heap Memory Used / Max | 힙 메모리 사용량 | OOM 경고 조기 인지. Xmx 근접 시 Full GC 빈도 증가 |
| GC Count / Time | GC 동작 횟수 및 소요 시간 | Full GC 반복 = 시스템 멈춤 수준 성능 저하 |
| Thread Count / Peak | Thread 증가 추이 | Thread Leak 조짐 탐지 |
| Metaspace Used | 클래스 로딩 공간 | Metaspace 가득 차면 OOM: Metaspace 발생 |
| Class Loading Count | 동적 클래스 로드 수 | 메모리 부하 유발 코드 탐지 |
| Uptime / CPU Usage | Task별 성능 지표 | CPU 스파이크는 GC나 무한 루프의 징후 |
Thread Leak = 객체가 여전히 참조되어 GC가 회수하지 못하는 객체 누수 Heap Pressure = GC 빈번 → Full GC 반복 → 앱 멈춤
ecs-discovery 도입
왜 ecs-discovery인가
ECS Service를 한 번 만든 후에는 LB Target Group에 Fargate를 추가하는 것이 불가하여 Service를 새로 만들어야 합니다. 또한 한 개의 ECS Service에 여러 컨테이너가 있을 수 있지만, 로드 밸런서 대상 그룹 연결은 각 서비스마다 한 개의 컨테이너에만 가능합니다.
ecs-discovery를 사용하면 Task Definition만 수정해주면 됩니다.
ecs-discovery 동작 방식
- AWS ECS 클러스터의 Task 목록을 주기적으로 조회
- 각 Task의 Docker Labels, 포트 정보를 파싱
- Prometheus용
ecs_file_sd.yml파일을 자동 생성 - Prometheus가 이 파일을 지속적으로 참조하여 스크래핑
실행 (운영/개발 클러스터 모두 수집)
# 운영 클러스터
docker run -d \
--name ecs-discovery-prd \
-v /monitoring/output_prd:/output \
-e AWS_REGION=ap-northeast-2 \
-e AWS_ACCESS_KEY_ID=[access-key] \
-e AWS_SECRET_ACCESS_KEY=[secret-key] \
tkgregory/prometheus-ecs-discovery \
--config.write-to=/output/ecs_file_sd.yml \
--config.cluster=[cluster-name-prd]
# 개발 클러스터
docker run -d \
--name ecs-discovery-dev \
-v /monitoring/output_dev:/output \
-e AWS_REGION=ap-northeast-2 \
-e AWS_ACCESS_KEY_ID=[access-key] \
-e AWS_SECRET_ACCESS_KEY=[secret-key] \
tkgregory/prometheus-ecs-discovery \
--config.write-to=/output/ecs_file_sd.yml \
--config.cluster=[cluster-name-dev]Prometheus 설정
# /etc/prometheus/prometheus.yml
- job_name: 'ecs-tasks'
file_sd_configs:
- files:
- /monitoring/output_prd/ecs_file_sd.yml
- /monitoring/output_dev/ecs_file_sd.ymlecs-discovery가 생성한 ecs_file_sd.yml이 갱신될 때 Prometheus가 자동으로 reload합니다.
Grafana 대시보드
JVM 메모리 관련 대시보드를 구성하여 다음을 시각화합니다.
- Heap Memory 사용량 추이
- GC 횟수 및 소요 시간
- Thread Count 변화
- Metaspace 사용량
- Task별 CPU/Memory 사용률
Grafana 지표 결과를 분석하여 정기 레포트 형식으로 전송할 수 있습니다.
비용 분석
ecs-discovery 방식 (채택)
| 항목 | 비용 |
|---|---|
| ECS Task 추가 리소스 | 메모리 5~30MB, CPU 0.1% 미만 |
| Prometheus + Grafana (EC2) | 기존 인스턴스 활용, 추가 비용 없음 |
| ecs-discovery | 로컬 리소스만 소모, AWS 과금 없음 |
| 합계 | 추가 비용 거의 없음 |
비교: 서비스별 NLB 연결 방식
각 태스크마다 모니터링용 서비스를 별도 생성하는 방식과 비교:
| 항목 | 비용 |
|---|---|
| Fargate → EC2 아웃바운드 트래픽 | ~$21/월 (9개 Task 기준) |
| 관리 포인트 | 서비스 수, LB 연결, 리스너 수 증가 |
40KB 응답 × 5초 간격 × 9개 Task = 약 186GB/월 아웃바운드 트래픽
ecs-discovery 방식이 비용과 관리 측면 모두 유리합니다.
실전 사례
[상황]
A 서비스가 갑자기 느려짐
CloudWatch: CPU 40%, 메모리 60% → 이상 없어 보임
[Prometheus 확인]
- Full GC 20초 이상 반복
- Thread 수 지속 증가
[원인]
Thread Leak + Heap Pressure
[결과]
조기 대응 가능 → 서비스 장애 방지
정리
- CloudWatch만으로는 보이지 않는 JVM 내부 상태를 실시간 추적
- ecs-discovery로 ECS Task 자동 감지, 관리 포인트 최소화
- Prometheus + Grafana로 시각화 대시보드 구성
- 서비스별 NLB 연결 대비 비용 절감 및 운영 간소화
- OOM, Thread Leak, GC 이슈 조기 탐지 체계 확립