Solucionar problemas de Pods y contenedores de Kubernetes

KubernetesBeginner
Practicar Ahora

Introducción

Un estado de Pod como Pending o CrashLoopBackOff es un síntoma, no la causa raíz. Una resolución eficaz de problemas debe basarse siempre en las evidencias y seguir un orden coherente: configuración del objeto, condiciones, eventos, estado de cada contenedor, registros e inspección en tiempo real.

Diagnosticarás dos fallos preparados, los corregirás y después inspeccionarás un Pod saludable con varios contenedores. El objetivo es aprender un método reutilizable de resolución de problemas, no memorizar una única solución.

Sigue el mismo orden para cada incidente: empieza con una visión general, aísla un objeto, revisa su estado actual y sus eventos, examina las evidencias específicas del contenedor y, por último, corrige el controlador responsable. Así evitarás cambios aleatorios y podrás explicar por qué funcionó una solución.

Comenzar por el estado del Pod

En este paso identificarás qué cargas de trabajo están saludables y cuáles necesitan investigación. Comienza con una vista de todo el espacio de nombres. -o wide conserva la tabla habitual, pero añade columnas útiles como la IP del Pod y el nodo asignado.

cd /home/labex/project/pod-troubleshooting
kubectl get deployments,pods -n practice-debug -o wide

El Pod saludable debería mostrar 2/2 Running. Uno de los Pods de un Deployment debería mostrar un problema al extraer la imagen, mientras que el trabajador debería reiniciarse repetidamente.

Un resultado típico del entorno de prueba es el siguiente (los sufijos de los Pods, las edades y los recuentos de reinicios serán diferentes):

NAME                                 READY   STATUS             RESTARTS   AGE
pod/broken-image-58758b4d69-c7j7g    0/1     ImagePullBackOff   0          33s
pod/crashy-worker-5557cfb974-xwpst   0/1     Error              2          33s
pod/web-with-sidecar                 2/2     Running            0          33s

No te preocupes si el trabajador muestra actualmente Error en lugar de CrashLoopBackOff. Se ejecuta brevemente, finaliza y espera antes de que Kubernetes vuelva a iniciarlo, por lo que el estado mostrado cambia mientras lo observas. El valor creciente de RESTARTS es una señal más clara de que el contenedor está fallando repetidamente.

Muestra los motivos de espera a nivel de contenedor sin tener que leer todo el YAML del Pod:

kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[*].ready,WAITING_REASON:.status.containerStatuses[*].state.waiting.reason,RESTARTS:.status.containerStatuses[*].restartCount'

[*] significa «cada elemento de la lista», por lo que esta misma tabla también funciona con Pods que tienen varios contenedores. Usa esta primera vista para decidir cuál será el siguiente comando de diagnóstico.

Por ejemplo, WAITING_REASON=ImagePullBackOff apunta a la configuración de la imagen o al acceso al registro. El trabajador puede no mostrar ningún motivo de espera mientras su último intento aparece como Terminated; por eso esta tabla es solo un punto de partida, no un diagnóstico completo.

En este momento no corrijas nada. El fallo de la imagen y el reinicio del trabajador son síntomas distintos y requieren evidencias diferentes. Un buen primer comando reduce el campo de búsqueda sin dar por supuesta la causa.

Diagnosticar un fallo al extraer una imagen

En este paso seguirás el fallo de la imagen desde el estado hasta los eventos. Guarda el nombre generado del Pod para que los comandos posteriores apunten al mismo objeto:

IMAGE_POD=$(kubectl get pods -n practice-debug -l app=broken-image -o jsonpath='{.items[0].metadata.name}')
echo "$IMAGE_POD"
kubectl describe pod "$IMAGE_POD" -n practice-debug

Lee la información desde el final hacia el principio. La sección Events debería mostrar que Kubernetes no puede extraer nginx:not-a-real-tag. Confirma directamente la imagen configurada:

kubectl get pod "$IMAGE_POD" -n practice-debug -o jsonpath='{.spec.containers[0].image}{"\n"}'
kubectl events -n practice-debug --for "pod/${IMAGE_POD}"

Los eventos explican la acción que falló, mientras que la consulta JSONPath demuestra qué imagen deseada provocó el problema. Usar ambas evidencias evita confundir un problema temporal del registro con una etiqueta permanentemente inválida.

En el entorno probado, las líneas decisivas eran similares a estas:

Image:          nginx:not-a-real-tag
State:          Waiting
  Reason:       ErrImagePull
Failed to pull image "nginx:not-a-real-tag": ... manifest unknown
Error: ImagePullBackOff

ErrImagePull registra un intento fallido de extracción. ImagePullBackOff significa que Kubernetes está introduciendo una espera antes de volver a intentar la misma operación. El mensaje manifest unknown y la etiqueta inválida relacionan el síntoma con su causa.

Corrige el Deployment en lugar de editar el Pod generado. kubectl set image modifica la plantilla del Pod, por lo que el controlador del Deployment reemplaza el Pod fallido:

kubectl set image deployment/broken-image web=nginx:1.27-alpine -n practice-debug
kubectl rollout status deployment/broken-image -n practice-debug --timeout=60s

El final correcto del comando debería mostrar:

deployment.apps/broken-image image updated
deployment "broken-image" successfully rolled out

El Pod antiguo es desechable; la plantilla del Deployment es el estado deseado persistente. Después del despliegue progresivo debería aparecer un nuevo nombre de Pod y el Deployment debería indicar una réplica disponible.

Leer los registros actuales y anteriores

En este paso diagnosticarás un contenedor que se inicia y después finaliza. Busca su Pod y revisa la información de reinicio:

WORKER_POD=$(kubectl get pods -n practice-debug -l app=crashy-worker -o jsonpath='{.items[0].metadata.name}')
kubectl get pod "$WORKER_POD" -n practice-debug
kubectl describe pod "$WORKER_POD" -n practice-debug

En un contenedor que se reinicia, kubectl logs normal lee el intento actual. --previous solicita al entorno de ejecución del contenedor el intento finalizado inmediatamente anterior:

kubectl logs "$WORKER_POD" -n practice-debug -c worker
kubectl logs "$WORKER_POD" -n practice-debug -c worker --previous || \
  echo "The previous container log is no longer available; continue with the current log and describe output."

La salida actual o anterior debería revelar el mensaje de la propia aplicación:

configuration missing

--previous resulta especialmente útil cuando un nuevo intento comienza antes de que puedas inspeccionar el que falló. Sin embargo, proporciona evidencias históricas y no garantiza que los datos sigan almacenados: el entorno de ejecución local puede haber eliminado ya el registro de ese contenedor. Si aparece unable to retrieve container logs, el mensaje alternativo es esperado. Combina el registro actual disponible con describe, cuyos campos Last State, Exit Code y Restart Count siguen demostrando que el proceso finalizó repetidamente.

La opción -c worker identifica explícitamente el contenedor. Corrige el controlador reemplazando el comando defectuoso por un bucle de larga duración:

kubectl patch deployment crashy-worker -n practice-debug --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/command","value":["sh","-c","echo worker-ready; while true; do sleep 30; done"]}]'
kubectl rollout status deployment/crashy-worker -n practice-debug --timeout=60s

El parche extenso puede parecer intimidante, así que léelo de dentro hacia fuera: path selecciona el campo command del primer contenedor, op: replace indica el tipo de cambio y value proporciona la matriz de comandos de reemplazo. Solo estás modificando un campo; el resto del Deployment permanece intacto.

El parche JSON reemplaza con precisión un único campo de la plantilla del Pod del Deployment. En producción, realiza también el cambio equivalente en el manifiesto de origen.

Espera a que termine el despliegue progresivo, porque el comando modifica inmediatamente el estado deseado, pero el reemplazo y la disponibilidad se producen de forma asíncrona. Una respuesta correcta de la API todavía no demuestra que la carga de trabajo esté saludable.

Dirigirse a contenedores concretos en un Pod con varios contenedores

En este paso inspeccionarás un Pod cuyos contenedores tienen responsabilidades diferentes. Primero muestra sus nombres:

kubectl get pod web-with-sidecar -n practice-debug -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}'

El range de JSONPath recorre la lista de contenedores. Lee los registros de cada contenedor por separado:

kubectl logs web-with-sidecar -n practice-debug -c reporter --tail=5
kubectl logs web-with-sidecar -n practice-debug -c web --tail=5

--tail=5 limita la salida a las cinco últimas líneas. Normalmente, el reportero imprime repetidamente mensajes reporter-alive. NGINX puede mostrar avisos de inicio o no mostrar todavía registros de solicitudes; ambos resultados son normales porque nadie le ha enviado una solicitud HTTP.

Ejecuta un comando dentro del contenedor web:

kubectl exec web-with-sidecar -n practice-debug -c web -- nginx -v

La imagen probada devolvió:

nginx version: nginx/1.27.5

La versión exacta del parche puede cambiar si se actualiza la imagen; la evidencia importante es que el comando se ejecutó correctamente dentro del contenedor web.

El primer -- marca el final de las opciones de kubectl. Todo lo que aparece después es el comando que se ejecuta dentro del contenedor seleccionado.

Esta distinción es importante porque -c web es una opción interpretada por kubectl, mientras que nginx -v se ejecuta dentro del contenedor. Si omites -c en un Pod con varios contenedores, kubectl puede elegir uno predeterminado y mostrar evidencias del proceso equivocado.

Los contenedores de un mismo Pod comparten su espacio de nombres de red, por lo que el reportero de BusyBox también puede solicitar NGINX mediante la dirección de retorno local. En wget -qO-, -q oculta los mensajes de progreso y -O- escribe el cuerpo de la respuesta en la salida estándar. La tubería envía ese cuerpo a head, que conserva únicamente sus primeras líneas:

kubectl exec web-with-sidecar -n practice-debug -c reporter -- wget -qO- http://127.0.0.1 | head

La respuesta debería comenzar por <!DOCTYPE html>. Este es un patrón de diagnóstico reutilizable para los próximos desafíos: ejecutar un cliente HTTP pequeño dentro de un Pod preparado y observar el comportamiento de la aplicación sin exponer un puerto externo nuevo.

Confirmar la recuperación desde varios ángulos

En este paso verificarás que la corrección de los controladores produjo Pods de reemplazo saludables. Comprueba conjuntamente la disponibilidad de los controladores y la preparación de los Pods:

kubectl get deployments,pods -n practice-debug
kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount,OWNER:.metadata.ownerReferences[0].name'

Por último, comprueba si los eventos de advertencia recientes siguen describiendo fallos activos:

--field-selector type=Warning solicita al servidor de la API únicamente los eventos de tipo Warning. Después, --sort-by=.metadata.creationTimestamp ordena los resultados de los más antiguos a los más recientes, para que puedas distinguir los fallos históricos del estado actual.

kubectl get events -n practice-debug --field-selector type=Warning --sort-by=.metadata.creationTimestamp

Las advertencias antiguas pueden permanecer después de la recuperación. Considera las condiciones actuales de los Pods y la disponibilidad de los controladores como la fuente del estado de salud vigente, mientras que los eventos explican cómo se llegó a ese estado.

Después de las dos correcciones, la salida del estado actual debería tener este formato:

NAME                            READY   UP-TO-DATE   AVAILABLE
deployment.apps/broken-image    1/1     1            1
deployment.apps/crashy-worker   1/1     1            1

NAME                                 READY   STATUS    RESTARTS
pod/broken-image-...                 1/1     Running   0
pod/crashy-worker-...                1/1     Running   0
pod/web-with-sidecar                 2/2     Running   0

Es posible que sigas viendo advertencias antiguas como Failed, BackOff o incluso una advertencia inicial FailedCreatePodSandBox procedente del arranque del clúster. Compara los nombres de los objetos y las marcas de tiempo: las advertencias se refieren a los Pods reemplazados, mientras que los nuevos Pods están en estado Running y ambos Deployments están disponibles. Los eventos son un historial, no un panel de salud en tiempo real.

La conclusión final debe combinar las evidencias actuales: ambos Deployments están disponibles, todos los contenedores previstos están preparados y no queda ningún Pod activo en estado de extracción de imagen fallida ni en un ciclo de reinicios. La resolución de problemas termina con la verificación, no con el comando de corrección.

Resumen

Has diagnosticado fallos de imagen y de arranque mediante condiciones, eventos, estado de los contenedores, registros actuales y registros anteriores. Has corregido las plantillas de los controladores en lugar de editar los Pods generados y has aprendido a dirigirte a contenedores concretos mediante logs y exec. El próximo desafío te pedirá aplicar este flujo de trabajo basado en evidencias sin una guía paso a paso.