{"id":"IL-0246","org":"Adyen","date":"2026-07-08","docs":[],"title":{"en":"New pods on Adyen's big data Kubernetes nodes could not start, failing with \"Cilium API client timeout exceeded\"","ko":"Cilium 에이전트가 엔드포인트를 만들지 못해 빅데이터 쿠버네티스 노드에서 새 파드가 뜨지 못했다"},"domain":["kubernetes","networking"],"impact":{"scope":"New pods on affected nodes of Adyen's production big data Kubernetes clusters could not start; no SLO was breached and payment processing was not affected","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Scaling parameters can interact with other logic months later as traffic grows: the larger table that solved capacity limits on 512GB nodes interacted with the garbage collection auto-scaling on the 2TB nodes. Logs only showed symptoms, so profiling and tracing across the container runtime, the CNI plugin, the Cilium agent and the kernel were needed, and simple arithmetic with measured numbers pointed at the cause early. Auto-scaling algorithms need bounds, and the 12-hour maximum interval was too long for this workload. A timeout protects the caller but does not cancel the work behind it. Adyen recommends that Cilium deployments with dynamic workloads alert on cilium_datapath_conntrack_gc_duration_seconds and cilium_datapath_conntrack_gc_entries and set conntrackGCInterval: 60s.","ko":"규모에 맞춰 바꾼 값은 트래픽이 늘면서 몇 달 뒤에야 전체 영향을 드러낼 수 있다. 512GB 노드를 위해 바꾼 bpf_map_dynamic_size_ratio 는 처음 문제를 풀었지만, 2TB 노드에서 커진 테이블이 가비지 컬렉션 주기 자동 조정과 맞물렸다. 로그는 증상만 보여 줬고, 원인은 프로파일링과 추적으로 컨테이너 런타임, CNI 플러그인, Cilium 에이전트, 리눅스 커널까지 훑어야 보였다. 실제 숫자로 어림셈을 하자 전체 흐름을 이해하기 전에 원인을 짚을 수 있었다.\n\n자동 조정 알고리즘에는 한계가 있어야 한다. Cilium 의 주기 자동 조정은 오래 연결이 적다가 파드 하나로 연결이 크게 늘어나는 노드나, 5% 가 아주 큰 수가 되는 큰 테이블을 고려하지 않았다. 최대 12시간이라는 주기도 이 작업에는 너무 길었다. 타임아웃은 호출한 쪽만 보호할 뿐 작업을 멈추지 않는다. 필요하면 작업을 취소하는 처리를 명시적으로 넣어야 한다. 연결 추적 상태는 핵심 운영 지표로 다뤄야 하고, 한가한 시간의 CPU 절약을 위해 바쁜 시간의 파드 생성 타임아웃을 감수해서는 안 된다."},"related":[],"sources":[{"url":"https://adyen.com/knowledge-hub/inside-cilium-cni-solving-kubernetes-pod-setup-timeouts","kind":"blog","archived":null}],"summary":{"en":"Adyen runs Cilium across more than 100 Kubernetes clusters. Its big data clusters mix very different pods: Trino pods that open a new connection to one of more than 500 HDFS datanodes for each file, up to about 50,000 connections a minute per pod at peak, Spark batch pods that live from a second to a few hours, and CPU-heavy pods with few connections, on machines with 64 cores and 512GB of RAM or 128 cores and 2TB of RAM. The clusters ran Kubernetes 1.31.7 and Cilium 1.16.5. Months earlier Adyen had set cilium_bpf_map_dynamic_size_ratio to 0.0050 in its Helm chart so that connection tracking capacity would grow with memory, which gave the 2TB nodes a TCP connection tracking table of 16 million entries, twice the default of eight million for that amount of memory.\n\nWhen an endpoint is created for a new pod, the Cilium agent runs scrubIPsInConntrackTable, which walks the whole connection tracking table to remove entries left for the pod's IP by an earlier pod. The walk takes two BPF syscalls per entry and cannot run in parallel, and on these nodes it reached about 200,000 entries a second, so a table of 7 million entries took 35 seconds and a full table of 16 million would take 80 seconds, against a CNI timeout of 90 seconds. The table held so many entries because the periodic garbage collection of expired entries had almost stopped. Cilium lengthens the interval by 1.5 times whenever a run deletes less than 5% of the entries, up to 12 hours, and on a 16-million-entry table 5% is more than 800,000 entries. A node that first ran light workloads drifted to the 12-hour interval, and when heavy data workloads arrived, expired entries piled up until the next run.\n\nA global mutex on the connection tracking table made concurrent pod starts worse: each pod waited for the scan of the one before it, so the second pod's timer ran out and the rest had no chance. A timed-out create did not stop the agent's work; containerd immediately called DeleteEndpoint(), which also walks the table, and the kubelet's podWorkerLoop retried every 60 to 90 seconds, so create and delete requests queued up faster than they were served. Adyen fixed the interval with conntrackGCInterval: 60s, applied at 9:00 am and rolled out to the DaemonSet by 10:00 am. The table size dropped and stayed stable, endpoint API call durations returned to normal, and the timeout has not appeared since. The clusters serve only analytics and big data, the issue was resolved before any SLO was breached, and real-time payment processing was not affected.","ko":"Adyen 은 100개가 넘는 쿠버네티스 클러스터 모두에서 Cilium CNI 를 쓴다. 운영 빅데이터 클러스터는 Kubernetes 1.31.7 과 Cilium 1.16.5 로 돌았고, 노드는 물리 코어 64개와 RAM 512GB, 또는 물리 코어 128개와 RAM 2TB 였다. 500개가 넘는 HDFS datanode 에서 파일을 하나 받을 때마다 새 연결이 생겨, Trino 파드 하나가 붐비는 시간에 1분에 약 50,000개 연결을 만들었다. Spark 같은 배치 작업 파드는 1초에서 몇 시간까지 살다 사라졌다. 팀은 몇 달 전 512GB 노드의 연결 추적 고갈을 막으려고 Helm 차트에 cilium_bpf_map_dynamic_size_ratio: 0.0050 을 넣었고, 2TB 노드의 TCP 연결 추적 테이블 최대 크기는 기본값 800만의 두 배인 16,777,216 개가 됐다.\n\nCilium 은 엔드포인트를 만들 때 scrubIPsInConntrackTable 함수로 연결 추적 테이블 전체를 훑어, 그 파드의 IP 에 이전 연결의 흔적이 남지 않았는지 확인하고 지운다. 팀은 pprof 실행 추적에서 에이전트가 이 함수 안에서 nextKey() 와 lookup() BPF 시스템 콜을 쉼 없이 번갈아 부르는 것을 봤다. 35밀리초 구간에서 시스템 콜 14,916번을 세었고, 초당 약 213,000개 항목을 훑는 속도였다. 이 작업은 앞 키가 있어야 다음 키를 얻으므로 병렬화할 수 없었다. cilium bpf ct list global 로 본 테이블에는 약 700만 개 항목이 있었다. 대부분 이미 만료된 항목이었고, 몇 시간 전에 만료된 것도 있었다. 700만 개를 훑는 데 35초, 꽉 찬 1,600만 개를 훑는 데는 80초까지 걸릴 수 있어 CNI 타임아웃 90초에 가까웠다.\n\n만료된 항목이 남은 까닭은 가비지 컬렉션 주기의 자동 조정이었다. Cilium 의 주기적 가비지 컬렉션은 한 번에 지운 항목이 5% 보다 적으면 주기를 1.5배로 늘리고 25% 보다 많으면 줄인다. 주기는 5분에서 시작해 최대 12시간까지 늘어난다. 1,600만 개 테이블에서 5% 는 800,000개가 넘는다. 연결이 적던 노드에서 주기가 12시간까지 늘어난 뒤 무거운 데이터 작업이 올라오면, 다음 가비지 컬렉션까지 만료된 항목이 쌓였다. 연결 추적 테이블에는 전역 mutex 가 있었다. 그래서 파드 50개가 한꺼번에 뜨면 하나씩 차례로 80초 동안 테이블을 훑었고, 뒤의 파드들은 기다리다 90초 타임아웃을 냈다. 타임아웃 뒤에도 작업은 멈추지 않았고, 삭제 요청과 kubelet 의 재시도가 더해져 대기열이 줄지 않았다. 팀은 conntrackGCInterval: 60s 로 주기를 고정했고, 오전 9:00 에 적용해 10:00 에 DaemonSet 배포를 마쳤다. 그 뒤 연결 추적 테이블 크기는 크게 줄어 안정됐고, 타임아웃 오류는 다시 나지 않았다. SLO 를 어기기 전에 해결했다. 빅데이터 환경은 결제 처리와 분리돼 있어 실시간 결제와 가맹점 거래에는 영향이 없었다."},"trigger":{"en":"Heavy data workloads landed on nodes where the interval of Cilium's periodic garbage collection of expired connection tracking entries had grown, during a quiet period, toward its 12-hour maximum.","ko":"몇 달 전 Helm 차트에 cilium_bpf_map_dynamic_size_ratio: 0.0050 을 설정해, 2TB RAM 노드의 TCP 연결 추적 테이블 최대 크기가 기본값의 두 배인 16,777,216 개가 됐다. 이 큰 테이블이 연결 추적 가비지 컬렉션의 주기 자동 조정과 맞물려, 연결이 적던 노드에 무거운 데이터 작업이 올라오면 만료된 항목이 몇 시간 동안 쌓였다."},"severity":"unknown","symptoms":["pod-stuck-container-creating"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":null,"components":["cilium"],"root_cause":"config-change","remediation":[{"text":{"en":"Set a fixed connection tracking garbage collection interval, conntrackGCInterval: 60s, and rolled it out to the Cilium DaemonSet","ko":"Cilium 설정에 conntrackGCInterval: 60s 를 넣어 가비지 컬렉션 주기가 자동으로 늘어나지 않게 고정했다. 오전 9:00 에 적용해 10:00 에 DaemonSet 배포를 마쳤고, 연결 추적 테이블 크기와 엔드포인트 API 처리 시간이 정상으로 돌아왔다."}}],"action_items":[{"text":{"en":"Alert on the connection tracking garbage collection duration and table size metrics","ko":"워크로드가 동적으로 바뀌는 Cilium 환경에서는 conntrackGCInterval: 60s 설정과 함께, 가비지 컬렉션 시간 cilium_datapath_conntrack_gc_duration_seconds 와 테이블 크기 cilium_datapath_conntrack_gc_entries 에 미리 알림을 건다. 이 장애 때 가비지 컬렉션 시간은 1초에서 80초로 뛰었고 테이블에는 대부분 만료된 항목 700만 개가 있었다."}}],"contributing":["delayed-effect-change","default-config-unchanged","retry-storm","no-alert-on-limit"],"recurrence_of":null,"symptoms_text":{"en":"On Adyen's production big data Kubernetes clusters, new pods on some nodes could not start because setting up their network through Cilium timed out. Once one pod had timed out, every other pod trying to start on the same node timed out as well.\n\nThe pods failed with: Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox \"0fecf4844d3f8f2df218f09d91f9698bb424e2166551952f46fd7638f4757cf2\": plugin type=\"cilium-cni\" failed (add): unable to create endpoint: Cilium API client timeout exceeded. On the affected nodes the duration of the Cilium agent API calls DELETE /v1/endpoint and PUT /v1/endpoint, which add and delete Cilium endpoints, grew linearly over time from 4:20 pm, a sign that endpoint creation and deletion never finished.\n\nThe Cilium agent logs showed no related timeouts, even with debug logging turned on. On a broken node `cilium endpoint list` timed out, `cilium endpoint log <ENDPOINT_ID>` showed the endpoint stuck in the regenerating phase, `kubectl get ciliumendpoint` showed it still regenerating while its Kubernetes manifest showed a ready status, and /var/run/cilium/state held endpoint folders with a _next suffix, meaning they had not completed. This placed the problem in the Cilium agent rather than in the kubelet or the CNI plugin.\n\nExecution traces from pprof, taken right after spawning 50 pods on a broken node, showed goroutines spending all their time in BPF syscalls, nextKey() and lookup() in a tight loop at about 426,171 syscalls a second, inside the function scrubIPsInConntrackTable. `cilium status --verbose` showed a TCP connection tracking map of 16777216 entries, and `cilium bpf ct list global` listed about 7 million entries, most of them already expired, some by several hours. Garbage collection metrics showed frequent runs that deleted almost nothing. A goroutine dump taken with gops showed 57 goroutines in createEndpoint() waiting on an RWMutex and 56 in scrubIPsInConntrackTable() waiting for a lock, and the metric cilium_datapath_conntrack_gc_duration_seconds had risen from 1s to 80s.","ko":"Adyen 의 운영 빅데이터 쿠버네티스 클러스터에서 새 파드가 네트워크 설정 단계에서 타임아웃으로 실패했다. 한 파드가 실패하면 같은 노드에 뜨려는 다른 파드도 모두 타임아웃을 냈다.\n\n파드 이벤트에는 `Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox \"0fecf4844d3f8f2df218f09d91f9698bb424e2166551952f46fd7638f4757cf2\": plugin type=\"cilium-cni\" failed (add): unable to create endpoint: Cilium API client timeout exceeded` 가 찍혔다.\n\n영향받은 노드에서는 Cilium 엔드포인트를 지우고 더하는 DELETE /v1/endpoint 와 PUT /v1/endpoint 의 API 처리 시간도 늘었다. 16:20 부터 엔드포인트 호출 지연이 시간이 갈수록 선형으로 늘었고, 엔드포인트 생성과 삭제가 끝나지 않았다.\n\nCilium 에이전트 로그에는 관련된 타임아웃이 없었고, 디버그 로그를 켜도 오류가 보이지 않았다. 고장 난 노드에서 cilium endpoint list 는 타임아웃을 냈다. cilium endpoint log <ENDPOINT_ID> 는 동작했고 엔드포인트가 regenerating 단계에 멈춰 있다고 보여 줬다. kubectl get ciliumendpoint 는 여전히 regenerating 이라고 나왔지만, 엔드포인트의 쿠버네티스 매니페스트는 ready 였다. /var/run/cilium/state 에는 이름이 _next 로 끝나는 엔드포인트 폴더가 남아 있었다. 팀은 이것으로 문제가 kubelet 이나 CNI 플러그인이 아니라 Cilium 에이전트 안에 있다고 확인했다.\n\n여러 파드가 한꺼번에 뜨면 문제가 커졌다. CNI 가 90초 뒤 타임아웃을 내도 에이전트는 작업을 멈추지 않았고, containerd 가 곧바로 DeleteEndpoint() 를 불러 삭제 요청까지 쌓였다. kubelet 이 60~90초 뒤 다시 시도할 때마다 생성과 삭제 요청이 대기열에 더해졌다. 파드 cilium-node-breaker-5 는 15:54:30 에 첫 생성을 시도했고, 15:56:00 에 타임아웃으로 삭제된 뒤 15:57:12, 15:59:57, 16:02:39 에 다시 생성을 시도했다."},"updated_at":"2026-11-14T12:33:49.401846Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0246","org_slug":"adyen"}