컨테이너가 랜덤하게 재시작됐는데 알고 보니 OOMKilled였던 경우
문제 발생
애플리케이션이 특정 패턴 없이 가끔 재시작되는 문제가 있었는데, 애플리케이션 자체 로그에는 에러나 크래시 흔적이 전혀 없었습니다.
원인 분석
애플리케이션 로그가 깨끗한데 컨테이너가 재시작된다면, 애플리케이션이 스스로 죽은 게 아니라 외부(커널/오케스트레이터)에 의해 강제 종료됐을 가능성을 의심해야 합니다. 대표적인 원인이 OOMKilled입니다 — 컨테이너가 설정된 메모리 제한(resources.limits.memory)을 초과하면, 리눅스 커널의 OOM(Out Of Memory) killer가 그 프로세스를 즉시 강제 종료합니다. 이 종료는 애플리케이션에게 정상적으로 로그를 남기거나 정리할 기회조차 주지 않습니다 — SIGKILL과 유사하게 즉시 종료되기 때문에 애플리케이션 로그가 깨끗한 것입니다.
Kubernetes 환경이라면 kubectl describe pod <pod-name>으로 확인했을 때 Last State가 Terminated, Reason: OOMKilled로 표시되는 것이 명확한 증거입니다.
해결 방안
- 먼저 실제 메모리 사용 패턴을 확인합니다.
kubectl top pod나 모니터링 도구(Prometheus + Grafana 등)로 컨테이너가 실제로 얼마나 메모리를 쓰는지, 특정 시점에 급증하는지 확인합니다. - 일시적인 스파이크라면
limits.memory를 실제 필요량에 맞게 늘립니다 — 너무 빠듯하게 잡은 제한이 정상적인 부하에서도 OOMKilled를 유발할 수 있습니다.
resources:
requests:
memory: "256Mi"
limits:
memory: "512Mi" # 실제 사용 패턴에 맞춰 조정- 메모리 사용량이 시간이 지날수록 계속 늘어나는 패턴이라면 메모리 누수를 의심해야 합니다 — 이 경우 제한을 늘리는 것은 문제를 늦추는 것일 뿐 근본 해결이 아닙니다. 힙 덤프나 프로파일러로 실제 누수 지점을 찾아야 합니다.
requests와limits의 차이도 이해해야 합니다 —requests는 스케줄링 시 "이 정도는 보장해달라"는 값이고,limits는 "이 이상은 절대 못 쓴다"는 강제 상한입니다. 둘을 너무 다르게 잡으면(예: request는 작게, limit은 크게) 노드에 예상보다 많은 파드가 몰려 스케줄링될 수 있고, 실제 부하가 몰리는 시점에 여러 파드가 동시에 메모리 경합을 벌이는 상황이 생길 수 있습니다.- JVM처럼 자체적으로 힙 크기를 관리하는 런타임이라면, 컨테이너 메모리 제한과 JVM 힙 설정(
-Xmx등)을 서로 일치시켜야 합니다 — JVM이 컨테이너 제한을 인지하지 못하고 그보다 큰 힙을 쓰려고 하면 오히려 더 빨리 OOMKilled됩니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.