Plantilla de Pod Disruption Budget de Kubernetes
Una plantilla para definir Pod Disruption Budgets que controlan las disrupciones voluntarias durante drenajes de nodos, actualizaciones y ventanas de mantenimiento.
Visión General
Un Pod Disruption Budget (PDB) le dice a Kubernetes cuánto de tu carga de trabajo puede interrumpirse voluntariamente a la vez. Cuando alguien ejecuta kubectl drain para sacar un nodo de servicio durante una actualización, la eviction API revisa cada PDB que cubre un pod antes de eliminarlo. Sin un PDB, nada impide que un drain desaloje todas las réplicas de un servicio en pocos segundos — y tu balanceador se entera antes de que los pods terminen de reagendarse en otro nodo.
Los PDB solo gobiernan las disrupciones voluntarias: drenajes, mantenimiento programado, scale-downs del cluster autoscaler y evictions manuales por la API. No hacen nada contra las involuntarias — un kernel panic, un disco muerto o una partición de red mata pods sin mirar tu presupuesto. Los replica controllers resuelven esos casos agendando reemplazos; el trabajo del PDB es más acotado: limitar la velocidad a la que un operador (o el autoscaler) puede tumbar pods sanos a propósito.
Esa distinción importa al escribir el presupuesto. Un PDB no es una garantía de disponibilidad — es un freno. Si un nodo falla en el mismo minuto en que arrancas un drain, igual puedes quedar por debajo de tu mínimo, porque las disrupciones voluntarias e involuntarias cuentan juntas. Dimensiona las réplicas con margen, y trata el PDB como protección contra errores de mantenimiento, no contra la física.
Esta plantilla te da un documento PDB completo y listo para copiar: selección de estrategia, ejemplos por servicio, interacción con drains, integración con Helm, monitoreo y troubleshooting. Los manifiestos listos para aplicar están en el repositorio de apoyo. Combínala con la checklist de revisión de Helm charts cuando publiques PDBs dentro de charts, y con los Resource Quotas de Kubernetes para la parte de capacidad en clusters multi-tenant.
Cuándo Usar
- Deployments o StatefulSets con varias réplicas que no pueden permitirse perder todos los pods a la vez durante un drain
- Flujos de actualización de cluster donde los nodos se drenan en una ventana programada
- Kubernetes administrado (EKS, GKE, AKS) donde el proveedor rota nodos durante actualizaciones del control plane o del node pool
- Servicios con requisitos de cumplimiento o SLO que documentan disponibilidad mínima durante mantenimiento
- Equipos que usan cluster autoscaler o Karpenter, donde el scale-down drena nodos sin que nadie lo esté mirando
Evita los PDB en cargas de una sola réplica que toleran caídas (un PDB con minAvailable: 1 solo bloquea todos los drains), en DaemonSets (un pod por nodo, la eviction no aplica igual) y en Jobs o CronJobs cuyos pods son efímeros por diseño.
Solución
# Pod Disruption Budget — `<Nombre del Servicio>`
## Resumen del PDB
| Campo | Valor |
|-------|-------|
| Servicio | example-app |
| Namespace | production |
| Deployment | example-app |
| Réplicas Mínimas | 3 |
| Estrategia PDB | minAvailable |
| Mínimo Disponible | 2 |
| Máximo No Disponible | — |
| Versión de API | policy/v1 |
| Última Actualización | 2026-10-19 |
| Responsable | Equipo DevOps |
## 1. Definiciones de PDB
### PDB estándar (minAvailable)
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: example-app-pdb
namespace: production
labels:
app.kubernetes.io/name: example-app
app.kubernetes.io/instance: example-app
app.kubernetes.io/managed-by: helm
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: example-app
app.kubernetes.io/instance: example-app
```
### PDB con maxUnavailable
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: example-app-pdb
namespace: production
labels:
app.kubernetes.io/name: example-app
app.kubernetes.io/instance: example-app
spec:
maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: example-app
app.kubernetes.io/instance: example-app
```
### PDB con porcentaje
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: example-app-pdb
namespace: production
spec:
minAvailable: 50%
selector:
matchLabels:
app.kubernetes.io/name: example-app
```
## 2. Selección de Estrategia PDB
| Estrategia | Valor | Cuándo Usar | Ejemplo |
|------------|-------|-------------|---------|
| minAvailable (absoluto) | 2 | Mínimo fijo de réplicas necesarias | 3 réplicas, siempre hacen falta 2 |
| minAvailable (porcentaje) | 50% | Escala con el número de réplicas | 10 réplicas, hacen falta 5 |
| maxUnavailable (absoluto) | 1 | Controlar la tasa de disrupción | 5 réplicas, permitir 1 caída |
| maxUnavailable (porcentaje) | 25% | Disrupción proporcional | 8 réplicas, permitir 2 caídas |
### Matriz de Decisión
| Número de Réplicas | Estrategia Recomendada | Motivo |
|--------------------|------------------------|--------|
| 1 | Sin PDB (o minAvailable: 1) | Réplica única, el PDB bloquea todos los drains |
| 2 | maxUnavailable: 1 | Permite 1 caída, mantiene 1 corriendo |
| 3 | minAvailable: 2 | Mantiene 2 corriendo, permite 1 disrupción |
| 5 | maxUnavailable: 1 | Permite 1 disrupción a la vez |
| 10+ | maxUnavailable: 25% | Control de disrupción proporcional |
## 3. PDBs por Servicio
### Web Frontend
| Campo | Valor |
|-------|-------|
| Deployment | web-frontend |
| Réplicas | 6 |
| Estrategia | maxUnavailable: 1 |
| Motivo | Tráfico alto, tolera 1 pod caído |
| HPA | Sí (mín: 4, máx: 12) |
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-frontend-pdb
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: web-frontend
```
### Servicio API
| Campo | Valor |
|-------|-------|
| Deployment | api-service |
| Réplicas | 4 |
| Estrategia | minAvailable: 3 |
| Motivo | Ruta crítica, necesita 75% disponible |
| HPA | Sí (mín: 3, máx: 8) |
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-service-pdb
namespace: production
spec:
minAvailable: 3
selector:
matchLabels:
app.kubernetes.io/name: api-service
```
### Worker (Procesos en Segundo Plano)
| Campo | Valor |
|-------|-------|
| Deployment | background-worker |
| Réplicas | 3 |
| Estrategia | maxUnavailable: 1 |
| Motivo | Los jobs se reanudan, tolera 1 caída |
| HPA | No |
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: background-worker-pdb
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: background-worker
```
### Base de Datos (StatefulSet)
| Campo | Valor |
|-------|-------|
| StatefulSet | postgres-cluster |
| Réplicas | 3 |
| Estrategia | minAvailable: 2 |
| Motivo | Basado en quorum, necesita mayoría |
| HPA | No |
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: postgres-pdb
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: postgres-cluster
```
### Caché Redis
| Campo | Valor |
|-------|-------|
| Deployment | redis-cache |
| Réplicas | 1 |
| Estrategia | Sin PDB |
| Motivo | Réplica única, el PDB bloquearía todos los drains |
### Job Programado (CronJob)
| Campo | Valor |
|-------|-------|
| CronJob | data-export |
| Estrategia | Sin PDB |
| Motivo | Jobs efímeros, el CronJob controller los reagenda |
## 4. Interacción entre PDB y Node Drain
### Cómo el PDB Afecta a los Node Drains
| Escenario | Nº de Pods | PDB | Comportamiento del Drain |
|-----------|------------|-----|--------------------------|
| 3 pods, minAvailable: 2 | 3 | 2 | Desaloja 1 a la vez, espera al nuevo pod antes del siguiente |
| 3 pods, minAvailable: 3 | 3 | 3 | Drain bloqueado — no puede desalojar ningún pod |
| 5 pods, maxUnavailable: 1 | 5 | 1 | Desaloja 1 a la vez |
| 2 pods, maxUnavailable: 1 | 2 | 1 | Desaloja 1, espera al reemplazo |
| 1 pod, minAvailable: 1 | 1 | 1 | Drain bloqueado — no puede desalojar el único pod |
### Comandos de Drain
```bash
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data
## Drain con timeout (el PDB puede bloquear)
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data --timeout=5m
## Drain forzado (ignora el PDB — usar con cuidado)
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data --force
## Revisar el estado del drain
kubectl get pods -o wide --field-selector spec.nodeName=node-1
## Reactivar el nodo tras el mantenimiento
kubectl uncordon node-1
```
### Revisar el Estado del PDB
```bash
## Revisar el estado del PDB
kubectl get pdb -n production
## Estado detallado del PDB
kubectl describe pdb example-app-pdb -n production
## Revisar qué pods están protegidos
kubectl get pods -n production -l app.kubernetes.io/name=example-app
```
## 5. PDB en Helm Charts
### values.yaml
```yaml
podDisruptionBudget:
enabled: true
minAvailable: 2
# maxUnavailable: 1 # Alternativa a minAvailable
```
### Template
```yaml
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "chart.fullname" . }}
labels:
{{- include "chart.labels" . | nindent 4 }}
spec:
{{- if .Values.podDisruptionBudget.minAvailable }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
{{- end }}
{{- if .Values.podDisruptionBudget.maxUnavailable }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- end }}
selector:
matchLabels:
{{- include "chart.selectorLabels" . | nindent 6 }}
{{- end }}
```
## 6. Monitoreo de PDBs
### Métricas Clave
| Métrica | Descripción | Umbral de Alerta |
|---------|-------------|------------------|
| Disrupciones permitidas del PDB | Número de pods que pueden interrumpirse | < 0 (bloqueado) |
| Pods sanos actuales del PDB | Pods sanos en este momento | < minAvailable |
| Pods sanos deseados del PDB | Objetivo de pods sanos | — |
| Pods esperados del PDB | Total de pods que coinciden con el selector | — |
| Duración de drain bloqueado | Tiempo que un drain queda bloqueado por el PDB | > 10 min |
### Reglas de Alerta
```yaml
## Reglas de alerta de Prometheus
groups:
- name: pdb-alerts
rules:
- alert: PDBBlocked
expr: kube_poddisruptionstatus_allowed_disruptions == 0
for: 10m
labels:
severity: warning
annotations:
summary: "El PDB {{ $labels.pdb }} bloquea todas las disrupciones"
description: "Ningún pod puede interrumpirse para {{ $labels.namespace }}/{{ $labels.pdb }}"
- alert: PDBBelowMinAvailable
expr: kube_poddisruptionstatus_current_healthy < kube_poddisruptionstatus_desired_healthy
for: 5m
labels:
severity: critical
annotations:
summary: "El PDB {{ $labels.pdb }} por debajo del mínimo disponible"
description: "Solo {{ $value }} pods sanos, se necesitan {{ $labels.desired_healthy }}"
```
## 7. Troubleshooting de PDBs
### Problemas Comunes
| Problema | Causa | Solución |
|----------|-------|----------|
| Drain atascado | El PDB bloquea la eviction (minAvailable = réplicas) | Escalar el deployment, luego drenar |
| Drain atascado | No hay réplicas suficientes para satisfacer el PDB | Escalar antes de drenar |
| PDB no encontrado | El PDB no existe o está en otro namespace | Verificar: `kubectl get pdb -n <ns>` |
| Selector del PDB no coincide | El selector no coincide con las labels de los pods | Verificar labels: `kubectl get pods --show-labels` |
| Timeout de eviction | El pod tarda demasiado en terminar | Añadir terminationGracePeriodSeconds |
### Pasos de Depuración
```bash
## 1. Revisar el estado del PDB
kubectl get pdb -n production
kubectl describe pdb example-app-pdb -n production
## 2. Revisar el conteo de pods vs. los requisitos del PDB
kubectl get pods -n production -l app.kubernetes.io/name=example-app --no-headers | wc -l
## 3. Revisar si los pods están sanos
kubectl get pods -n production -l app.kubernetes.io/name=example-app
## 4. Revisar el estado del drain del nodo
kubectl get nodes -o wide
kubectl describe node node-1 | grep -A 10 "Conditions:"
## 5. Revisar los eventos de eviction
kubectl get events -n production --field-selector reason=Eviction
```
Cómo Funciona el Flujo de Eviction
Cuando ejecutas kubectl drain, no borra pods — envía evictions a través del subrecurso /eviction de la API de pods. Esa indirección es el punto clave: un kubectl delete pod directo ignora los presupuestos por completo, mientras que una eviction se valida contra cada PDB cuyo selector coincida con el pod. La eviction API responde 429 Too Many Requests cuando se violaría el presupuesto, y el drain reintenta hasta que el reemplazo del pod esté listo en otro nodo.
Hay tres detalles de ese bucle que sorprenden en producción. Primero, el chequeo cuenta pods sanos actuales, no réplicas deseadas — si un pod está en crash-loop, ya descuenta tu presupuesto, así que un drain puede atascarse aunque tengas réplicas deseadas de sobra. Segundo, la eviction es por pod y secuencial: el drain no desaloja el pod dos hasta que el reemplazo del pod uno reporta Ready, y por eso un servicio que tarda en arrancar convierte un drain de dos minutos en veinte. Tercero, tanto kubectl drain --force como kubectl delete pod se saltan este camino — por eso forzar un drain es la forma más rápida de violar tu propio piso de disponibilidad.
Explicación
Los dos campos del presupuesto, minAvailable y maxUnavailable, son mutuamente excluyentes — elige uno por PDB. minAvailable fija un piso: “nunca bajes de N pods sanos”. maxUnavailable fija un techo sobre la tasa de disrupción: “nunca tumbes más de N a la vez”. Ambos aceptan números absolutos o porcentajes, y los porcentajes redondean hacia arriba en minAvailable y hacia abajo en maxUnavailable — un detalle que importa con pocas réplicas, donde minAvailable: 50% sobre tres pods exige dos sanos.
Para la mayoría de los servicios, maxUnavailable: 1 es el default más sensato. No depende de cuántas réplicas corras, sobrevive a los scale-ups del HPA sin ediciones, y garantiza que los drains avancen de a un pod. Usa minAvailable cuando el piso en sí es el requisito — un StatefulSet basado en quorum como etcd o una base de datos que necesita mayoría para seguir escribiendo.
Los deployments de una sola réplica son la trampa clásica. Un PDB con minAvailable: 1 sobre un solo pod hace que cualquier drain se cuelgue para siempre, porque desalojar la única réplica violaría el presupuesto. O escalas a dos o más réplicas antes de activar el PDB, o lo omites y aceptas que los drains causarán una caída breve. Un drain bloqueado también frena lo que lo disparó — incluido el cluster autoscaler, que se queda esperando en vez de recuperar un nodo vacío.
La interacción con el autoscaler corta en los dos sentidos. Cuando el autoscaler (o Karpenter) decide que un nodo está infrautilizado, lo drena primero y ese drain respeta los PDBs. Si tu PDB no puede satisfacerse, el scale-down aborta y sigues pagando el nodo. unhealthyPodEvictionPolicy: AlwaysAllow (policy/v1, estable desde Kubernetes 1.31) suaviza esto permitiendo que la eviction avance sobre pods que todavía no están Ready — útil para cargas en crash-loop, peligroso para todo lo demás.
No confundas el presupuesto del PDB con el maxUnavailable del rollingUpdate del Deployment. Parecen iguales pero viven en planos distintos: el campo de rolling-update controla cuántos pods pueden estar caídos durante un deploy, y los pods que se están reemplazando no cuentan contra el PDB porque el Deployment corre réplicas extra temporalmente. El PDB te protege durante el mantenimiento; el rolling-update te protege durante los deploys. Necesitas los dos, ajustados por separado.
Variantes
| Contexto | Enfoque | Notas |
|---|---|---|
| Réplica única | Sin PDB o minAvailable: 0 | Acepta caída durante los drains |
| StatefulSet | minAvailable para quorum | Las bases de datos necesitan mayoría |
| DaemonSet | No necesita PDB | Los pods de DaemonSet corren en cada nodo |
| Job/CronJob | No necesita PDB | Pods efímeros, se reagendan |
| Escalado con HPA | Usa PDB porcentual | Escala con el número de réplicas |
| Multi-AZ | Un PDB por AZ | Evita disrupción en todas las AZ |
Una variante más que vale la pena conocer: minAvailable: 0 o maxUnavailable: 100% desactiva el presupuesto en la práctica pero mantiene el objeto PDB. Los equipos lo usan como opt-out documentado — el objeto existe, los revisores ven la decisión, y nadie confunde la ausencia de PDB con un descuido durante una auditoría. Si la adoptas, deja un comentario en el manifiesto explicando por qué el presupuesto es permisivo a propósito, o la siguiente persona lo va a “arreglar” por ti.
Qué Funciona
- Pon siempre un PDB en deployments multi-réplica — evita que un drain desaloje todo a la vez.
- Usa
maxUnavailable: 1como default salvo que un servicio necesite de verdad un piso fijo. - Prefiere presupuestos porcentuales en cargas con HPA para que el presupuesto siga al número de réplicas.
- Escala antes de drenar cuando el conteo actual no puede satisfacer el PDB.
- Alerta sobre
allowedDisruptions == 0— un PDB bloqueado descubierto en pleno incidente llega tarde. - Ejercita los PDBs en game days antes de confiar en ellos en una actualización real.
- Publica los PDBs dentro de los Helm charts para que sea imposible olvidarlos por servicio.
Errores Comunes
- PDB en un deployment de una réplica — todos los drains se cuelgan para siempre.
minAvailableigual al número de réplicas — ningún pod puede desalojarse jamás.- Omitir PDBs en servicios críticos — un drain tumba el servicio entero.
- Un selector que no coincide con las labels de los pods — el PDB existe pero no protege nada.
- Sin alertas sobre el estado del PDB — los drains bloqueados se descubren en pleno incidente.
- Olvidar el PDB al hacer scale-down — el presupuesto puede frenar al autoscaler.
- Usar
policy/v1beta1— está deprecado y eliminado; usapolicy/v1. - Esperar que el PDB cubra fallos de nodo — solo gobierna evictions voluntarias.
Véase También
- Checklist de Revisión de Helm Charts — verifica que los PDBs viajan dentro de tus charts.
- Plantilla de Resource Quotas de Kubernetes — la parte de capacidad de la gobernanza de cargas.
- Plantilla de Política de Auto-Scaling — alinea las reglas del HPA con tu presupuesto de disrupción.
Lecturas Recomendadas
Preguntas frecuentes
¿Qué pasa si un PDB bloquea un node drain?
El drain espera — por defecto, indefinidamente. Usa --timeout para acotar la espera. Si el presupuesto nunca puede satisfacerse (menos réplicas que minAvailable), el drain se queda bloqueado hasta el timeout o hasta que escales, relajes el PDB o uses --force, que ignora el presupuesto y puede causar caída.
¿Debería usar minAvailable o maxUnavailable?
Usa minAvailable cuando necesitas un piso garantizado — "mantén siempre dos pods corriendo". Usa maxUnavailable cuando quieres controlar la tasa de disrupción — "nunca tumbes más de uno a la vez". Para la mayoría de los servicios maxUnavailable: 1 es un buen default porque sobrevive a cambios en el número de réplicas sin ediciones.
¿Los PDBs protegen contra fallos de hardware?
No. Los PDBs solo aplican a disrupciones voluntarias como drains y evictions por API. Un nodo muerto lo gestiona el replica controller reagendando pods en otro sitio. Si un nodo muere mientras un drain también está en curso, ambos descuentan de tu conteo de pods sanos — dimensiona las réplicas con margen para ese solapamiento.
¿Puedo tener minAvailable y maxUnavailable en el mismo PDB?
No — son mutuamente excluyentes dentro de un mismo PDB. Elige el que coincida con tu forma de pensar la disponibilidad: un piso que no puedes cruzar, o una tasa que no puedes superar.
¿Cómo interactúan los PDBs con el cluster autoscaler?
El autoscaler drena un nodo antes de eliminarlo, y ese drain respeta los PDBs. Un PDB que no puede satisfacerse frena el scale-down hasta el timeout del autoscaler — ajustable con --max-graceful-termination-sec — y entonces el nodo sobrevive. Es una característica: el autoscaler no puede matar tu quorum sin ruido. Define unhealthyPodEvictionPolicy: AlwaysAllow si los pods que no llegan a Ready son la razón por la que tus drains se atascan.
¿Por qué kubectl delete pod funcionó si el PDB bloqueaba mi drain?
Porque delete no es una eviction. El PDB solo protege el camino de la API /eviction — los drains y las evictions del autoscaler pasan por ahí, los borrados directos no. Si necesitas quitar un pod durante mantenimiento, desalójalo (kubectl drain, o POST al subrecurso de eviction) en vez de borrarlo.
Recursos Relacionados
Checklist de Revisión de Helm Charts
Un checklist para revisar Helm charts cubriendo values, templates, security, resource limits, probes, RBAC y best practices.
DocPlantilla de Kubernetes Resource Quotas
Plantilla para definir Kubernetes resource quotas por namespace: CPU y memory limits, object count quotas, storage quotas, LimitRanges para default requests, priority class integration y monitoring con ejemplos para multi-tenant clusters.
DocPlantilla de Política de Auto-Scaling
Una plantilla para documentar reglas de scale-up y scale-down para infraestructura en la nube.
DocPlantilla de Diseño de Pipeline CI/CD
Una plantilla para documentar stages, gates, environments, deployment strategies, rollback procedures y security scanning de pipelines CI/CD.
DocPolítica de Gestión de Estado de Terraform
Una política para gestionar Terraform state files: backend configuration, locking, isolation, access control, versioning y disaster recovery.