IL-0195 Palark · 2023-06-07 (게시일)kubernetes
Palark 가 맡은 고객 클러스터에서 kube-apiserver 가 메모리를 다 써 버리고 죽기를 반복했고, 프로덕션 환경을 포함한 클러스터 전체에서 쿠버네티스 API 가 멈췄다. 팀은 먼저 컨트롤 플레인 노드에 CPU 와 메모리를 더했다. 처음 더한 양으로는 모자랐고, 한 번 더 늘린 뒤에야 API 가 안정됐다. 노드는 CPU 8개, 메모리 16 GB 에서 CPU 16개, 메모리 64 GB 로 커졌다.
원인은 Cilium 파드가 API 에 LIST 요청을 대량으로 보낸 것이었다. 클러스터에는 노드가 200개 넘게 있었고, 동시에 들어오는 요청이 API 서버의 메모리 사용량을 50 GB 까지 끌어올렸다. 부하가 API 서버 하나에 몰려 그 서버가 죽으면 요청이 다른 서버로 넘어가 같은 일이 반복됐다.
팀은 cilium-agent 가 API 에 동시에 보내는 요청 수를 제한하기로 했다. LIST 요청이 조금 느려져도 Cilium 동작에는 영향이 없다고 보았다. 쿠버네티스 1.20 부터 기본으로 켜져 있는 API Priority and Fairness 를 썼다. 이 기능은 API 서버로 들어오는 요청을 우선순위 수준으로 나누고, 수준마다 동시 처리 한도와 대기열을 둔다. 팀은 d8-cni-cilium 서비스 어카운트 그룹이 cilium.io API 그룹에 보내는 list 요청을 cilium-pods 라는 FlowSchema 로 묶었다. 이 요청은 assuredConcurrencyShares 5, 큐 16개, handSize 4, queueLengthLimit 50 으로 제한한 cilium-pods PriorityLevelConfiguration 이 맡아 처리하게 했다. 적용한 뒤에는 cilium-agent 를 재시작해도 메모리 사용량이 크게 변하지 않았고, 팀은 노드 자원을 원래 크기로 줄였다.
어느 평범한 아침, 기술 지원 엔지니어가 전화로 고객 클러스터가 프로덕션 환경까지 포함해 사실상 동작하지 않는다고 알려 왔다. 클러스터에 접속해 보니 kube-apiserver 가 메모리를 전부 써 버린 상태였다.
kube-apiserver 는 죽고, 재시작하고, 다시 죽기를 계속 반복했다. 그 결과 쿠버네티스 API 에 아예 접근할 수 없었다. 메모리 사용량 그래프는 50 GB 까지 올라갔다. 컨트롤 플레인 노드는 원래 CPU 8개, 메모리 16 GB 였다.
고객과 합의한 시험 시간에 cilium-agent 를 재시작하자 같은 흐름이 재현됐다. API 서버 하나에 부하가 몰렸고, 그 서버가 노드의 메모리를 다 먹은 뒤 죽었다. 그러면 요청이 다른 API 서버로 넘어갔고, 그 서버에서 같은 일이 되풀이됐다.
팀은 Grafana 대시보드에서 API 요청 수가 크게 늘어난 것을 보았다. Prometheus 에서 apiserver_request_total{verb=~"LIST", instance=~".*:.*", resource=~".*"} 로 요청을 인스턴스와 리소스별로 묶어 보니 LIST 요청 상당수가 Cilium 네트워크 정책 쪽에서 나오고 있었다. 감사 로그를 켜 두었기 때문에 문제의 요청을 하나하나 확인할 수도 있었다.
증상 ? 컨트롤 플레인 무응답OOMKilled
원인 ? 용량 한계
기술 지원 엔지니어가 전화로 고객 클러스터가 사실상 동작하지 않는다고 알렸다. 요청을 보낸 쪽을 찾을 때는 Grafana 대시보드에서 API 요청 수가 급증한 것을 먼저 보았고, Prometheus 의 apiserver_request_total 을 인스턴스와 리소스별로 묶어 Cilium 네트워크 정책 쪽 LIST 요청을 찾았다. 감사 로그로 실제 요청도 확인했다.
특정 클라이언트의 요청을 API Priority and Fairness 의 별도 우선순위 수준에 묶어 두면, 그 클라이언트가 요청을 쏟아내도 사용자 요청이 밀리지 않고 API 서버도 버틴다. API 서버 전체의 동시 처리 한도는 --max-requests-inflight 기본값 400 과 --max-mutating-requests-inflight 기본값 200 을 더한 값이다. API 서버는 이 한도를 각 우선순위 수준의 assuredConcurrencyShares 비율대로 나누므로, 한 수준의 값을 올리면 다른 수준의 한도는 줄어든다. 로그 보기나 exec 같은 오래 걸리는 요청과 WATCH 요청은 이 한도에 걸리지 않는다. 큐 개수, queueLengthLimit, handSize 는 요청이 너무 느려지지 않으면서 갑작스러운 폭증에도 API 서버가 넘치지 않도록 실험으로 정해야 한다. 지켜볼 지표로는 apiserver_flowcontrol_rejected_requests_total, apiserver_current_inqueue_requests, apiserver_flowcontrol_request_execution_seconds 가 있다. Palark 는 이런 일을 이번 말고도 여러 번 겪었다.
같은 원인 · 공통 쿠버네티스 API 서버
같은 원인 · 공통 쿠버네티스 API 서버
같은 원인 · 공통 쿠버네티스 API 서버
같은 원인 · 공통 쿠버네티스 API 서버
같은 원인 · 공통 쿠버네티스 API 서버
같은 원인 · 공통 쿠버네티스 API 서버
같은 원인 · 공통 쿠버네티스 API 서버
같은 원인 · 공통 쿠버네티스 API 서버