Un cluster de Kubernetes puede andar bien en pruebas y fallar en producción por motivos que no aparecen en una demo: un nodo que se queda sin memoria, una actualización que baja todas las réplicas a la vez o un respaldo que nadie restauró nunca. Casi siempre el origen es un valor por omisión que nadie revisó.
Esta guía recorre nueve errores frecuentes. Para cada uno explica qué pasa, cómo detectarlo con kubectl o un manifiesto y cómo corregirlo. Se apoya en la documentación oficial de Kubernetes, en la guía de endurecimiento de NSA y CISA (versión 1.2, agosto de 2022) y en la documentación de AWS, Google Cloud y Microsoft para sus servicios gestionados.
Los ejemplos corresponden a las versiones con soporte al 4 de noviembre de 2026: 1.35, 1.36 y 1.37. La 1.34 llegó a fin de vida el 27 de octubre de 2026 (kubernetes.io/releases).
1. Pods sin requests ni limits
Qué pasa. El scheduler ubica cada Pod según sus requests, no según su consumo real. Si un Pod no declara requests ni limits de CPU y memoria, queda en la clase de calidad de servicio (QoS) BestEffort. Cuando un nodo se queda sin recursos, Kubernetes desaloja primero los BestEffort, luego los Burstable y al final los Guaranteed. Los límites también se comportan distinto: el de memoria se aplica terminando el contenedor (OOM kill) y el de CPU, frenándolo (throttling). El contenedor no se cae, pero se pone lento.
Cómo detectarlo.
# Pods sin requests ni limits (QoS BestEffort)
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,POD:.metadata.name,QOS:.status.qosClass' | grep BestEffort
# Contenedores que terminaron por superar su límite de memoria
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\t"}{.status.containerStatuses[*].lastState.terminated.reason}{"\n"}{end}' | grep OOMKilledCómo corregirlo. Declara requests en todos los contenedores, a partir del consumo medido, y un límite de memoria. Si requests y limits son iguales para CPU y memoria en todos los contenedores, el Pod es Guaranteed y queda último en la fila de desalojo. Para que nada entre a un namespace sin valores, usa un LimitRange, que los asigna por omisión, y un ResourceQuota para acotar el consumo total del namespace, como recomienda la guía de NSA y CISA.
apiVersion: v1
kind: LimitRange
metadata:
name: valores-por-omision
namespace: app
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 128Mi
default:
memory: 256Mi2. Probes mal configuradas
Qué pasa. Las tres probes cumplen funciones distintas. La liveness reinicia el contenedor cuando falla; la readiness lo saca de los Services sin reiniciarlo; la startup suspende las otras dos hasta que la aplicación termina de arrancar. La documentación advierte que una liveness mal implementada puede provocar fallas en cascada: reinicios bajo carga, peticiones fallidas y más carga sobre los Pods que quedan. El caso típico es una liveness que consulta la base de datos: si la base cae, se reinician todas las réplicas a la vez. Otro es una aplicación lenta sin startup probe, que muere antes de terminar de arrancar.
Cómo detectarlo.
# Probes que están fallando
kubectl get events -A --field-selector reason=Unhealthy
# Pods ordenados por número de reinicios
kubectl get pods -A --sort-by='.status.containerStatuses[0].restartCount'Cómo corregirlo. La liveness debe revisar solo el propio proceso; las dependencias externas van en la readiness. Agrega una startup probe sobre el mismo endpoint, con failureThreshold × periodSeconds suficiente para el peor arranque. Ten presentes los valores por omisión: periodSeconds 10, timeoutSeconds 1 y failureThreshold 3.
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30 # hasta 300 s para arrancar
periodSeconds: 10
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 53. Imágenes con latest o sin digest
Qué pasa. Si no indicas un tag, Kubernetes asume latest. Un tag se puede mover a otra imagen; un digest es un hash del contenido y no cambia. La documentación oficial pide evitar :latest en producción porque cuesta saber qué versión corre y hacer rollback. Con tags que se mueven puedes terminar con Pods que ejecutan código distinto bajo el mismo nombre. Un detalle: imagePullPolicy se fija al crear el objeto y no se actualiza si después cambias el tag.
Cómo detectarlo.
# Imágenes en uso que no están fijadas por digest
kubectl get pods -A -o jsonpath='{range .items[*].spec.containers[*]}{.image}{"\n"}{end}' | sort -u | grep -v '@sha256:'Cómo corregirlo. Usa un tag de versión y agrega el digest. Cuando hay ambos, Kubernetes descarga según el digest. Escanea las imágenes en el pipeline de construcción, como recomienda la guía de NSA y CISA.
image: registry.example.com/tienda/api:1.8.3@sha256:<digest>4. Secretos expuestos y RBAC demasiado amplio
Qué pasa. Por omisión, los Secrets se guardan sin cifrar en etcd, codificados en base64, que no es cifrado. Cualquiera con acceso a la API y permiso de lectura los ve. Según las buenas prácticas de RBAC, los permisos list y watch también revelan su contenido, y quien puede crear Pods en un namespace puede leer sus Secrets. Por eso un RBAC amplio y los secretos sin cifrar son el mismo problema.
Cómo detectarlo.
# Quién tiene cluster-admin
kubectl get clusterrolebindings -o jsonpath='{range .items[?(@.roleRef.name=="cluster-admin")]}{.metadata.name}{"\t"}{.subjects[*].name}{"\n"}{end}'
# Qué puede hacer la cuenta de servicio por omisión de un namespace
kubectl auth can-i --list --as=system:serviceaccount:app:default -n app
# Cifrado en reposo, en un nodo de control creado con kubeadm
sudo grep encryption-provider-config /etc/kubernetes/manifests/kube-apiserver.yamlRevisa también que no haya contraseñas en ConfigMaps ni en manifiestos guardados en Git.
Cómo corregirlo. Activa el cifrado en reposo: si el kube-apiserver no tiene --encryption-provider-config, o el primer proveedor de ese archivo es identity, los datos no se cifran. En un servicio gestionado, busca la opción equivalente en la documentación del proveedor. Asigna permisos con RoleBinding por namespace en vez de ClusterRoleBinding, evita los comodines y no agregues usuarios al grupo system:masters. Fija automountServiceAccountToken: false en los Pods que no usan la API.
5. Sin NetworkPolicies
Qué pasa. Por omisión, un Pod no está aislado: acepta todo el tráfico entrante y puede abrir cualquier conexión saliente. Un Pod comprometido puede hablar con todos los demás. Además, las NetworkPolicies las aplica el plugin de red; si el tuyo no las soporta, crearlas no tiene efecto.
Cómo detectarlo. Ejecuta kubectl get networkpolicy -A: cada namespace que no aparece ahí tiene el tráfico abierto. Confirma en la documentación de tu plugin de red que soporte NetworkPolicy.
Cómo corregirlo. La guía de NSA y CISA recomienda una política por omisión que niegue todo el tráfico de entrada y salida, y luego abrir solo lo necesario. Ojo: negar la salida también bloquea el DNS, así que hay que permitirlo aparte.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: denegar-todo
namespace: app
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: permitir-dns
namespace: app
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }La etiqueta k8s-app: kube-dns es la que usa la documentación de DNS; confírmala en tu cluster.
6. Una sola réplica y sin PodDisruptionBudget
Qué pasa. Vaciar un nodo (drain) para actualizarlo es una interrupción voluntaria. Con una sola réplica y sin PodDisruptionBudget (PDB), un drain deja la aplicación sin Pods. El error contrario también existe: un PDB con maxUnavailable: 0 impide que el drain termine. Los servicios gestionados lo resuelven cada uno a su manera. En EKS, si los Pods no salen del nodo en 15 minutos, la actualización falla con PodEvictionFailure, salvo que se fuerce. En GKE, en las actualizaciones por surge, el drain respeta el PDB hasta una hora y después desaloja a la fuerza. En AKS, el tiempo de espera por omisión es de 30 minutos y la validación previa revisa PDB mal configurados.
Cómo detectarlo.
# Deployments con una sola réplica
kubectl get deploy -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,REPLICAS:.spec.replicas' | awk '$3 == 1'
# PDB existentes; ALLOWED DISRUPTIONS en 0 bloquea los drain
kubectl get pdb -ACómo corregirlo. Usa al menos dos réplicas, repartidas entre nodos o zonas con reglas de anti-afinidad, y un PDB con maxUnavailable: 1. La documentación prefiere maxUnavailable porque se ajusta solo cuando cambia el número de réplicas.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
namespace: app
spec:
maxUnavailable: 1
selector:
matchLabels:
app: api7. Respaldo de etcd que nunca se restauró
Qué pasa. Todos los objetos de Kubernetes viven en etcd. El snapshot contiene todo el estado del cluster, Secrets incluidos, así que hay que cifrarlo. Si la mayoría de los miembros de etcd falla de forma permanente, los Pods pueden seguir corriendo, pero no se pueden programar Pods nuevos. Un respaldo que nunca se restauró no está probado.
Cómo detectarlo y corregirlo en un cluster propio. Verifica que exista un snapshot reciente y restáuralo de prueba en otra máquina. Las rutas de los certificados aparecen en la descripción del Pod de etcd.
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=<ca> --cert=<cert> --key=<key> \
snapshot save /respaldos/etcd.db
etcdutl --write-out=table snapshot status /respaldos/etcd.db
etcdutl --data-dir /tmp/etcd-prueba snapshot restore /respaldos/etcd.dbEn una restauración real, la documentación indica detener todos los kube-apiserver, restaurar todas las instancias de etcd y volver a levantarlos. Corre etcd con un número impar de miembros; para producción se recomiendan cinco.
En un servicio gestionado. El proveedor opera etcd: EKS, por ejemplo, ubica tres instancias en tres zonas de disponibilidad. Lo que debes respaldar son tus recursos y volúmenes, con AWS Backup para EKS, Backup for GKE o una herramienta equivalente. Backup for GKE, por ejemplo, no respalda la configuración de los node pools ni la red. Prueba la restauración en un cluster aparte.
8. Actualizaciones atrasadas o fuera de orden
Qué pasa. El proyecto mantiene las tres versiones menores más recientes, y cada una recibe parches por unos 14 meses: 12 de soporte estándar y 2 de mantenimiento. Pasado ese plazo no hay más parches de seguridad. La política de version skew fija reglas que se rompen fácilmente: el kube-apiserver no puede saltarse versiones menores; el kubelet nunca puede ser más nuevo que el kube-apiserver y puede estar hasta tres versiones menores atrás; kubectl admite una versión de diferencia hacia arriba o hacia abajo.
Cómo detectarlo.
kubectl version
kubectl get nodes # la columna VERSION es la del kubelet
# APIs obsoletas que todavía reciben peticiones
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apisCómo corregirlo. Sube una versión menor a la vez, en este orden: plano de control, nodos (vaciándolos antes de actualizar el kubelet), clientes como kubectl y, por último, manifiestos ajustados a los cambios de API. Antes, revisa la guía de migración de APIs obsoletas; kubectl convert reescribe un manifiesto a la versión nueva. Con kubeadm, kubeadm upgrade plan muestra a qué versiones puedes subir.
9. Sin observabilidad mínima
Qué pasa. Kubernetes no almacena logs a nivel de cluster: necesitas un backend aparte. kubectl logs solo muestra el archivo vigente tras la rotación; si un Pod escribió 40 MiB y el kubelet rota cada 10 MiB, verás como máximo 10 MiB. kubectl top requiere metrics-server u otro adaptador. Y en un cluster propio, si el kube-apiserver no recibe --audit-policy-file, no se registra ningún evento de auditoría.
Cómo detectarlo.
kubectl get apiservices | grep metrics.k8s.io
kubectl top nodes
kubectl get events -A --field-selector type=WarningCómo corregirlo. El mínimo razonable: métricas de nodos y Pods (los componentes de Kubernetes las exponen en formato Prometheus en /metrics), logs enviados fuera del cluster y auditoría activada. La guía de NSA y CISA sugiere registrar las peticiones sobre Secrets solo a nivel Metadata, para no copiar su contenido a los logs. Configura alertas, como mínimo, para reinicios repetidos, OOMKilled, nodos NotReady y PDB sin interrupciones permitidas.
Lista de verificación previa a producción
Área | Verificación |
|---|---|
Recursos | Ningún Pod BestEffort; LimitRange y ResourceQuota en cada namespace |
Probes | Liveness sin dependencias externas; startup probe en apps lentas |
Imágenes | Sin |
Secretos | Cifrado en reposo activo; sin secretos en Git ni en ConfigMaps |
RBAC | Sin comodines; |
Red | Plugin compatible con NetworkPolicy; denegación por omisión y DNS permitido |
Disponibilidad | Dos o más réplicas repartidas; PDB con |
Respaldo | Snapshot de etcd (o respaldo del proveedor) cifrado y restauración probada |
Versiones | Versión menor con soporte; sin APIs obsoletas en uso; plan de actualización escrito |
Observabilidad | Métricas, logs fuera del cluster, auditoría y alertas básicas |
Fuentes
- Kubernetes · Releases y Patch Releases.
- Kubernetes · Version Skew Policy.
- Kubernetes · Pod Quality of Service Classes.
- Kubernetes · Resource Management for Pods and Containers.
- Kubernetes · Limit Ranges.
- Kubernetes · Liveness, Readiness, and Startup Probes y Configure Liveness, Readiness and Startup Probes.
- Kubernetes · Images.
- Kubernetes · Secrets y Encrypting Confidential Data at Rest.
- Kubernetes · Role Based Access Control Good Practices y kubectl auth can-i.
- Kubernetes · Network Policies y Debugging DNS Resolution.
- Kubernetes · Disruptions y Specifying a Disruption Budget.
- Kubernetes · Operating etcd clusters for Kubernetes.
- Kubernetes · Upgrade A Cluster, Deprecated API Migration Guide y Deprecation Policy.
- Kubernetes · Logging Architecture, Resource metrics pipeline, Metrics For Kubernetes System Components y Auditing.
- NSA y CISA · Kubernetes Hardening Guidance, versión 1.2 (agosto de 2022).
- AWS · Amazon EKS architecture, Managed node update behavior y Back up your EKS Clusters with AWS Backup.
- Google Cloud · GKE node pool upgrade strategies y Backup for GKE.
- Microsoft · AKS upgrade options.
Si quieres revisar esta lista con tu equipo antes de pasar a producción, conversemos.