Pod Disruption Budgets in Practice: Ensuring Kubernetes Application Availability
Pod Disruption Budgets (PDBs) protect application availability during voluntary disruptions by limiting the number of pods that can be simultaneously unavailable. This guide covers PDB design patterns, best practices, integration with autoscaling, and troubleshooting strategies for production environments.
Executive Summary
Kubernetes distinguishes between voluntary disruptions (planned maintenance, scaling, upgrades) and involuntary disruptions (hardware failures, network partitions). Pod Disruption Budgets provide guarantees about application availability during voluntary disruptions, enabling safe cluster operations while maintaining service levels.
PDB Fundamentals
Understanding Disruption Types
Voluntary vs Involuntary Disruptions:
# Voluntary Disruptions (Protected by PDB):
# - kubectl drain
# - Node maintenance
# - Cluster scaling down
# - VPA evictions
# - Application updates
# - Manual pod deletion with PDB awareness
# Involuntary Disruptions (NOT protected by PDB):
# - Hardware failures
# - Kernel panics
# - Network partitions
# - Out of memory kills
# - Cloud provider maintenance
# - Force deletions
---
# Basic PDB Example
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb
namespace: production
spec:
# Specify minimum available pods
minAvailable: 3
selector:
matchLabels:
app: web-app
tier: frontend
---
# Alternative: Maximum unavailable
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: production
spec:
# Specify maximum unavailable pods
maxUnavailable: 1
selector:
matchLabels:
app: api-server
PDB Calculation Modes
MinAvailable vs MaxUnavailable:
# minAvailable - Absolute number
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: database-pdb-absolute
namespace: production
spec:
minAvailable: 2 # Always keep at least 2 pods running
selector:
matchLabels:
app: database
---
# minAvailable - Percentage
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: cache-pdb-percentage
namespace: production
spec:
minAvailable: 75% # Keep at least 75% of pods running
selector:
matchLabels:
app: cache
---
# maxUnavailable - Absolute number
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-pdb-absolute
namespace: production
spec:
maxUnavailable: 1 # Allow at most 1 pod to be unavailable
selector:
matchLabels:
app: worker
---
# maxUnavailable - Percentage
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: frontend-pdb-percentage
namespace: production
spec:
maxUnavailable: 25% # Allow up to 25% of pods to be unavailable
selector:
matchLabels:
app: frontend
---
# Calculation Examples:
#
# Deployment with 10 replicas:
# - minAvailable: 7 → Can disrupt 3 pods
# - minAvailable: 70% → Can disrupt 3 pods
# - maxUnavailable: 3 → Can disrupt 3 pods
# - maxUnavailable: 30% → Can disrupt 3 pods
#
# During scaling:
# - Scaling up from 10 to 15 replicas
# - minAvailable: 7 → Still 7 (absolute)
# - minAvailable: 70% → Now 11 (percentage)
# - Scaling down from 10 to 5 replicas
# - minAvailable: 7 → Cannot scale below 7! (blocks scaling)
# - minAvailable: 70% → Now 4 (percentage, allows scaling)
Production PDB Patterns
High Availability Services
Critical Service PDB:
# critical-service-pdb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
namespace: production
spec:
replicas: 10
selector:
matchLabels:
app: payment-service
tier: critical
template:
metadata:
labels:
app: payment-service
tier: critical
spec:
containers:
- name: payment
image: payment-service:v2.0
resources:
requests:
cpu: 500m
memory: 1Gi
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-service-pdb
namespace: production
annotations:
description: "Ensures 90% availability during disruptions"
owner: "payments-team"
slo: "99.9%"
spec:
minAvailable: 90% # Always keep 90% available
selector:
matchLabels:
app: payment-service
tier: critical
unhealthyPodEvictionPolicy: AlwaysAllow # Evict unhealthy pods
---
# With 10 replicas:
# - minAvailable: 90% = 9 pods
# - Can disrupt: 1 pod at a time
# - Allows rolling updates one pod at a time
# - Protects against concurrent disruptions
Stateful Applications
StatefulSet PDB:
# statefulset-pdb.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
namespace: production
spec:
serviceName: elasticsearch
replicas: 6
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: elasticsearch:8.11.0
ports:
- containerPort: 9200
name: http
- containerPort: 9300
name: transport
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 100Gi
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: elasticsearch-pdb
namespace: production
spec:
# For Elasticsearch: Allow disrupting minority of nodes
maxUnavailable: 2 # Never disrupt more than 2 nodes
selector:
matchLabels:
app: elasticsearch
---
# Elasticsearch cluster considerations:
# - 6 node cluster: maxUnavailable: 2
# - Maintains quorum (4/6 nodes)
# - Allows rolling updates
# - Prevents split-brain scenarios
# - Ensures data availability during maintenance
Multi-Tier Applications
Coordinated PDBs:
# multi-tier-pdb.yaml
---
# Frontend tier - can tolerate more disruption
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: frontend-pdb
namespace: production
spec:
maxUnavailable: 30% # Can disrupt up to 30%
selector:
matchLabels:
app: ecommerce
tier: frontend
---
# API tier - moderate disruption tolerance
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
namespace: production
spec:
minAvailable: 75% # Keep 75% available
selector:
matchLabels:
app: ecommerce
tier: api
---
# Backend tier - conservative disruption
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: backend-pdb
namespace: production
spec:
maxUnavailable: 1 # Only 1 pod at a time
selector:
matchLabels:
app: ecommerce
tier: backend
---
# Database tier - strictest protection
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: database-pdb
namespace: production
spec:
minAvailable: 2 # Always maintain quorum
selector:
matchLabels:
app: ecommerce
tier: database
Integration with Autoscaling
PDB with Horizontal Pod Autoscaler
HPA and PDB Coordination:
# hpa-pdb-integration.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-service
namespace: production
spec:
replicas: 10 # Initial size
selector:
matchLabels:
app: web-service
template:
metadata:
labels:
app: web-service
spec:
containers:
- name: web
image: web-service:v1
resources:
requests:
cpu: 200m
memory: 256Mi
---
# HPA scales between 5 and 50 replicas
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-service-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-service
minReplicas: 5
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
# PDB uses percentage to adapt to HPA scaling
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-service-pdb
namespace: production
spec:
# Use percentage - adapts to HPA scaling
minAvailable: 80% # Always keep 80% available
selector:
matchLabels:
app: web-service
---
# Scaling scenarios:
#
# At minReplicas (5):
# - minAvailable: 80% = 4 pods
# - Can disrupt: 1 pod
#
# At midpoint (25):
# - minAvailable: 80% = 20 pods
# - Can disrupt: 5 pods
#
# At maxReplicas (50):
# - minAvailable: 80% = 40 pods
# - Can disrupt: 10 pods
#
# PDB automatically adjusts as HPA scales!
Avoiding PDB Blocking HPA Scale-Down:
# pdb-hpa-best-practices.yaml
# GOOD: Percentage-based PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: good-pdb
namespace: production
spec:
minAvailable: 80% # Scales with deployment size
selector:
matchLabels:
app: scalable-service
---
# BAD: Absolute PDB higher than HPA minReplicas
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: bad-pdb
namespace: production
spec:
minAvailable: 10 # Problem: HPA minReplicas is 5!
selector:
matchLabels:
app: scalable-service
# This PDB will prevent HPA from scaling below 10 replicas
# even though HPA allows 5 minReplicas
---
# BETTER: Use maxUnavailable with appropriate value
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: better-pdb
namespace: production
spec:
maxUnavailable: 20% # Allows disruption of minority
selector:
matchLabels:
app: scalable-service
PDB with Vertical Pod Autoscaler
VPA Eviction Coordination:
# vpa-pdb-integration.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-server-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Auto" # Will evict pods to apply recommendations
minReplicas: 5 # Don't evict if fewer replicas
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 200m
memory: 256Mi
maxAllowed:
cpu: 4000m
memory: 8Gi
---
# PDB protects during VPA evictions
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: production
spec:
minAvailable: 80% # VPA respects this during evictions
selector:
matchLabels:
app: api-server
---
# VPA eviction behavior with PDB:
# - VPA wants to evict pod for resource adjustment
# - Checks PDB before eviction
# - If PDB allows: evicts pod
# - If PDB blocks: waits for opportunity
# - Ensures gradual updates maintaining availability
PDB with Cluster Autoscaler
Node Draining Protection:
# ca-pdb-integration.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
namespace: production
spec:
replicas: 20
selector:
matchLabels:
app: batch-processor
template:
metadata:
labels:
app: batch-processor
annotations:
# Don't prevent node scale-down
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
spec:
containers:
- name: processor
image: batch-processor:v1
resources:
requests:
cpu: 1000m
memory: 2Gi
---
# PDB ensures graceful scale-down
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: batch-processor-pdb
namespace: production
spec:
minAvailable: 75% # Keep 75% running during scale-down
selector:
matchLabels:
app: batch-processor
---
# Cluster Autoscaler behavior:
# - Identifies underutilized node
# - Attempts to drain node
# - Respects PDB during drain
# - If PDB blocks: tries different node
# - If all nodes blocked: waits for opportunity
# - Ensures gradual, safe scale-down
Advanced PDB Configurations
Unhealthy Pod Eviction Policy
Kubernetes 1.26+ Feature:
# unhealthy-pod-eviction.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-with-unhealthy-policy
namespace: production
spec:
minAvailable: 3
selector:
matchLabels:
app: my-app
# AlwaysAllow: Evict unhealthy pods even if PDB would be violated
# IfHealthyBudget: Only evict unhealthy pods if healthy pods meet budget
unhealthyPodEvictionPolicy: AlwaysAllow
---
# Scenarios:
#
# Deployment with 5 replicas, minAvailable: 3
# - 3 healthy, 2 unhealthy pods
#
# AlwaysAllow:
# - Can evict all 2 unhealthy pods immediately
# - Ignores PDB for unhealthy pods
# - Helps recover from cascading failures
#
# IfHealthyBudget:
# - Can evict unhealthy pods only if 3 healthy remain
# - Still respects PDB constraints
# - Default behavior
Multiple PDBs for Same Pods
Overlapping Selectors:
# multiple-pdbs.yaml
# Deployment with multiple labels
apiVersion: apps/v1
kind: Deployment
metadata:
name: multi-role-service
namespace: production
spec:
replicas: 10
selector:
matchLabels:
app: multi-role
role: api
team: platform
template:
metadata:
labels:
app: multi-role
role: api
team: platform
spec:
containers:
- name: service
image: multi-role:v1
---
# PDB #1: Team-based budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: platform-team-pdb
namespace: production
spec:
minAvailable: 8 # Keep 8 pods for platform team
selector:
matchLabels:
team: platform
---
# PDB #2: Role-based budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-role-pdb
namespace: production
spec:
maxUnavailable: 2 # Allow disrupting 2 API pods
selector:
matchLabels:
role: api
---
# Behavior with multiple PDBs:
# - ALL PDBs must be satisfied for eviction
# - Most restrictive PDB wins
# - In this case: min(8 available from PDB#1, max 2 unavailable from PDB#2)
# - Result: Can disrupt 2 pods maximum
Troubleshooting PDB Issues
PDB Blocking Operations
Diagnostic Commands:
#!/bin/bash
# pdb-diagnostics.sh
NAMESPACE=${1:-production}
echo "=== Pod Disruption Budgets ==="
kubectl get pdb -n ${NAMESPACE} -o wide
echo ""
echo "=== PDB Details ==="
for pdb in $(kubectl get pdb -n ${NAMESPACE} -o name); do
echo "--- ${pdb} ---"
kubectl describe ${pdb} -n ${NAMESPACE}
done
echo ""
echo "=== Pods Covered by PDBs ==="
for pdb in $(kubectl get pdb -n ${NAMESPACE} -o name | cut -d/ -f2); do
echo "PDB: ${pdb}"
SELECTOR=$(kubectl get pdb ${pdb} -n ${NAMESPACE} -o jsonpath='{.spec.selector.matchLabels}' | jq -r 'to_entries | map("\(.key)=\(.value)") | join(",")')
kubectl get pods -n ${NAMESPACE} -l ${SELECTOR}
echo ""
done
echo "=== Pods NOT Covered by Any PDB ==="
kubectl get pods -n ${NAMESPACE} --show-labels
echo ""
echo "=== Recent PDB-related Events ==="
kubectl get events -n ${NAMESPACE} --sort-by='.lastTimestamp' | grep -i disruption
echo ""
echo "=== Check for Blocked Drains ==="
kubectl get nodes -o json | jq -r '.items[] | select(.spec.unschedulable==true) | .metadata.name'
Common PDB Problems:
# problem-pdbs.yaml
---
# PROBLEM 1: PDB with no matching pods
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: typo-pdb
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app: webapp # Typo! Should be "web-app"
# FIX: Ensure selector matches pod labels exactly
---
# PROBLEM 2: PDB stricter than deployment size
apiVersion: apps/v1
kind: Deployment
metadata:
name: small-app
spec:
replicas: 2 # Only 2 replicas
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: impossible-pdb
spec:
minAvailable: 3 # Impossible! Only 2 replicas exist
# FIX: Adjust PDB to maxUnavailable: 1 or minAvailable: 1
---
# PROBLEM 3: Conflicting PDBs
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: pdb-a
spec:
minAvailable: 8
selector:
matchLabels:
app: service
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: pdb-b
spec:
maxUnavailable: 1
selector:
matchLabels:
app: service
# With 10 replicas:
# - pdb-a requires: 8 available (2 can be disrupted)
# - pdb-b requires: 1 unavailable (1 can be disrupted)
# - Result: Only 1 can be disrupted (most restrictive)
# FIX: Ensure PDBs have compatible constraints
---
# PROBLEM 4: Forgotten PDB during deployment deletion
# Deployment is deleted but PDB remains
# PDB now matches no pods but prevents future operations
# FIX: Use ownerReferences or delete PDB with deployment
PDB Status Inspection
Detailed PDB Status:
# Check PDB status
$ kubectl get pdb web-app-pdb -o yaml
status:
currentHealthy: 8
desiredHealthy: 7 # Based on minAvailable/maxUnavailable
disruptionsAllowed: 1 # Can disrupt 1 pod
expectedPods: 10 # Total pods matching selector
observedGeneration: 3
conditions:
- lastTransitionTime: "2025-12-10T10:00:00Z"
message: ""
reason: ""
status: "True"
type: DisruptionAllowed
Monitoring and Alerting
PDB Metrics
ServiceMonitor for PDB Metrics:
# pdb-monitoring.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: pdb-alerts
namespace: monitoring
spec:
groups:
- name: pod-disruption-budgets
interval: 30s
rules:
- alert: PDBViolated
expr: |
kube_poddisruptionbudget_status_current_healthy
< kube_poddisruptionbudget_status_desired_healthy
for: 5m
labels:
severity: warning
annotations:
summary: "PDB {{ $labels.namespace }}/{{ $labels.poddisruptionbudget }} is violated"
description: "Current healthy: {{ $value }}, Desired: {{ $labels.desired_healthy }}"
- alert: PDBNoDisruptionsAllowed
expr: kube_poddisruptionbudget_status_disruptions_allowed == 0
for: 15m
labels:
severity: warning
annotations:
summary: "PDB {{ $labels.namespace }}/{{ $labels.poddisruptionbudget }} allows no disruptions"
description: "Cannot perform voluntary disruptions - check pod health and replica count"
- alert: PDBMismatchedPods
expr: |
kube_poddisruptionbudget_status_expected_pods == 0
for: 10m
labels:
severity: warning
annotations:
summary: "PDB {{ $labels.namespace }}/{{ $labels.poddisruptionbudget }} matches no pods"
description: "Check selector labels - PDB may be misconfigured"
- alert: PDBLowHealthyPods
expr: |
(kube_poddisruptionbudget_status_current_healthy
/ kube_poddisruptionbudget_status_expected_pods) < 0.8
for: 10m
labels:
severity: warning
annotations:
summary: "PDB {{ $labels.namespace }}/{{ $labels.poddisruptionbudget }} has low healthy pod ratio"
description: "Only {{ $value | humanizePercentage }} pods healthy"
Grafana Dashboard
PDB Dashboard:
{
"dashboard": {
"title": "Pod Disruption Budget Status",
"panels": [
{
"title": "Disruptions Allowed",
"targets": [
{
"expr": "kube_poddisruptionbudget_status_disruptions_allowed",
"legendFormat": "{{ namespace }}/{{ poddisruptionbudget }}"
}
]
},
{
"title": "Healthy vs Expected Pods",
"targets": [
{
"expr": "kube_poddisruptionbudget_status_current_healthy",
"legendFormat": "{{ namespace }}/{{ poddisruptionbudget }} - Healthy"
},
{
"expr": "kube_poddisruptionbudget_status_expected_pods",
"legendFormat": "{{ namespace }}/{{ poddisruptionbudget }} - Expected"
},
{
"expr": "kube_poddisruptionbudget_status_desired_healthy",
"legendFormat": "{{ namespace }}/{{ poddisruptionbudget }} - Desired"
}
]
},
{
"title": "PDB Health Ratio",
"targets": [
{
"expr": "kube_poddisruptionbudget_status_current_healthy / kube_poddisruptionbudget_status_expected_pods",
"legendFormat": "{{ namespace }}/{{ poddisruptionbudget }}"
}
]
}
]
}
}
Best Practices
Production PDB Checklist
# pdb-best-practices.yaml
# 1. Always define PDBs for production services
# - Protects during maintenance
# - Ensures SLO compliance
# - Coordinates with autoscaling
# 2. Use appropriate min/max values
# Good:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: good-pdb
spec:
minAvailable: 75% # Percentage adapts to scaling
selector:
matchLabels:
app: scalable-service
# 3. Match PDB to service criticality
# Critical services: minAvailable: 90%
# Standard services: minAvailable: 75%
# Background jobs: maxUnavailable: 50%
# 4. Coordinate with HPA
# - Use percentages for PDB
# - Ensure PDB minAvailable < HPA minReplicas
# 5. Test PDB effectiveness
# - Simulate node drains
# - Verify rolling updates respect PDB
# - Check autoscaler interactions
# 6. Monitor PDB status
# - Alert on violated PDBs
# - Track disruptions allowed
# - Monitor pod health ratios
# 7. Document PDB decisions
# - Explain min/max choices
# - Link to SLOs
# - Maintain runbooks
# 8. Use labels consistently
# - Ensure selectors match pods
# - Avoid typos in labels
# - Use automation to verify
# 9. Handle unhealthy pods
# - Set unhealthyPodEvictionPolicy appropriately
# - AlwaysAllow for resilient services
# - IfHealthyBudget for critical services
# 10. Clean up obsolete PDBs
# - Remove PDBs when deployments deleted
# - Use ownerReferences
# - Regular audits
PDB Anti-Patterns
# pdb-anti-patterns.yaml
# ANTI-PATTERN 1: No PDB for production service
# Risk: Uncontrolled disruptions during maintenance
# ANTI-PATTERN 2: Overly restrictive PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
minAvailable: 100% # NEVER allows any disruption!
# Fix: Use 90% or appropriate threshold
# ANTI-PATTERN 3: Absolute values with dynamic scaling
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
minAvailable: 10 # Blocks scaling below 10
# Fix: Use percentages
# ANTI-PATTERN 4: Overlapping PDBs without coordination
# Multiple PDBs with different constraints
# Fix: Ensure PDBs are compatible
# ANTI-PATTERN 5: Ignoring pod readiness
# PDB counts unhealthy pods as healthy
# Fix: Implement proper readiness probes
# ANTI-PATTERN 6: PDB for single-replica deployment
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
minAvailable: 1 # Blocks all voluntary disruptions!
# Fix: Don't use PDB, or accept disruptions
# ANTI-PATTERN 7: Forgetting PDB during testing
# Only adding PDB in production
# Fix: Include PDB in all environments
# ANTI-PATTERN 8: Not testing PDB behavior
# Assuming PDB works without verification
# Fix: Regular drain simulations
Real-World Scenarios
Scenario 1: Rolling Update
# rolling-update-with-pdb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
replicas: 20
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 5 # Can have up to 25 pods during update
maxUnavailable: 0 # Never go below 20 healthy pods
template:
spec:
containers:
- name: api
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 3
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-service-pdb
spec:
minAvailable: 90% # Keep 18/20 pods (90%) available
selector:
matchLabels:
app: api-service
# Rolling update behavior:
# - Kubernetes creates 5 new pods (maxSurge)
# - Waits for new pods to become ready
# - Terminates old pods one by one
# - PDB ensures never more than 2 unavailable
# - Update proceeds gradually and safely
Scenario 2: Node Maintenance
#!/bin/bash
# safe-node-drain.sh
NODE=$1
echo "Draining node: ${NODE}"
# Check PDBs first
echo "Checking PDBs..."
kubectl get pdb --all-namespaces
# Drain with PDB respect (default)
kubectl drain ${NODE} \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=600 \
--timeout=15m
# If drain hangs, check which pods are blocking
kubectl get pods --all-namespaces --field-selector spec.nodeName=${NODE}
# Check PDB status for blocking PDBs
for ns in $(kubectl get ns -o name | cut -d/ -f2); do
kubectl get pdb -n ${ns} -o json | \
jq -r '.items[] | select(.status.disruptionsAllowed == 0) | .metadata.name'
done
# Once maintenance complete
kubectl uncordon ${NODE}
Conclusion
Pod Disruption Budgets are essential for maintaining application availability during voluntary disruptions. Key takeaways:
- Always define PDBs for production services
- Use percentage-based values for services with dynamic scaling
- Coordinate PDBs with HPA, VPA, and Cluster Autoscaler
- Test PDB effectiveness with drain simulations
- Monitor PDB status and alert on violations
- Document PDB decisions and link to SLOs
- Avoid overly restrictive PDBs that block operations
- Implement proper readiness probes for accurate health detection
- Regular audits to remove obsolete PDBs
- Maintain runbooks for PDB troubleshooting
Properly configured PDBs enable safe cluster operations while maintaining service availability guarantees, forming a critical component of production Kubernetes reliability engineering.