{"id":"IL-0195","org":"Palark","date":"2023-06-07","docs":[],"title":{"en":"The kube-apiservers of a cluster with more than 200 nodes ran out of memory and crashed one after another under mass LIST requests from Cilium agents","ko":"200개가 넘는 노드의 고객 클러스터에서 Cilium 파드가 LIST 요청을 한꺼번에 쏟아내 kube-apiserver 가 메모리를 다 쓰고 죽기를 되풀이했다."},"domain":["kubernetes"],"impact":{"scope":"The Kubernetes API of the whole cluster, including the production environment","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"In a large cluster, many agents sending LIST requests at once can exhaust kube-apiserver memory, and with several API servers the crash moves from one to the next. APF can hold back such a client: a FlowSchema matches its requests and a PriorityLevelConfiguration gives them a small concurrency share and a queue, so the requests run more slowly instead of all at once.\n\nWith APF enabled, the total concurrency it divides is the sum of --max-requests-inflight (default 400) and --max-mutating-requests-inflight (default 200), shared among priority levels in proportion to their assured concurrency shares, so raising one level's share lowers every other level's limit. Long-running requests such as logs and exec, and WATCH requests, are not subject to APF. The response headers X-Kubernetes-PF-FlowSchema-UID and X-Kubernetes-PF-PriorityLevel-UID show where a request landed, and the metrics Apiserver_flowcontrol_rejected_requests_total, Apiserver_current_inqueue_requests and Apiserver_flowcontrol_request_execution_seconds, with the debug endpoints /debug/api_priority_and_fairness/dump_priority_levels and /debug/api_priority_and_fairness/dump_queues, show how the queues behave.","ko":"특정 클라이언트의 요청을 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 는 이런 일을 이번 말고도 여러 번 겪었다."},"related":[],"sources":[{"url":"https://palark.com/blog/kubernetes-api-flow-control-management/","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"A customer cluster run by Palark, with more than 200 nodes, lost its Kubernetes API when the Cilium agent pods sent LIST requests to the API en masse. Simultaneous LIST requests from so many nodes drove kube-apiserver memory up until one API server used all the memory on its control plane node and crashed; its requests then moved to the next API server, which failed the same way.\n\nTo keep production running, the team first added CPU and memory to the control plane nodes. The first increase was not enough, and the API stabilised only after the next one; in total the nodes went from 8 CPUs and 16 GB of RAM to 16 CPUs and 64 GB.\n\nThe lasting fix used API Priority and Fairness (APF), the API server feature that sorts incoming requests into priority levels, each with its own share of concurrency and its own queues. A FlowSchema named cilium-pods matched LIST requests to the cilium.io API group from the group system:serviceaccounts:d8-cni-cilium, split them into flows by user, and sent them to a PriorityLevelConfiguration of type Limited with assuredConcurrencyShares 5, 16 queues, handSize 4 and queueLengthLimit 50. The team accepted slightly slower LIST responses for Cilium because they would not affect Cilium's work. With the new objects in place, restarting cilium-agent no longer changed memory use significantly, and the control plane nodes went back to their original size.","ko":"Palark 가 맡은 고객 클러스터에서 kube-apiserver 가 메모리를 다 써 버리고 죽기를 반복했고, 프로덕션 환경을 포함한 클러스터 전체에서 쿠버네티스 API 가 멈췄다. 팀은 먼저 컨트롤 플레인 노드에 CPU 와 메모리를 더했다. 처음 더한 양으로는 모자랐고, 한 번 더 늘린 뒤에야 API 가 안정됐다. 노드는 CPU 8개, 메모리 16 GB 에서 CPU 16개, 메모리 64 GB 로 커졌다.\n\n원인은 Cilium 파드가 API 에 LIST 요청을 대량으로 보낸 것이었다. 클러스터에는 노드가 200개 넘게 있었고, 동시에 들어오는 요청이 API 서버의 메모리 사용량을 50 GB 까지 끌어올렸다. 부하가 API 서버 하나에 몰려 그 서버가 죽으면 요청이 다른 서버로 넘어가 같은 일이 반복됐다.\n\n팀은 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 를 재시작해도 메모리 사용량이 크게 변하지 않았고, 팀은 노드 자원을 원래 크기로 줄였다."},"trigger":{"en":"Cilium agent pods on more than 200 nodes sent LIST requests to the Kubernetes API en masse at the same time.","ko":"200개가 넘는 노드에서 Cilium 파드가 쿠버네티스 API 에 LIST 요청을 동시에 대량으로 보냈다."},"severity":"unknown","symptoms":["control-plane-unresponsive","oom-killed"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"A tech support engineer phoned the team to report that the customer's cluster was practically non-operational.","ko":"기술 지원 엔지니어가 전화로 고객 클러스터가 사실상 동작하지 않는다고 알렸다. 요청을 보낸 쪽을 찾을 때는 Grafana 대시보드에서 API 요청 수가 급증한 것을 먼저 보았고, Prometheus 의 apiserver_request_total 을 인스턴스와 리소스별로 묶어 Cilium 네트워크 정책 쪽 LIST 요청을 찾았다. 감사 로그로 실제 요청도 확인했다."},"components":["kube-apiserver","cilium"],"root_cause":"capacity","remediation":[{"text":{"en":"added CPU and memory to the control plane nodes in two steps, from 8 CPUs and 16 GB of RAM to 16 CPUs and 64 GB, until the API stabilised","ko":"컨트롤 플레인 노드에 CPU 와 메모리를 두 차례에 걸쳐 더해 CPU 8개, 메모리 16 GB 에서 CPU 16개, 메모리 64 GB 로 늘렸고, 두 번째 증설 뒤 API 가 안정됐다."}},{"text":{"en":"restarted the Cilium agents in a test window agreed with the customer to reproduce the memory growth","ko":"고객과 합의한 시험 시간에 cilium-agent 를 재시작해 API 서버가 하나씩 메모리를 다 쓰고 죽는 흐름을 확인했다."}},{"text":{"en":"deployed a FlowSchema and a PriorityLevelConfiguration named cilium-pods that limit and queue LIST requests from the Cilium service accounts to cilium.io resources","ko":"d8-cni-cilium 서비스 어카운트의 cilium.io list 요청을 묶는 cilium-pods FlowSchema 와, 이 요청의 동시 처리 수를 제한하는 cilium-pods PriorityLevelConfiguration 을 만들어 클러스터에 배포했다."}},{"text":{"en":"reduced the control plane nodes back to their original resources","ko":"cilium-agent 를 재시작해도 메모리 사용량이 크게 변하지 않는 것을 확인한 뒤 컨트롤 플레인 노드 자원을 원래 크기로 줄였다."}}],"action_items":[{"text":{"en":"Make an API Priority and Fairness configuration a standard part of Palark's Kubernetes platform","ko":"API 요청을 제한해야 했던 일을 여러 번 겪은 뒤, API Priority and Fairness 설정을 Palark 쿠버네티스 플랫폼의 기본 구성으로 넣었다."}}],"contributing":["default-config-unchanged"],"recurrence_of":null,"symptoms_text":{"en":"A tech support engineer called Palark to say that a customer's cluster, production environment included, had practically stopped working. On the control plane nodes the kube-apiserver processes had used up all the memory and were crashing and restarting over and over, so the Kubernetes API was unreachable and completely non-functional.\n\nMemory graphs from the time of the failure showed memory consumption as high as 50 GB. The control plane nodes originally had 8 CPUs and 16 GB of RAM each. In a reply to a reader, the team explains how the source was found: a Grafana dashboard of API request counts showed a large increase, the Prometheus query apiserver_request_total{verb=~\"LIST\", instance=~\".*:.*\", resource=~\".*\"} grouped by instance and resource showed that many LIST requests came from Cilium network policies, and the API audit log showed the individual requests.\n\nIn a test window agreed with the customer, the team restarted the Cilium agents and watched the same sequence repeat: the load on one API server grew, it consumed all the memory on its node and crashed, the requests moved to another API server, and the same thing happened there.","ko":"어느 평범한 아침, 기술 지원 엔지니어가 전화로 고객 클러스터가 프로덕션 환경까지 포함해 사실상 동작하지 않는다고 알려 왔다. 클러스터에 접속해 보니 kube-apiserver 가 메모리를 전부 써 버린 상태였다.\n\nkube-apiserver 는 죽고, 재시작하고, 다시 죽기를 계속 반복했다. 그 결과 쿠버네티스 API 에 아예 접근할 수 없었다. 메모리 사용량 그래프는 50 GB 까지 올라갔다. 컨트롤 플레인 노드는 원래 CPU 8개, 메모리 16 GB 였다.\n\n고객과 합의한 시험 시간에 cilium-agent 를 재시작하자 같은 흐름이 재현됐다. API 서버 하나에 부하가 몰렸고, 그 서버가 노드의 메모리를 다 먹은 뒤 죽었다. 그러면 요청이 다른 API 서버로 넘어갔고, 그 서버에서 같은 일이 되풀이됐다.\n\n팀은 Grafana 대시보드에서 API 요청 수가 크게 늘어난 것을 보았다. Prometheus 에서 apiserver_request_total{verb=~\"LIST\", instance=~\".*:.*\", resource=~\".*\"} 로 요청을 인스턴스와 리소스별로 묶어 보니 LIST 요청 상당수가 Cilium 네트워크 정책 쪽에서 나오고 있었다. 감사 로그를 켜 두었기 때문에 문제의 요청을 하나하나 확인할 수도 있었다."},"updated_at":"2026-11-10T00:00:40.472467Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0195","org_slug":"palark"}