IL-0176 LINE · 2020-07-10 (게시일)kubernetesobservability
클러스터는 쿠버네티스 1.13.5 에 노드 235개, 컨트롤 플레인 노드 7개, etcd 노드 5개로 이루어져 있었고 모든 노드가 VM 이었다. Prometheus 2.14.0 서버는 vCPU 4개, 메모리 8GB, 스와프를 끈 호스트에서 돌았다. 이 Prometheus 파드가 CrashLoopBackOff 에 빠져 950번 재시작했다. 종료 코드 137 은 프로세스가 SIGKILL 을 받았다는 뜻이다.
kubelet 로그의 OOM 이벤트는 cAdvisor 가 /dev/kmsg 에서 커널의 OOM 메시지를 읽어 kubelet 의 oom_watcher 로 넘긴 것이었다. 컨테이너를 죽인 것은 kubelet 이 아니라 커널의 OOM Killer 였다. 시계열 수로 추정한 메모리 사용량은 5.7GB 로, 시작하고 5분 만에 8GB 호스트가 감당하기 어려운 수준이 됐다. docker restart 로 재현해 보니 컨테이너가 다시 뜬 뒤 호스트 메모리를 거의 다 쓰고 OOM 으로 죽었다.
OOM 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 에서 불필요한 설정 대부분을 지우자 컨테이너는 더 이상 비정상 종료되지 않았다.
어느 월요일 아침, 엔지니어가 쿠버네티스 클러스터 경고를 보려고 Prometheus 대시보드를 열었는데 접속되지 않았다. Prometheus 서버 파드는 CrashLoopBackOff 상태였다.
kubectl 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 이었다.
처음에는 디스크를 의심했다. 예전에 수집 지표가 가용 디스크를 넘어 Prometheus 가 비정상 종료된 적이 있었기 때문이다. 하지만 30Gi PVC 의 사용률은 35% 로 넉넉했다. Prometheus 로그에도 눈에 띄는 오류는 없었다. 마지막 로그는 "replaying WAL, this may take awhile" 와 "WAL segment loaded" 였고, WAL 을 메모리로 리플레이하던 중에 죽은 것이었다.
호스트의 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 까지 뛰었다.
원인 ? 용량 한계
월요일 아침 엔지니어가 클러스터 경고를 확인하려고 Prometheus 대시보드를 열었다가 접속되지 않는 것을 보고 알았다.
원인은 둘이었다. 첫째, Prometheus 파드의 requests 와 limits 를 잘못 잡아 Prometheus 서버 컨테이너가 높은 oom_score_adj 를 받았다. 둘째, 대규모 클러스터에서 Prometheus 가 쓸 메모리를 미리 예측하지 않았다. 쿠버네티스에서 파드의 QoS 클래스는 메모리가 모자랄 때 어느 컨테이너가 먼저 죽는지에 영향을 준다. 커널의 OOM Killer 는 메모리 사용량과 oom_score_adj 로 점수를 매기고, kubelet 은 QoS 클래스에 따라 oom_score_adj 를 정한다.
같은 원인 · 공통 kubelet
같은 원인 · 공통 kubelet
같은 원인 · 공통 Prometheus
같은 원인 · 공통 Prometheus
같은 원인 · 공통 kubelet
같은 원인 · 공통 Prometheus
같은 원인 · 공통 Prometheus
같은 원인 · 공통 Prometheus
같은 원인 · 공통 kubelet
같은 원인 · 공통 리눅스 커널