{"id":"IL-0176","org":"LINE","date":"2020-07-10","docs":["https://github.com/torvalds/linux/blob/v3.10/fs/proc/base.c#L439","https://github.com/torvalds/linux/blob/v3.10/mm/oom_kill.c#L141"],"title":{"en":"Prometheus in a 235-node Kubernetes cluster crash-looped because over two million series exhausted host memory and the kernel OOM killer chose it first, as Burstable QoS gave it oom_score_adj 999","ko":"시계열 급증으로 호스트 메모리가 바닥나자 oom_score_adj 999 를 받은 Prometheus 컨테이너가 커널 OOM Killer 에게 먼저 죽어 CrashLoopBackOff 에 빠졌다."},"domain":["kubernetes","observability"],"impact":{"scope":"The cluster's Prometheus monitoring server; its dashboard could not be reached","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Exit code 137 together with an OOM event in the kubelet log means the kernel killed the container, not kubelet. Under node memory pressure the kernel kills the process with the highest score, and kubelet derives each container's oom_score_adj from the pod's QoS class: a container without a memory request in a pod where another container has one ends up Burstable with a score near 1000, so it is killed first. In a large cluster, Prometheus memory needs to be estimated from the expected series count and requested explicitly.","ko":"원인은 둘이었다. 첫째, Prometheus 파드의 requests 와 limits 를 잘못 잡아 Prometheus 서버 컨테이너가 높은 oom_score_adj 를 받았다. 둘째, 대규모 클러스터에서 Prometheus 가 쓸 메모리를 미리 예측하지 않았다. 쿠버네티스에서 파드의 QoS 클래스는 메모리가 모자랄 때 어느 컨테이너가 먼저 죽는지에 영향을 준다. 커널의 OOM Killer 는 메모리 사용량과 oom_score_adj 로 점수를 매기고, kubelet 은 QoS 클래스에 따라 oom_score_adj 를 정한다."},"related":[],"sources":[{"url":"https://engineering.linecorp.com/ko/blog/prometheus-container-kubernetes-cluster","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"The cluster ran Kubernetes 1.13.5 with 235 nodes, 7 control plane nodes and 5 etcd nodes plus worker nodes, all of them VMs. Prometheus 2.14.0 ran with storage.tsdb.retention.size=25GB on a host with 4 vCPUs, 8GB of memory and a 100GB SSD. Exit code 137 means the process received SIGKILL. The author ruled out a manual kill, since only the author had root on the host, and traced through the kubelet and cAdvisor code that kubelet only reports OOM events that cAdvisor reads from /dev/kmsg; so the kernel OOM killer, not kubelet, had killed the container. Prometheus loaded more than two million series within minutes of starting, which needed more memory than the host had.\n\nThe author then looked at why the kernel chose Prometheus. The OOM killer in the kernel in use (3.10.0-957.el7) scores processes by memory use adjusted by oom_score_adj, and the Prometheus container's oom_score_adj was 999. A SystemTap script watching writes to /proc/<pid>/oom_score_adj showed no process changing it at run time. The containerd and kubelet code showed that kubelet sets the value when the container is created, from the pod's QoS class: -998 for Guaranteed, 1000 for BestEffort, and for Burstable 1000 minus 1000 times the memory request divided by node memory. In the Prometheus pod only the configmap-reload container had requests and limits (memory 50Mi and 100Mi, CPU 100m and 200m); the Prometheus container had none. The pod was therefore Burstable, and the Prometheus container got 999, which made it the first process to be killed.\n\nThe author names two causes: wrong resource requests and limits on the Prometheus pod, which gave the container a high oom_score_adj, and no advance estimate of how much memory Prometheus needs in a large cluster. Removing most of the unnecessary settings from the Prometheus ConfigMap stopped the crashes.","ko":"클러스터는 쿠버네티스 1.13.5 에 노드 235개, 컨트롤 플레인 노드 7개, etcd 노드 5개로 이루어져 있었고 모든 노드가 VM 이었다. Prometheus 2.14.0 서버는 vCPU 4개, 메모리 8GB, 스와프를 끈 호스트에서 돌았다. 이 Prometheus 파드가 CrashLoopBackOff 에 빠져 950번 재시작했다. 종료 코드 137 은 프로세스가 SIGKILL 을 받았다는 뜻이다.\n\nkubelet 로그의 OOM 이벤트는 cAdvisor 가 /dev/kmsg 에서 커널의 OOM 메시지를 읽어 kubelet 의 oom_watcher 로 넘긴 것이었다. 컨테이너를 죽인 것은 kubelet 이 아니라 커널의 OOM Killer 였다. 시계열 수로 추정한 메모리 사용량은 5.7GB 로, 시작하고 5분 만에 8GB 호스트가 감당하기 어려운 수준이 됐다. docker restart 로 재현해 보니 컨테이너가 다시 뜬 뒤 호스트 메모리를 거의 다 쓰고 OOM 으로 죽었다.\n\nOOM Killer 는 oom_score 가 가장 높은 프로세스를 고르는데, Prometheus 컨테이너의 oom_score_adj 는 999 였다. SystemTap 으로 지켜보니 실행 중에 이 값을 바꾸는 프로세스는 없었고, 커널은 값을 지정하지 않은 프로세스에 0 을 줬다. 값은 kubelet 이 CRI 요청에 담아 containerd 에 넘긴 것이었다. 파드 매니페스트에서 configmap-reload 컨테이너에만 requests 와 limits 가 있어 파드는 Burstable QoS 로 분류됐다. kubelet 은 Burstable 컨테이너의 oom_score_adj 를 1000 에서 메모리 요청 비율을 빼서 계산한다. 메모리 요청이 없는 Prometheus 컨테이너는 계산값이 Best-Effort 와 같은 1000 이 됐고, kubelet 이 여기서 1 을 빼 999 를 줬다. 그래서 메모리가 모자랄 때 가장 먼저 죽는 대상이 됐다. Prometheus configMap 에서 불필요한 설정 대부분을 지우자 컨테이너는 더 이상 비정상 종료되지 않았다."},"trigger":{"en":"After start, the Prometheus time series count rose to 2,162,256 within five minutes and its memory use filled the 8GB host.","ko":"Prometheus 가 시작한 지 5분 만에 시계열 수가 2,162,256 까지 급증해 메모리 8GB 호스트의 메모리를 거의 다 썼다."},"severity":"unknown","symptoms":["crash-loop","oom-killed","monitoring-down","readiness-probe-failures"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"The author found it on a Monday morning when the Prometheus dashboard, opened to check the cluster for alerts, could not be reached.","ko":"월요일 아침 엔지니어가 클러스터 경고를 확인하려고 Prometheus 대시보드를 열었다가 접속되지 않는 것을 보고 알았다."},"components":["prometheus","kubelet","linux-kernel"],"root_cause":"capacity","remediation":[{"text":{"en":"removed most of the unnecessary settings from the Prometheus ConfigMap, after which the container stopped crashing","ko":"Prometheus configMap 에서 불필요한 설정 대부분을 지워 컨테이너가 더 이상 비정상 종료되지 않게 했다."}}],"action_items":[{"text":{"en":"Set appropriate resource requests and limits on the Prometheus pod","ko":"Prometheus 파드의 requests 와 limits 를 알맞게 고친다. 지금 문제를 완전히 풀지는 못해도 비슷한 문제를 막아 해결 시간을 줄인다."}},{"text":{"en":"Reduce unneeded metrics by adjusting scrape targets or scrape interval","ko":"불필요한 수집 지표를 줄이도록 수집 대상이나 수집 간격을 조정한다."}},{"text":{"en":"Estimate Prometheus memory needs, provide dedicated hosts with a node label such as deploy-topology: Prometheus, and pin the Prometheus pod to them with a nodeSelector","ko":"Prometheus 에 필요한 메모리를 예측해 새 호스트를 마련하고 deploy-topology: Prometheus 같은 노드 레이블을 붙인 뒤, 매니페스트에 nodeSelector 를 넣어 Prometheus 파드가 그 호스트에만 배치되게 한다."}}],"contributing":["no-resource-limits","no-capacity-planning"],"recurrence_of":null,"symptoms_text":{"en":"One Monday morning the author opened the Prometheus dashboard to check a Kubernetes cluster for alerts and could not reach it. The Prometheus pod was in CrashLoopBackOff: kubectl get pods showed it 1/2 ready with 950 restarts over 3d20h on worker3.\n\nkubectl describe showed the Prometheus container (prom/Prometheus:v2.14.0) Waiting with Reason CrashLoopBackOff, its last state Terminated with Reason Error and Exit Code 137, while the configmap-reload sidecar was running normally. Because Prometheus had crashed on full disks before, the author checked storage first: the 30Gi PVC was only 35% used (11G of 32G). The Prometheus log showed no error; its last lines were the start of TSDB and the WAL replay (\"replaying WAL, this may take awhile\", \"WAL segment loaded\" segment=0 maxSegment=12).\n\nThe kubelet log on the host showed the readiness probe failing with \"HTTP probe failed with statuscode: 503\", then \"Got sys oom event from cadvisor\", then the liveness probe failing with \"read: connection reset by peer\" and a PLEG ContainerDied event. Restarting the container with docker restart reproduced it: Prometheus used almost all of the host's memory and the kernel killed it.\n\nThe Grafana dashboard for Prometheus showed the number of time series jumping to 2,162,256 within five minutes of start. A memory calculator put that at an estimated 5.7GB, on a host with 8GB of memory and swap disabled.","ko":"어느 월요일 아침, 엔지니어가 쿠버네티스 클러스터 경고를 보려고 Prometheus 대시보드를 열었는데 접속되지 않았다. Prometheus 서버 파드는 CrashLoopBackOff 상태였다.\n\nkubectl get pods 출력에는 cluster-monitoring-Prometheus-d56bb6d46-tzmcj 1/2 CrashLoopBackOff 950 3d20h <IP> worker3 이 찍혔다. kubectl describe 로 보니 configmap-reload 컨테이너는 Running 이었다. Prometheus 컨테이너는 State: Waiting, Reason: CrashLoopBackOff 였고 마지막 상태는 Reason: Error, Exit Code: 137 이었다.\n\n처음에는 디스크를 의심했다. 예전에 수집 지표가 가용 디스크를 넘어 Prometheus 가 비정상 종료된 적이 있었기 때문이다. 하지만 30Gi PVC 의 사용률은 35% 로 넉넉했다. Prometheus 로그에도 눈에 띄는 오류는 없었다. 마지막 로그는 \"replaying WAL, this may take awhile\" 와 \"WAL segment loaded\" 였고, WAL 을 메모리로 리플레이하던 중에 죽은 것이었다.\n\n호스트의 kubelet 로그에는 Readiness probe 실패 \"HTTP probe failed with statuscode: 503\", \"Got sys oom event from cadvisor\", Liveness probe 실패 \"read: connection reset by peer\", 그리고 ContainerDied 이벤트가 차례로 찍혔다. Grafana 대시보드를 보니 Prometheus 가 시작하고 5분 만에 시계열 수가 2,162,256 까지 뛰었다."},"updated_at":"2026-11-10T00:00:37.381057Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0176","org_slug":"line"}