Kubernetes APF vyhladovanie: keď jeden controller zablokuje kubectl
Symptómy sú zradné, lebo na prvý pohľad to vyzerá ako “Kubernetes je dnes divný”:
kubectl get podsvisí alebo timeoutuje.- Rollouty sa zaseknú (“waiting for rollout” donekonečna).
- Controllery začnú logovať 429 a agresívne retryovať.
- Nody vyzerajú OK, etcd vyzerá OK a CPU na API serveri nie je na 100%.
Toto som v produkcii riešil viackrát. Root cause nebol etcd ani “pomalý apiserver”. Bol to API Priority and Fairness (APF), ktoré robilo presne to, čo má — len naša konfigurácia spôsobila, že kritický control-plane traffic súťažil s hlučným controllerom.
Testované na: Kubernetes 1.29–1.31, managed control plane aj self-hosted kube-apiserver, Prometheus scrape apiserver metrík.
Prečo je to v roku 2026 častejšie
APF už nie je exotika. Multi-tenant clustre, GitOps, operátory a “všetko je controller” znamená, že API load je permanentne pod tlakom. Keď APF nesprávne izoluje traffic, dostanete failure mode, ktorý vyzerá ako náhodná flaky infra — až kým nepozriete APF metriky.
Incident (anonymizovaný)
Nasadil som interný controller (CRD) a mal som bug:
- robil cluster-wide LIST na pody
- robil cluster-wide LIST na secrety
- v každom reconcile loope
- a pri chybe retryoval agresívne
Súčasne sme mali custom FlowSchema pre “platform automation”, ktoré matchovalo väčšinu service accountov a mapovalo ich do jedného priority levelu s nízkymi shares.
Blast radius:
- GitOps driftoval
- HPA prestalo reagovať
kubectlbolo nespoľahlivé pre všetkých- pár workloadov malo restarty, lebo controllery nedokázali updateovať objekty
Constraint:
- nemohol som “len zväčšiť apiserver” (managed control plane).
- potreboval som mitigáciu v clustri: izolovať hlučný controller a ochrániť systémový traffic.
Timeline (čo som reálne spravil)
- T-0: report: “kubectl visí”, rollouty stoja.
- T+5m: vidím vlnu 429 v logoch controllerov a GitOps retry.
- T+10m: grafujem APF rejections a vidím step change po mojom deployi.
- T+20m: nájdem FlowSchema, ktoré sa správa ako catch-all pre service accounty.
- T+30m: scale down hlučného controllera (okamžitá úľava).
- T+45m: pridám FlowSchema + PriorityLevelConfiguration na izoláciu.
- T+60m: rejections padnú, control plane sa stabilizuje, kubectl funguje.
Mechanizmus: ako vznikne APF vyhladovanie
APF je “traffic router” + limit na concurrency
APF matchuje request do FlowSchema, ktorá ho namapuje na PriorityLevelConfiguration.
Priority level kontroluje:
- koľko “seats” (concurrency) flow dostane (
assuredConcurrencyShares) - či sa requesty queueujú alebo rejectujú pri overload (Queuing vs Reject)
Vyhladovanie vznikne, keď “všetko matchuje rovnaký flow”
Najčastejšia chyba nie je “APF je broken”.
Je to:
- FlowSchema matchuje viac trafficu, než ste chceli (typicky
system:authenticated) - FlowSchema má vyššiu precedence, než si myslíte
- kritický a bulk traffic skončí v jednom priority bucket-e
Keď sa bucket nasýti, klienti vidia:
- queued latency (kubectl visí)
- 429 rejections (controllery retryujú → ešte viac loadu)
- nestabilné watch/list správanie (viac retry, viac LISTov)
Prečo retry spraví z incidentu špirálu
429 je “slušná” chyba. Veľa controllerov to berie ako transient a retryuje tvrdo. Vznikne loop:
- APF rejectne (429)
- controller retryuje → vyšší request rate
- APF rejectne viac
- control plane je nepoužiteľný pre všetko, čo zdieľa rovnaký priority level
Runbook: rýchla diagnostika APF starvation
Čo skontrolovať ako prvé
- Potvrdiť 429 a timeouty v čase
- logy controllerov (GitOps, operátory, custom controllery)
- Pozrieť APF rejection metriky Ak scrapujete kube-apiserver metriky, toto je najrýchlejší signál.
PromQL príklady (mená/labely sa môžu jemne líšiť podľa distribúcie):
# Celkové APF rejections
sum(rate(apiserver_flowcontrol_rejected_requests_total[5m]))
# Rejections podľa priority levelu
sum by (priority_level) (rate(apiserver_flowcontrol_rejected_requests_total[5m]))
# Rejections podľa FlowSchema
topk(10, sum by (flow_schema) (rate(apiserver_flowcontrol_rejected_requests_total[5m])))
- Skontrolovať FlowSchema a precedence Mňa už párkrát zabil “catch-all” FlowSchema, ktoré matchovalo viac než sme chceli.
kubectl get flowschemas.flowcontrol.apiserver.k8s.io
kubectl get prioritylevelconfigurations.flowcontrol.apiserver.k8s.io
A potom:
kubectl get flowschemas.flowcontrol.apiserver.k8s.io -o yaml | grep -n "matchingPrecedence" -n
Hľadám:
- nízke
matchingPrecedence(vyššia priorita) - broad match na
system:authenticated - pravidlá, ktoré omylom pokrývajú “všetko”
Ako potvrdiť hypotézu
A. Nájsť FlowSchema / priority level pod tlakom
Z metrík si vyberiem top flow_schema alebo priority_level a otvorím ho:
kubectl get flowschema <name> -o yaml
kubectl get prioritylevelconfiguration <name> -o yaml
Otázky:
- kto matchuje FlowSchema? (service accounts, groups, namespace)
- je tam môj controller?
- je tam queueing? aký je queue limit?
- koľko shares má ten priority level?
B. Korelovať s konkrétnym hlučným klientom U mňa to bol nový controller. Ak nie je jasné, najrýchlejšie je:
- dočasne scale down podozrivé controllery (GitOps, operátory)
- sledovať, či rejections padnú
- prestať, keď nájdete vinníka
Je to hrubé, ale pri incidente to funguje.
Bezpečné mitigácie (v poradí)
- Scale down / pauznúť hlučný controller Najrýchlejšie rozbije retry loop.
kubectl -n <ns> scale deploy/<controller> --replicas=0
-
Znížiť jeho client-side QPS/burst Ak vlastníte kód, je to typicky drobná zmena. Ak nie, hľadajte konfiguráciu/env.
-
Izolovať ho v APF FlowSchema matchujúca iba jeho service account + low-share PriorityLevelConfiguration.
-
Opraviť ochranu systémového trafficu Ak váš APF config náhodne znížil prioritu system flow, opravte precedence a shares.
Rizikové mitigácie
- Vypnúť APF (často nemožné na managed control plane; a viete si vyrobiť tvrdší meltdown).
- Slepo zvyšovať shares všade
- stratíte fairness a môžete odpáliť etcd load.
- Reštartovať controllery ako “fix”
- restart často zvýši list/watch a búrku zhorší.
Čo sme zmenili (konkrétne)
1) Izolácia controllera cez PriorityLevelConfiguration
apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: PriorityLevelConfiguration
metadata:
name: plc-noisy-controller
spec:
type: Limited
limited:
assuredConcurrencyShares: 5
limitResponse:
type: Queuing
queuing:
queues: 16
handSize: 4
queueLengthLimit: 50
2) FlowSchema iba pre konkrétny service account
apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: FlowSchema
metadata:
name: fs-noisy-controller
spec:
matchingPrecedence: 2000
priorityLevelConfiguration:
name: plc-noisy-controller
rules:
- subjects:
- kind: ServiceAccount
serviceAccount:
name: noisy-controller
namespace: platform
resourceRules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
namespaces: ["*"]
clusterScope: true
3) Fix bug + rate limit v controlleri
Skutočný fix bol prestať robiť cluster-wide LIST v tight loope. APF izolácia ale zabezpečila, že jedna bugnutá verzia už nedokáže vyhladovať celý cluster.
Ako verifikovať
- APF rejections padnú
sum(rate(apiserver_flowcontrol_rejected_requests_total[5m]))
- kubectl je znova rýchly
time kubectl get ns >/dev/null
time kubectl get pods -A --request-timeout=10s >/dev/null
- controllery prestanú spamovať 429 GitOps aj kube-controller-manager sa upokoja.
Prevencia / guardrails
- API QPS budgety per controller
- APF pravidlá
- žiadna FlowSchema nesmie matchovať
system:authenticatedbez review - zmeny precedence musia ísť cez diff review
- žiadna FlowSchema nesmie matchovať
- Alerty
- APF rejections > 0 dlhšie než N min
- queue depth non-zero pre kritické priority levels
- Game day
- staging: nasimulovať hlučný controller a dokázať, že cluster ostane použiteľný
Súvisiace čítanie
- etcd Watch Replay Búrky: Keď Obrovské ConfigMapy Zabíjajú Control Plane
- etcd Quota Alarm: Keď Váš Kubernetes Cluster Prejde do Read-Only
- Pod zaseknutý v Terminating: produkčný rozhodovací strom pre finalizery, volume a mŕtve nody
- Kubernetes TLS Certifikát Rotácia: Výpadok o 3:00 Ráno
- Dash Contracts v Go: CI kompilator pre Grafana dashboardy a Prometheus alerty
- Prometheus Kardinalita Explózia: Detekcia, Prevencia a Obnova
- Architectural Linting: Automatizovaná ochrana proti spaghetti kódu
Súvisiace články
ingress-nginx reload búrky: prečo 502 špičky sedia s Ingress churnom
Reloady NGINX Ingressu vedia dropovať keep-alive a robiť 502 špičky pri častých zmenách. Runbook na dôkaz reloadu, zníženie churnu a hardening.
tcpdump vidí SYN, ale služba timeoutuje: pasca listen backlogu
Klienti timeoutujú, tcpdump ukazuje SYN (niekedy aj SYN-ACK), ale aplikácia nič neloguje. Častý vinník: Linux listen/accept fronty, ktoré sa pri load-e alebo CPU starvation preplnia.
etcd Watch Replay Búrky: Keď Obrovské ConfigMapy Zabíjajú Control Plane
Apiserver je 'náhodne pomalý'. Príčina: veľké, často aktualizované ConfigMapy spúšťajú watch compaction, čo spôsobuje simultánny relist tisícov kontrolérov.
Kubernetes OOM Killer: Prečo Kontajner Zomiera pri 50% Pamäte
Kontajner má 4GB memory limit ale OOM kill pri 2GB used. Kernel buffers, page cache a cgroup accounting triky spôsobujú skoré OOMKills. Tu je celý obraz.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.