증상
ECS Fargate에서 운영 중인 서비스의 태스크가 간헐적으로 종료되는 현상이 발생했습니다.
- ECS 콘솔에서 태스크 상태:
STOPPED - Stop reason:
OutOfMemoryError: Container killed due to memory usage - 특정 시간대(트래픽 피크)에 집중 발생
- 태스크가 종료되면 ECS 서비스가 새 태스크를 띄우지만, 반복 종료로 서비스 불안정
원인 분석
1. Task Definition 메모리 설정 확인
{
"memory": "512",
"containerDefinitions": [
{
"memory": 512,
"memoryReservation": 256
}
]
}Task 레벨 메모리와 컨테이너 메모리가 동일하게 512MB로 설정되어 있어, 컨테이너가 사용할 수 있는 여유 메모리가 전혀 없는 상태였습니다.
2. JVM 힙 메모리 미설정
Java 기반 애플리케이션이었으나 JVM 힙 메모리(-Xmx, -Xms)가 명시적으로 설정되지 않아, JVM이 컨테이너 메모리 전체를 힙으로 사용하려 시도했습니다.
3. CloudWatch 메트릭 확인
CloudWatch의 MemoryUtilization 메트릭을 확인한 결과, 평상시 70~80%를 유지하다가 피크 시 95% 이상으로 치솟는 패턴이 확인되었습니다.
해결 방법
1. Task Definition 메모리 상향
{
"memory": "1024",
"containerDefinitions": [
{
"memory": 1024,
"memoryReservation": 512
}
]
}2. JVM 힙 메모리 명시 설정
Dockerfile 또는 Task Definition의 환경 변수에 JVM 옵션을 추가했습니다.
JAVA_OPTS="-Xms256m -Xmx768m -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"-XX:+UseContainerSupport: 컨테이너 메모리 제한을 JVM이 인식-XX:MaxRAMPercentage=75.0: 컨테이너 메모리의 75%까지만 힙으로 사용 (나머지는 메타스페이스, 스레드 스택 등에 할당)
Heap 제한의 핵심 — "죽는 서비스"를 "느려지기만 하는 서비스"로
JVM Heap을 컨테이너 메모리의 60~70%로 제한하는 이유는 단순히 메모리 절약이 아닙니다. OOM으로 프로세스가 죽는 것을 GC로 느려지기만 하는 것으로 바꾸는 것이 핵심입니다.
설정 안 했을 때
JVM Heap이 컨테이너 메모리 끝까지 사용
↓
Native / Stack / Metaspace 공간 부족
↓
Linux OOM Killer 작동
↓
컨테이너 즉시 종료 (Exit 137)
↓
ECS가 태스크 교체 → 순간 서비스 영향
설정했을 때 (60~70%)
Heap이 미리 제한됨
↓
메모리 압박 시 GC 먼저 발생
↓
응답 느려질 수는 있지만 프로세스는 살아 있음
↓
헬스체크 실패 확률 급감
📌 운영 관점에서 "잠깐 느려짐"은 감당 가능하지만, "프로세스 종료"는 장애입니다.
3. CloudWatch 알람 설정
OOM 재발 방지를 위해 메모리 사용률 기반 알람을 추가했습니다.
MemoryUtilization >= 85% → Warning (Slack 알림)
MemoryUtilization >= 95% → Critical (전화 알림)
결과
- 메모리 설정 변경 후 OOM으로 인한 태스크 종료 0건
- 평상시 메모리 사용률 40~50%로 안정화
- 피크 시에도 70% 이하 유지
- JVM 힙 설정으로 GC 동작도 안정적으로 개선
교훈
- ECS Fargate에서 JVM 애플리케이션을 운영할 때는 반드시
-XX:MaxRAMPercentage나-Xmx를 명시해야 합니다 - Task 메모리와 컨테이너 메모리 사이에 여유분을 두는 것이 중요합니다
MemoryUtilization메트릭 알람은 OOM 사전 감지에 필수입니다