Marcio Cunha

¿Contenedor reiniciando solo? Cómo debugar el Exit Code

Conoce el significado real de los códigos de salida de Docker como 137, 139 y 111. Rastrea logs de kernel y el PID 1 para identificar fallas de memoria.

Marcio Cunha•5 min
También disponible en:PortuguêsEnglish
Resumen
  • El código de salida 137 indica que el proceso principal superó su límite de memoria y fue terminado por el kernel de Linux.
  • El código 139 señala un fallo de segmentación, ocurriendo cuando un programa intenta acceder a memoria inválida.
  • El PID 1 dentro del contenedor tiene la responsabilidad central de gestionar señales del sistema operativo.
  • Herramientas de observabilidad como el rastreo del OOM Killer ayudan a diagnosticar fallas críticas.
  • El uso correcto de comandos de parada y health checks previene bucles de reinicio indeseados en producción.

¿Por qué tu contenedor de Docker se reinicia solo?

Cuando un contenedor de Docker se apaga de repente y entra en un bucle infinito de reinicios, la primera reacción suele ser el pánico. En la práctica, Docker no apaga un sistema por capricho; siempre deja una pista valiosa sobre lo ocurrido justo antes de la clausura. Esa pista es el Exit Code, o código de salida, un número devuelto por el proceso cuando deja de funcionar. Comprender estos números transforma un error misterioso en un diagnóstico claro, permitiéndote resolver la raíz del problema en lugar de reiniciar el servicio rezando para que dure más tiempo.

Para investigar a fondo, necesitamos mirar más allá del comando básico 'docker ps'. El motor de Docker conversa directamente con el núcleo del sistema operativo para gestionar el aislamiento de procesos. Cuando algo sale de control —sea falta de memoria RAM, un error de programación de bajo nivel o una conexión rechazada—, el kernel interviene y termina la aplicación abruptamente. Antes de analizar los detalles, vale la pena recordar que gestionar fallas de infraestructura exige paciencia y herramientas adecuadas, al igual que cuando investigamos fallas físicas de hardware, tal como discutimos en nuestra guía sobre cómo probar circuito integrado en la placa con osciloscopio.

Descifrando los códigos de salida más comunes: 137, 139 y 111

Un código de salida es un número entero que el programa entrega al sistema operativo al terminar su ejecución. En el universo Docker, ciertos números aparecen tan frecuentemente que merecen un lugar permanente en la memoria de cualquier ingeniero. El infame código 137, por ejemplo, es la señal clásica de que el proceso fue ejecutado sumariamente por falta de memoria. En la práctica, esto significa que la aplicación pidió más RAM de la permitida, y el OOM Killer cortó el problema de raíz para salvar el servidor. Otro código frecuente es el 139, que representa un fallo de segmentación, ocurriendo cuando un software intenta leer o escribir en una dirección de memoria prohibida o corrupta.

El código 111 suele aparecer en escenarios de conexión rechazada, indicando que la aplicación intentó comunicarse con una base de datos o API externa que aún no estaba lista. Para inspeccionar el código de salida exacto de un contenedor que acaba de fallar, puedes ejecutar el siguiente comando en tu terminal:

docker inspect --format='{{.State.ExitCode}}' nombre_del_contenedor

Este sencillo comando lee directamente la estructura de metadatos de Docker y devuelve el número exacto, ahorrando tiempo de investigación y dirigiéndote directamente a la categoría correcta del error.

El papel crítico del PID 1 y la gestión de señales

Dentro de un contenedor, el primer proceso iniciado recibe el identificador PID 1. En la teoría de sistemas Unix, el PID 1 carga con una responsabilidad inmensa: funciona como el 'jefe' de todos los demás procesos generados allí dentro, encargándose de recolectar procesos huérfanos y pasar señales vitales, como el comando de terminación SIGTERM. Cuando escribimos instrucciones de Dockerfile incorrectamente, a menudo colocamos un script simple o un intérprete en el PID 1 que no sabe manejar señales del sistema operativo adecuadamente. El resultado práctico es que al intentar detener el contenedor, este ignora la advertencia educada y debe ser eliminado a la fuerza tras diez segundos.

Para prevenir este comportamiento, es fundamental utilizar el formato de instrucción 'exec' en tu Dockerfile o contar con gestores de procesos ligeros que reenvíen las señales correctamente. Cuando un contenedor se congela por completo y no responde ni a los comandos normales, los operadores deben recurrir a herramientas de fuerza bruta en la línea de comandos, un escenario que cubrimos detalladamente en ¿Contenedor de Docker bloqueado? Cómo forzar parada via CLI. Garantizar que el PID 1 sea consciente de su rol previene corrupción de datos y caídas de servicios críticos.

Rastreando registros del kernel e identificando el OOM Killer

A menudo, los registros tradicionales de tu aplicación (obtenidos con 'docker logs') se detienen de repente sin mostrar errores explícitos antes de un fallo. Esto sucede porque el problema ocurrió fuera del alcance del lenguaje de programación, directamente a nivel del sistema operativo. Cuando el consumo de memoria supera el límite del cgroup del contenedor, el kernel de Linux actúa en silencio. Para confirmar si tu contenedor fue víctima de esta ejecución sumaria, debes consultar el registro general de eventos del sistema host usando el comando dmesg en la terminal:

dmesg -T | grep -i oom

Este comando filtra el historial del kernel mostrando los eventos de falta de memoria junto con marcas de tiempo legibles. Al analizar la salida, verás exactamente qué proceso fue sacrificado y cuál era el consumo en ese instante. Identificar este patrón permite ajustar los límites de recursos en tu archivo Docker Compose, evitando nuevas interrupciones en producción.

Cómo prevenir bucles de reinicio y garantizar resiliencia

Descubrir por qué ocurrió un fallo es solo la mitad del trabajo; la otra mitad es asegurar que el sistema se mantenga estable y previsible. Las políticas de reinicio automático como 'restart: always' son excelentes para mantener servicios en línea tras un fallo repentino, pero pueden enmascarar problemas estructurales graves, creando un ciclo vicioso de consumo de CPU y reincidencia de errores. El enfoque ideal es combinar límites de memoria realistas, estrategias de monitoreo adecuadas y pruebas de carga para validar el comportamiento antes de pasar a producción.

En resumen, dominar la lectura de los códigos de salida y entender la dinámica entre el contenedor y el kernel de Linux elimina las conjeturas al depurar problemas de infraestructura. Al monitorear el PID 1, interpretar correctamente códigos como 137 y 139, y consultar el dmesg cuando sea necesario, ganas autonomía total para diagnosticar y corregir cualquier fallo en tus entornos contenerizados con rapidez y precisión.