La mayoría de incidencias en Kubernetes se repiten: un contenedor que se reinicia en bucle, un pod que no encuentra nodo, una imagen que no se descarga o un Service que no responde. En esta guía aprenderás un método de diagnóstico con kubectl y lo aplicarás a los errores más frecuentes, con la causa típica y la corrección de cada uno. Los comandos funcionan en cualquier clúster reciente, ya sea kubeadm, K3s o MicroK8s.
Requisitos previos
- Un clúster de Kubernetes y
kubectlconfigurado con permisos para leer pods, eventos y logs en el namespace afectado. Si tu clúster corre en un VPS de CubePath con Ubuntu 24.04, puedes trabajar directamente desde el nodo del plano de control. - metrics-server instalado si quieres usar
kubectl top.
En los ejemplos, your_namespace es el namespace de la aplicación y your_pod el nombre del pod. Para no repetir -n en cada comando, puedes fijar el namespace en el contexto actual:
kubectl config set-context --current --namespace=your_namespace
Paso 1: Seguir un método de diagnóstico
Ante cualquier fallo, sigue siempre el mismo orden, de lo general a lo concreto:
- Estado:
kubectl get podste dice en qué fase está el pod y cuántas veces se ha reiniciado. - Eventos:
kubectl describe podmuestra al final los eventos del planificador, del kubelet y de la descarga de imágenes. Aquí está la causa de casi todos losPendingeImagePullBackOff. - Logs:
kubectl logsmuestra la salida de la aplicación. Es la fuente principal en losCrashLoopBackOff. - Entorno: configuración, secretos, volúmenes, red y recursos del nodo.
Empieza por ver el estado de los pods:
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
api-7c9d8f6b5-2xkq9 0/1 CrashLoopBackOff 5 (40s ago) 4m 10.42.1.12 worker1
web-5f4b7d9c8-m8zlp 0/1 Pending 0 3m <none> <none>
worker-6b8c5d-p2hv4 0/1 ImagePullBackOff 0 2m 10.42.2.7 worker2
Revisa también los eventos recientes del namespace, ordenados por tiempo:
kubectl get events --sort-by=.lastTimestamp
Para ver solo los avisos, filtra por tipo:
kubectl get events --field-selector type=Warning
Notalos eventos se conservan una hora por defecto. Si el fallo es antiguo, puede que ya no aparezcan y tengas que reproducirlo.
Paso 2: Resolver un CrashLoopBackOff
CrashLoopBackOff significa que el contenedor arranca, termina y el kubelet lo reinicia esperando cada vez más tiempo entre intentos. El problema está en la aplicación o en su configuración, no en Kubernetes. Lee primero los logs del intento anterior, que son los del contenedor que falló:
kubectl logs your_pod --previous
Si el pod tiene varios contenedores, indica cuál con -c nombre_del_contenedor. Después consulta el código de salida:
kubectl describe pod your_pod | grep -A 5 "Last State"
Last State: Terminated
Reason: Error
Exit Code: 1
Started: Wed, 24 Sep 2026 10:12:03 +0000
Finished: Wed, 24 Sep 2026 10:12:04 +0000
Los códigos más habituales y lo que suelen indicar:
| Código | Significado | Causa típica |
|---|---|---|
| 1 | Error genérico de la aplicación | Configuración incorrecta, variable de entorno ausente, fallo al conectar con una dependencia. |
| 126 | Comando no ejecutable | Permisos del binario o del script de entrada. |
| 127 | Comando no encontrado | command o args mal escritos, o binario ausente en la imagen. |
| 137 | Proceso matado con SIGKILL | Reason: OOMKilled si superó su límite de memoria; también una sonda livenessProbe que falla. |
| 139 | Violación de segmento | Fallo del binario o incompatibilidad de arquitectura. |
Según la causa:
- Configuración o secretos: lista las variables que recibe el contenedor y comprueba que existen los ConfigMaps y Secrets referenciados:
kubectl set env pod/your_pod --list
kubectl get configmaps,secrets
- OOMKilled: sube el límite de memoria o reduce el consumo de la aplicación.
- Sonda de vida demasiado agresiva: si los eventos muestran
Liveness probe failedantes del reinicio, la aplicación no ha tenido tiempo de arrancar. Añade unastartupProbeo aumentainitialDelaySecondsyfailureThreshold.
Si el contenedor muere tan rápido que no hay nada que leer, crea una copia del pod que ejecute una shell en lugar del comando original y examina el sistema de archivos:
kubectl debug your_pod -it --copy-to=your_pod-debug --container=nombre_del_contenedor -- sh
Borra la copia al terminar con kubectl delete pod your_pod-debug.
Paso 3: Resolver un pod en Pending
Un pod en Pending sin IP ni nodo asignado no ha podido programarse. El planificador explica el motivo en los eventos:
kubectl describe pod your_pod | sed -n '/Events:/,$p'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 30s default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 Insufficient memory.
Los mensajes más habituales:
Insufficient cpuoInsufficient memory: la suma de requests del pod no cabe en ningún nodo. Compara lo solicitado con lo que queda en cada nodo:
kubectl describe nodes | grep -A 8 "Allocated resources"
Reduce las requests del pod si están sobredimensionadas o añade nodos al clúster.
untolerated taint: el nodo tiene un taint que el pod no tolera. Consulta los taints de los nodos:
kubectl get nodes -o custom-columns=NOMBRE:.metadata.name,TAINTS:.spec.taints
Si el pod debe ejecutarse ahí, añádele la toleration correspondiente en su especificación. Quita el taint solo si sabes por qué se puso.
didn't match Pod's node affinity/selector: el pod tiene unnodeSelectoro una afinidad que ningún nodo cumple. Compara las etiquetas de los nodos con lo que pide el pod:
kubectl get nodes --show-labels
kubectl get pod your_pod -o jsonpath='{.spec.nodeSelector}{"\n"}{.spec.affinity}{"\n"}'
unbound immediate PersistentVolumeClaims: el volumen no se ha podido aprovisionar. Revisa el PVC y la StorageClass:
kubectl get pvc
kubectl describe pvc nombre_del_pvc
kubectl get storageclass
Si el PVC no indica StorageClass y ninguna está marcada como (default), no se aprovisionará nunca.
Si el pod ni siquiera existe porque el Deployment no crea réplicas, el problema suele ser una ResourceQuota. El motivo aparece en los eventos del ReplicaSet:
kubectl describe resourcequota
kubectl get events --field-selector reason=FailedCreate
Paso 4: Resolver un ImagePullBackOff
ErrImagePull y después ImagePullBackOff indican que el kubelet no puede descargar la imagen. El mensaje exacto está en los eventos:
kubectl describe pod your_pod | grep -A 3 "Failed"
Warning Failed 15s kubelet Failed to pull image "registry.example.com/app:1.4": ... not found
Warning Failed 15s kubelet Error: ErrImagePull
Según el mensaje:
not foundomanifest unknown: la imagen o la etiqueta no existen. Comprueba el nombre exacto que usa el pod y verifica que la etiqueta está publicada en el registro:
kubectl get pod your_pod -o jsonpath='{.spec.containers[*].image}{"\n"}'
unauthorizedoauthentication required: el registro es privado. Crea un Secret de tipodocker-registrycon credenciales de solo lectura (sustituye los valores de ejemplo por los tuyos):
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=your_registry_user \
--docker-password=your_registry_token
Y referéncialo en la plantilla del pod, al mismo nivel que containers:
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.example.com/app:1.4
toomanyrequests: has superado el límite de descargas anónimas de Docker Hub. Autentícate con un Secret como el anterior o usa un registro espejo.no match for platform in manifest: la imagen no está publicada para la arquitectura del nodo (por ejemplo, soloamd64en un nodoarm64).- Timeouts o errores de DNS: el nodo no llega al registro. Compruébalo desde el propio nodo con
curl -I https://registry.example.com/v2/y revisa el registro del runtime consudo journalctl -u kubelet(en K3s,sudo journalctl -u k3so-u k3s-agent).
Paso 5: Diagnosticar DNS y Services
Si una aplicación no puede conectar con otra dentro del clúster, comprueba primero la resolución DNS. La documentación de Kubernetes proporciona un pod de pruebas con herramientas de DNS:
kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml
kubectl exec -it dnsutils -- nslookup kubernetes.default
Server: 10.43.0.10
Address: 10.43.0.10#53
Name: kubernetes.default.svc.cluster.local
Address: 10.43.0.1
Este pod se crea en el namespace default. Si la resolución falla, revisa CoreDNS:
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
Si los pods de CoreDNS están caídos o con errores de conexión hacia arriba, reinícialos:
kubectl -n kube-system rollout restart deployment/coredns
Si el DNS funciona pero el Service no responde, lo habitual es que su selector no coincida con las etiquetas de los pods y no tenga endpoints:
kubectl get service nombre_del_servicio -o jsonpath='{.spec.selector}{"\n"}'
kubectl get endpointslices -l kubernetes.io/service-name=nombre_del_servicio
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
nombre_del_servicio-8xk2d IPv4 <unset> <unset> 5m
Unos endpoints vacíos indican que ningún pod listo tiene esas etiquetas. Compáralas con kubectl get pods --show-labels. Si los pods existen pero no están READY, el problema es su readinessProbe. Comprueba también que el targetPort del Service coincide con el puerto en el que escucha el contenedor.
Para probar la conectividad desde dentro del clúster, lanza un pod temporal con herramientas de red:
kubectl run nettest --rm -it --image=nicolaka/netshoot -- curl -sv http://nombre_del_servicio.your_namespace.svc.cluster.local:80
Si la conexión se bloquea aunque haya endpoints, revisa si hay NetworkPolicies que la impidan:
kubectl get networkpolicies -A
Cuando termines, elimina el pod de DNS:
kubectl delete pod dnsutils
Paso 6: Depurar un contenedor en ejecución
Las imágenes de producción a menudo no incluyen shell ni herramientas. kubectl debug añade un contenedor efímero al pod que comparte su espacio de procesos y red, sin reiniciarlo:
kubectl debug -it your_pod --image=busybox:stable --target=nombre_del_contenedor
Desde esa shell puedes ver los procesos del contenedor objetivo con ps y probar la red con wget o nc. Para acceder al servicio desde tu equipo sin exponerlo, usa un reenvío de puertos:
kubectl port-forward pod/your_pod 8080:80
Y en otra terminal:
curl -I http://localhost:8080
Si sospechas del nodo (disco lleno, runtime colgado), puedes abrir una shell en él con un pod privilegiado; el sistema de archivos del nodo queda montado en /host:
kubectl debug node/your_node_name -it --image=busybox:stable
Elimina el pod de depuración que crea este comando cuando termines (kubectl get pods lo muestra con el prefijo node-debugger-).
Paso 7: Revisar el estado de los nodos
Si muchos pods fallan a la vez, el problema suele estar en un nodo. Comprueba su estado y sus condiciones:
kubectl get nodes
kubectl describe node your_node_name | sed -n '/Conditions:/,/Addresses:/p'
Las condiciones MemoryPressure, DiskPressure o PIDPressure a True explican desalojos masivos de pods (estado Evicted). Un nodo NotReady suele indicar que el kubelet está parado o no llega a la API. En el propio nodo, revisa el servicio:
sudo systemctl status kubelet
sudo journalctl -u kubelet -n 100 --no-pager
En K3s el servicio es k3s en el servidor y k3s-agent en los workers; en MicroK8s, ejecuta sudo microk8s inspect.
Conclusión
Con el orden estado, eventos, logs y entorno puedes localizar la causa de la mayoría de fallos de Kubernetes: los eventos explican los Pending e ImagePullBackOff, los logs del intento anterior explican los CrashLoopBackOff, y los endpoints y el DNS explican los Services que no responden. Como siguientes pasos, define requests y limits en todas tus cargas para evitar OOMKilled y desalojos, configura sondas de arranque y de preparación realistas, y centraliza logs y métricas para conservar el contexto cuando los eventos ya han caducado.
