[IT 심층 분석]
쿠버네티스 Pod OOM 재시작 반복의 숨겨진 원인과 리소스 설정 및 Liveness Probe 오구성 해부
김민준 · IT 시스템 엔지니어|
쿠버네티스로 전환한 이후 프로덕션 클러스터에서 특정 컨테이너가 하루에도 몇 번씩 무한 재시작 루프에 빠지는 현상이 퇴근을 막는 단골 악재가 되었습니다. kubectl describe pod 명령으로 이벤트 로그를 확인하면 항상 두 가지 원인 중 하나가 찍혀있었습니다. OOMKilled 즉 메모리 한도 초과 강제 종료이거나 Liveness probe failed로 인한 건강 검진 실패였습니다. 표면적으로는 다른 원인처럼 보이지만 실제로 이 두 가지는 쿠버네티스 리소스 설정을 잘못 이해한데서 파생된 같은 뿌리를 가진 문제였습니다.
OOMKilled가 발생한 컨테이너의 resources.limits.memory 값을 확인해보니 256Mi로 설정되어 있었습니다. 문제는 해당 서비스가 실제 운영 부하에서 평균 400Mi를 사용하는 Java 어플리케이션이었다는 것입니다. 개발자가 개발 환경에서의 가벼운 측정값을 그대로 프로덕션에 옮겨온 실수였습니다. limits 값은 쿠버네티스가 컨테이너에게 제공할 수 있는 최대 자원 한도이며 이를 초과하는 순간 커널 OOM Killer가 무자비하게 프로세스를 종료합니다. resources.requests는 스케줄링을 위한 최솟값이고 limits는 물리적 상한선임을 명확히 구분하는 이해가 필요합니다.
Liveness Probe 오구성 문제는 더 미묘했습니다. /health 엔드포인트를 주기적으로 호출하도록 설정해두었는데 initialDelaySeconds가 5초로 너무 짧았습니다. JVM 기반의 서비스는 스프링 앱 컨텍스트를 초기화하는 데만 보통 15~30초가 소요됩니다. 아직 완전히 기동되지 않은 상태에서 헬스체크가 시작되어 응답을 받지 못하자 쿠버네티스가 컨테이너가 비정상이라고 판단하고 재시작을 반복하면서 실제로는 정상인 서비스가 영원히 올라오지 못하는 악순환에 갇힌 것이었습니다. 해결책으로 initialDelaySeconds를 60초로 늘리고 JVM 힙 사용 현황을 모니터링한 후 limits.memory를 700Mi로 조정했습니다. 그날 이후 Pod는 단 한 번의 재시작도 없이 조용하고 건강하게 장기 운영되고 있습니다.