프로덕션 워크로드 중 하나가 간헐적으로 OOMKilled됩니다. 근데 kubectl top pod로 보면 limit 대비 40~50%밖에 안 쓰는 걸로 나와서 원인 파악이 안 돼요.
$ kubectl describe pod my-app-xxxxx
...
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Containers:
app:
Limits: memory: 1Gi
Requests: memory: 512Mi
$ kubectl top pod my-app-xxxxx
NAME CPU(cores) MEMORY(bytes)
my-app-xxxxx 120m 480Mi
처음엔 limit을 잘못 계산한 줄 알았는데, 30분 추적해도 kubectl top 기준으론 한 번도 1Gi 근처 안 갑니다. 근데 왜 OOMKilled가 나나요? 클러스터는 cgroup v2 사용 중이고 K8s 1.30입니다.
댓글 3
정확히 같은 증상으로 한참 삽질하던 분들이 많은 케이스입니다.
kubectl top은 진실이 아닙니다. 이유가 몇 가지 있습니다.1) kubectl top은 샘플링 기반 (평균값)
metrics-server가 기본 15초 간격으로 cAdvisor에서 평균을 가져옵니다. 순간 스파이크는 거의 확실히 놓칩니다. 반면 cgroup OOM killer는 1ms 단위로 반응합니다.
2) 진짜로 봐야 하는 건 memory.peak
cgroup v2에서 해당 Pod의 실제 최대 메모리 사용량을 보려면 컨테이너 안에서 이 파일을 읽으세요.
이 값이 limit에 근접하면 "샘플링이 스파이크를 놓쳤다"가 확정입니다.
3) Working Set vs RSS vs Cache
OOM killer가 참고하는 값은 working_set = anon + unevictable입니다.
kubectl top이 보여주는 것도 working_set이지만 샘플링 때문에 차이가 남. page cache(=file cache)는 OOM 회계에서 제외되지만 튜닝상 영향 줍니다.4) 모니터링 권장
container_memory_working_set_bytes를 scrape 간격 15초 이하로 (1초 권장)max_over_time(... [1m])로 스파이크 시각화5) 언어별 함정
-XX:MaxRAMPercentage=75.0권장--max-old-space-size를 limit의 75% 수준으로 (기본 1.5G가 limit보다 크면 바로 OOM)multiprocessingfork한 워커들의 RSS가 별도 누적 (COW이지만 write가 많으면 실질 증가)JVM 워크로드면 90% 확률로 heap 외 영역 누적이에요.
-Xmx만 보고 limit 맞추면 metaspace + DirectByteBuffer + 스레드 스택(1MB × 스레드 수)에서 터집니다. 저는jcmd <pid> VM.native_memory summary로 breakdown 보고 튜닝하는 편입니다.memory.peak체크해보니 정말 limit의 97%까지 순간 스파이크가 있었네요. Prometheus scrape 주기도 기본 60초라 못 잡은 거였고요. requests를 768Mi로 올리고 VPA recommendation 모드로 2주 관찰 들어갑니다. JVM 네이티브 메모리 breakdown은 처음 듣는 커맨드라 실무에 바로 써먹겠습니다. 감사합니다!