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 kubectl configurado 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:

  1. Estado: kubectl get pods te dice en qué fase está el pod y cuántas veces se ha reiniciado.
  2. Eventos: kubectl describe pod muestra al final los eventos del planificador, del kubelet y de la descarga de imágenes. Aquí está la causa de casi todos los Pending e ImagePullBackOff.
  3. Logs: kubectl logs muestra la salida de la aplicación. Es la fuente principal en los CrashLoopBackOff.
  4. 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

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ódigoSignificadoCausa típica
1Error genérico de la aplicaciónConfiguración incorrecta, variable de entorno ausente, fallo al conectar con una dependencia.
126Comando no ejecutablePermisos del binario o del script de entrada.
127Comando no encontradocommand o args mal escritos, o binario ausente en la imagen.
137Proceso matado con SIGKILLReason: OOMKilled si superó su límite de memoria; también una sonda livenessProbe que falla.
139Violación de segmentoFallo 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 failed antes del reinicio, la aplicación no ha tenido tiempo de arrancar. Añade una startupProbe o aumenta initialDelaySeconds y failureThreshold.

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 cpu o Insufficient 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 un nodeSelector o 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 found o manifest 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"}'
  • unauthorized o authentication required: el registro es privado. Crea un Secret de tipo docker-registry con 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, solo amd64 en un nodo arm64).
  • 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 con sudo journalctl -u kubelet (en K3s, sudo journalctl -u k3s o -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.