Wat je aan het eind hebt
Een PodDisruptionBudget (PDB) op je workload die voorkomt dat kubectl drain, Cluster Autoscaler scale-down en cloudprovider-nodeupgrades te veel pods tegelijk verwijderen. Je weet welk veld je moet kiezen per workloadtype en hoe je de misconfiguraties vermijdt die drainoperaties op slot zetten.
Vereisten
kubectlverbonden met een Kubernetes 1.27+-cluster (policy/v1PDB API is stabiel sinds 1.21; hetunhealthyPodEvictionPolicy-veld is bruikbaar in productie vanaf 1.27)- Een Deployment of StatefulSet met minimaal 2 replica's (single-replica workloads en PDBs gaan niet samen; zie de sectie over veelgemaakte fouten)
- Bekendheid met rolling updates en zero-downtime deployments
Voluntary vs. involuntary disruptions
PDBs beschermen alleen tegen voluntary disruptions. Dat onderscheid is belangrijk om te snappen voordat je YAML gaat schrijven.
Voluntary disruptions zijn acties waarbij een operator of controller bewust pods verwijdert:
kubectl drainvoor nodeonderhoud- Cluster Autoscaler of Karpenter die onderbenutte nodes consolideert
- Cloudprovider node pool upgrades (AKS, GKE, EKS)
- Handmatig
kubectl delete pod(al gaat dit wel buiten de Eviction API om, dus PDBs worden hierbij overgeslagen)
Involuntary disruptions zijn ongeplande storingen waar Kubernetes geen controle over heeft: hardwarefalen, kernel panics, verdwijnende VM's, spot instance-interruptions, out-of-memory kills. PDBs hebben hier geen zeggenschap over. Een nodecrash die drie pods meeneemt vraagt geen toestemming.
Het subtiele deel: involuntary disruptions tellen wel mee voor het budget. Als een nodefalen je healthy pods al onder desiredHealthy heeft gebracht, wordt een gelijktijdige kubectl drain op een andere node geblokkeerd totdat er vervangende pods draaien.
PDB aanmaken: minAvailable vs. maxUnavailable
Een PDB-spec vereist precies een van twee wederzijds exclusieve velden:
| Veld | Betekenis | Afronding (percentage) |
|---|---|---|
minAvailable |
Minimum pods dat beschikbaar moet blijven na eviction | Rondt omhoog af (conservatief: beschermt meer) |
maxUnavailable |
Maximum pods dat unavailable mag zijn na eviction | Rondt omhoog af (soepeler: staat meer disruptions toe) |
Beide accepteren een integer of een percentagestring zoals "25%".
Stap 1: kies je veld
Voor stateless services (web-API's, workers, microservices) gebruik je maxUnavailable. De officiele Kubernetes-docs raden dit aan omdat het automatisch meeschaalt met veranderingen in replica-aantallen. Statsig beschreef hoe ze overstapten van minAvailable naar maxUnavailable nadat services met minder dan vijf pods permanent op disruptionsAllowed: 0 bleven hangen.
Voor quorum-gebaseerde stateful systemen (etcd, ZooKeeper, Consul) gebruik je minAvailable ingesteld op de quorumgrootte. Een 3-node etcd-cluster heeft minAvailable: 2 nodig (quorum = floor(3/2) + 1 = 2). Het vereiste aantal gezonde members is vast, ongeacht het totale aantal replica's.
Stap 2: schrijf het PDB-manifest
Stateless API met maxUnavailable:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-api-pdb
namespace: production
spec:
maxUnavailable: 1 # een pod tegelijk
unhealthyPodEvictionPolicy: AlwaysAllow # voorkomt CrashLoopBackOff-deadlock (k8s 1.26+)
selector:
matchLabels:
app: web-api
Quorum-gebaseerde StatefulSet met minAvailable:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zookeeper-pdb
namespace: production
spec:
minAvailable: 2 # quorum voor een 3-node ensemble
unhealthyPodEvictionPolicy: IfHealthyBudget # conservatief voor stateful workloads
selector:
matchLabels:
app: zookeeper
Stap 3: toepassen en verifieren
kubectl apply -f pdb.yaml
kubectl get pdb -n production
# NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
# web-api-pdb N/A 1 2 5s
De kolom ALLOWED DISRUPTIONS is het getal dat de Eviction API controleert voor het goedkeuren of afwijzen van een eviction-verzoek. Als er 0 staat, kan geen enkele voluntary disruption doorgaan.
Voor de volledige status:
kubectl get pdb web-api-pdb -n production -o jsonpath='{.status}' | jq .
# {
# "currentHealthy": 3,
# "desiredHealthy": 2,
# "disruptionsAllowed": 1,
# "expectedPods": 3
# }
Unhealthy pods afhandelen met unhealthyPodEvictionPolicy
Voor Kubernetes 1.26 werd een pod die vastzat in CrashLoopBackOff nog steeds beschermd door z'n PDB. De pod was kapot en serveerde geen verkeer, maar het evicten ervan zou het aantal onder desiredHealthy brengen. Het gevolg: kubectl drain hangt oneindig wachtend op een pod die nooit gezond wordt.
Er bestaan twee policies:
IfHealthyBudget(standaard): unhealthy pods mogen alleen ge-evict worden als de applicatie als geheel niet verstoord is (currentHealthy >= desiredHealthy). Conservatief, maar risico op deadlock wanneer alle pods unhealthy zijn.AlwaysAllow: unhealthy pods (zonder Ready-conditie) mogen altijd ge-evict worden, ongeacht het budget. De Kubernetes-docs raden dit nu aan voor de meeste workloads.
Stel AlwaysAllow in, tenzij je een stateful systeem draait waar zelfs een gedeeltelijk kapotte pod bijdraagt aan databeschikbaarheid.
Hoe PDBs samenwerken met kubectl drain
Wanneer je kubectl drain uitvoert, gebeurt het volgende stap voor stap:
- De node wordt gecordoned (gemarkeerd als
Unschedulable). - Voor elke pod stuurt
kubectleen eviction-verzoek naar de Kubernetes Eviction API. - De Eviction API controleert de PDB. Als het evicten van de pod het budget zou schenden, komt er HTTP 429 (Too Many Requests) terug.
kubectl drainprobeert afgewezen verzoeken opnieuw totdat het lukt of de timeout bereikt is.
Als ALLOWED DISRUPTIONS op 0 staat, blokkeert drain oneindig. Cloudproviders hanteren hun eigen timeouts: AKS stopt na 1 uur met UpgradeFailed / PodDrainFailure, GKE force-draint na 1 uur, en EKS faalt de upgrade na 50 minuten.
Controleer voor een clusterupgrade op geblokkeerde PDBs:
kubectl get pdb --all-namespaces -o wide | grep ' 0 '
# Elke rij met ALLOWED DISRUPTIONS = 0 blokkeert de upgrade
Noodgrepen bij vastgelopen drains
# PDBs volledig omzeilen (voorzichtig)
kubectl drain <node> --ignore-daemonsets --disable-eviction
# --disable-eviction (k8s 1.18+) forceert directe verwijdering in plaats van eviction
# Drain met timeout zodat het niet eindeloos blokkeert
kubectl drain <node> --ignore-daemonsets --timeout=300s
--disable-eviction slaat PDB-controles helemaal over. Gebruik het alleen als je de impact op beschikbaarheid begrijpt en andere opties uitgeput zijn.
Hoe PDBs samenwerken met Cluster Autoscaler
De Cluster Autoscaler respecteert PDBs bij scale-down. Voordat een node wordt aangemerkt voor verwijdering, controleert de autoscaler of het evicten van de pods geen PDB zou schenden. Zo ja, dan wordt de node als "niet verwijderbaar" gemarkeerd en slaat de scale-down die over.
Configuraties die scale-down blokkeren:
maxUnavailable: 0op een PDB die een pod op de onderbenutte node matchtminAvailablegelijk aan het huidige replica-aantal, waardoordisruptionsAllowed: 0ontstaat- Een single-replica Deployment met een restrictieve PDB
Als Cluster Autoscaler niet omlaag schaalt en je PDB-interferentie vermoedt:
kubectl get pdb --all-namespaces -o wide
# Zoek naar ALLOWED DISRUPTIONS = 0 bij workloads op de vastzittende node
Voor Karpenter-gebruikers: Karpenters voluntary disruption-methoden (consolidation, drift, expiration) respecteren ook PDBs. Als een PDB op welke pod dan ook op een node blocking is, consolideert Karpenter die node niet. Karpenter NodePool Disruption Budgets zijn trouwens een apart, aanvullend systeem dat node-level disruptions beperkt, niet pod-level beschikbaarheid.
PDBs en rolling updates: gescheiden lagen
Een veelvoorkomend misverstand: PDBs beperken Deployment rolling updates niet. De officiele docs zeggen het expliciet: "workload resources (such as Deployment and StatefulSet) are not limited by PodDisruptionBudgets when doing rolling updates."
De Deployment-controller z'n .spec.strategy.rollingUpdate.maxUnavailable en maxSurge bepalen het rollout-gedrag. PDBs regelen voluntary evictions vanuit externe operaties (drain, autoscaler). Het zijn gescheiden, aanvullende lagen:
Deployment strategy → regelt rolling update-gedrag (nieuwe versie uitrollen)
PDB → regelt voluntary eviction-gedrag (drain, autoscaler)
Een goede combinatie voor een productieservice:
# Deployment: zero-downtime rollout
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # draai 1 nieuwe pod op voor je een oude verwijdert
maxUnavailable: 0 # nooit onder het gewenste aantal tijdens rollout
---
# PDB: veilig infrastructuuronderhoud
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
maxUnavailable: 1 # sta toe dat 1 pod tegelijk gedrained wordt
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app: myapp
Als een kubectl drain en een rolling update tegelijk plaatsvinden, wordt de gecombineerde unavailability van beide getoetst aan de PDB. De PDB voorkomt de rolling update zelf niet, maar voorkomt wel dat drain de situatie erger maakt.
Veelgemaakte fouten
PDB op een single-replica Deployment
Een PDB met minAvailable: 1 (of maxUnavailable: 0) op een single-replica Deployment levert permanent disruptionsAllowed: 0 op. Drain blokkeert eindeloos, Cluster Autoscaler kan niet omlaag schalen, Karpenter kan niet consolideren. Draai 2+ replica's, of sla de PDB voor die workload gewoon over.
maxUnavailable: 0 of minAvailable: 100%
Beide configuraties blokkeren alle voluntary evictions. Clusterupgrades op AKS, GKE en EKS lopen dan vast en falen. Gebruik dit alleen als je gecoordineerde out-of-band onderhoudsprocedures hebt en begrijpt dat het cluster z'n nodepools niet zelf kan herstellen.
Overlappende selectors tussen meerdere PDBs
Als twee PDBs dezelfde pod selecteren, retourneert de Eviction API HTTP 500 in plaats van 429. Drain faalt op een onverwachte manier. Elke PDB moet een unieke set pods dekken.
Lege selector in policy/v1
In policy/v1beta1 (verwijderd in Kubernetes 1.25) matchte een lege selector {} nul pods. In policy/v1 matcht een lege selector elke pod in de namespace. Een PDB-manifest migreren zonder de selector bij te werken kan onbedoeld de hele namespace op slot zetten.
De HPA-interactie vergeten
De Horizontal Pod Autoscaler raadpleegt geen PDBs bij het omlaag schalen van replica's. HPA kan het replica-aantal onder het PDB-minimum van minAvailable brengen, waardoor disruptionsAllowed op 0 komt en elke gelijktijdige drainoperatie blokkeert totdat HPA weer opschaalt. Monitor ALLOWED DISRUPTIONS na HPA scale-down events.
Resultaat verifieren
Controleer na het toepassen van je PDB of alles werkt:
# Huidige status bekijken
kubectl get pdb -n production -o wide
# ALLOWED DISRUPTIONS moet >= 1 zijn
# Simuleer een drain op een niet-kritieke node (cordon eerst, bekijk, drain daarna)
kubectl cordon <node-name>
kubectl drain <node-name> --ignore-daemonsets --dry-run=client
# De dry run toont welke pods ge-evict zouden worden en of PDBs dat blokkeren
# Tevreden? Voer de echte drain uit
kubectl drain <node-name> --ignore-daemonsets
Controleer na de drain dat de ge-evicte pods opnieuw ingepland zijn en de service de hele tijd beschikbaar bleef.
Wanneer escaleren
Als drain vastloopt ondanks je PDB-configuratie, verzamel dan het volgende voor je hulp vraagt:
kubectl get pdb --all-namespaces -o wide(volledige PDB-status)kubectl describe pdb <name> -n <namespace>(events en conditions)kubectl get events --field-selector reason=EvictionBlocked(eviction-specifieke events)- Kubernetes-versie (
kubectl version --short) - Cloudprovider en managed service tier (AKS, GKE, EKS)
- Of
unhealthyPodEvictionPolicyis ingesteld en op welke waarde - Aantal replica's en hun Ready-status (
kubectl get pods -l app=<label> -o wide)