Solución de Fallos en Servicios Systemd: Una Guía Paso a Paso
Diagnostique fallos en servicios systemd con comprobaciones de estado, registros del journal, revisión de archivos de unidad, corrección de dependencias y depuración del entorno.
Solución de Fallos en Servicios Systemd: Una Guía Paso a Paso
Los fallos en servicios systemd son más fáciles de depurar cuando te tomas el tiempo y sigues las pistas. Una unidad fallida suele dejar tres pistas útiles: el estado que systemd registró, el comando que intentó ejecutar y los registros escritos por systemd o la aplicación. Si los lees en orden, evitas la trampa común de editar un archivo de unidad antes de saber si el problema es la unidad, la aplicación, una dependencia o el host.
Los ejemplos a continuación usan un mywebapp.service ficticio, pero el mismo flujo de trabajo aplica a ayudantes de bases de datos, consumidores de colas, trabajos de respaldo, exportadores y demonios internos.
La Primera Línea de Defensa: systemctl status
Cuando un servicio falla al iniciar, el primer comando que debes ejecutar es systemctl status <nombre_del_servicio>. Este comando proporciona una instantánea del estado actual del servicio, incluyendo si está activo, cargado y, crucialmente, un fragmento de sus registros recientes. Esto a menudo proporciona suficiente información para identificar rápidamente el problema.
Supongamos que tu servicio de aplicación web, mywebapp.service, no está iniciando:
systemctl status mywebapp.service
Interpretación de la Salida de Ejemplo:
● mywebapp.service - My Web Application
Loaded: loaded (/etc/systemd/system/mywebapp.service; enabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Mon 2023-10-26 10:30:05 UTC; 10s ago
Process: 12345 ExecStart=/usr/local/bin/mywebapp-start.sh (code=exited, status=1/FAILURE)
Main PID: 12345 (code=exited, status=1/FAILURE)
CPU: 10ms
Oct 26 10:30:05 hostname systemd[1]: Started My Web Application.
Oct 26 10:30:05 hostname mywebapp-start.sh[12345]: Error: Port 8080 already in use
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Main process exited, code=exited, status=1/FAILURE
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Failed with result 'exit-code'.
De esta salida, podemos ver inmediatamente:
- El servicio
mywebapp.serviceestáfailed. - Falló con
Result: exit-code, lo que significa que el comandoExecStartsalió con un estado distinto de cero. - La línea
Processmuestra que el comandomywebapp-start.shfalló constatus=1/FAILURE. - Crucialmente, las líneas de registro indican:
Error: Port 8080 already in use. Esta es una clara indicación del problema.
Este comando es tu primera herramienta de diagnóstico, a menudo apuntando directamente a la causa o reduciendo dónde buscar a continuación.
Profundizando con journalctl
Mientras que systemctl status proporciona un resumen rápido, journalctl es tu comando de referencia para registros detallados. Consulta el diario de systemd, que recopila registros de todas las partes del sistema, incluidos los servicios.
Revisión Básica de Registros
Para ver todos los registros de un servicio específico, incluyendo entradas históricas:
journalctl -u mywebapp.service
Esto mostrará todas las entradas de registro asociadas con mywebapp.service. Si el servicio falla repetidamente, verás entradas de cada intento fallido.
Filtrado y Consultas Basadas en Tiempo
Para reducir los resultados, especialmente después de un fallo reciente, puedes usar banderas como --since y --priority:
- Mostrar registros desde una hora específica:
journalctl -u mywebapp.service --since "10 minutes ago" journalctl -u mywebapp.service --since "2023-10-26 10:00:00" - Mostrar solo mensajes de nivel de error o superiores:
journalctl -u mywebapp.service -p err - Combinar con
-xepara explicación extendida y salida detallada:journalctl -u mywebapp.service -xe --since "5 minutes ago"-xpuede agregar texto explicativo para algunos mensajes de systemd. Trata esas explicaciones como pistas, no como un reemplazo de los registros específicos de la unidad.
Entendiendo los Mensajes de Registro
Busca palabras clave como Error, Failed, Warning o mensajes específicos de la aplicación que indiquen qué salió mal. Presta atención a las marcas de tiempo para entender la secuencia de eventos que llevaron al fallo.
Consejo: Si el script ExecStart de tu servicio imprime en la salida estándar o error estándar, esos mensajes suelen ser capturados por journalctl. Asegúrate de que tus scripts registren mensajes de error descriptivos.
Inspeccionando el Archivo de Unidad: El Plano de Tu Servicio
Cada servicio systemd está definido por un archivo de unidad (ej., mywebapp.service). Las configuraciones incorrectas en este archivo son una fuente común de fallos de inicio. Necesitas entender qué está intentando hacer el servicio.
Recuperando el Archivo de Unidad
Para ver el archivo de unidad activo de tu servicio:
systemctl cat mywebapp.service
Este comando muestra el archivo de unidad exacto que systemd está usando, incluyendo cualquier anulación.
Directivas Clave a Verificar
Concéntrate en la sección [Service] para problemas relacionados con la ejecución y [Unit] para dependencias.
ExecStart: Este es el comando que systemd ejecuta para iniciar tu servicio. Verifica que la ruta sea correcta y que el comando en sí sea ejecutable y se ejecute correctamente cuando se invoca manualmente (ej., como elUserespecificado).ExecStart=/usr/local/bin/mywebapp-start.shType: Define el tipo de inicio del proceso. Los tipos comunes incluyen:simple(predeterminado):ExecStartes el proceso principal.forking:ExecStartbifurca un proceso hijo y el padre sale. Systemd espera a que el padre salga.oneshot:ExecStartse ejecuta y sale; systemd considera el servicio activo mientras el comando se esté ejecutando.notify: El servicio envía una notificación a systemd cuando está listo.- Un
Typeincorrecto puede llevar a que systemd piense que un servicio falló cuando en realidad inició, o viceversa.
User/Group: El usuario y grupo bajo los cuales se ejecutará el servicio. Los problemas de permisos a menudo provienen de que el servicio intente acceder a archivos o recursos para los que no tiene derechos bajo este usuario.User=mywebappuser Group=mywebappgroupWorkingDirectory: El directorio desde el cual se ejecutará el servicio. Las rutas relativas enExecStartu otros comandos dependen de esto.Restart: Define cuándo se debe reiniciar el servicio. Si está configurado enon-failureoalways, un servicio que falla podría reiniciarse constantemente, dificultando la captura del fallo inicial.TimeoutStartSec/TimeoutStopSec: Cuánto tiempo espera systemd para que el servicio inicie o se detenga. Si un servicio tarda más en inicializarse queTimeoutStartSec, systemd lo matará y reportará un fallo.
Problemas Comunes en Archivos de Unidad
- Rutas incorrectas: Error tipográfico en
ExecStartu otras rutas de archivo. - Variables de
Environmentfaltantes: Los servicios a menudo requieren variables de entorno específicas (ej.,PATH) que podrían no estar presentes en el entorno limpio de systemd (ver más abajo). - Permisos: El
Userespecificado no tiene permisos de ejecución para el script o permisos de lectura/escritura para los archivos de datos necesarios. - Errores de sintaxis: Simples errores tipográficos en el propio archivo de unidad.
Para probar ExecStart manualmente:
Cambia al usuario del servicio e intenta ejecutar el comando directamente:
sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh
Esto a menudo reproduce el error visto en journalctl directamente en tu terminal, facilitando la depuración.
Gestión de Dependencias: Cuando los Servicios No Pueden Iniciar Solos
Los servicios a menudo dependen de otros servicios o componentes del sistema para estar activos antes de poder iniciarse. Systemd usa las directivas Wants, Requires, After y Before para gestionar estas dependencias.
Identificando Dependencias
Usa systemctl list-dependencies <nombre_del_servicio> para ver qué requiere o quiere explícitamente un servicio para ejecutarse.
systemctl list-dependencies mywebapp.service
Directivas comunes en la sección [Unit]:
After=: Especifica que este servicio debe iniciar después de las unidades listadas. Si la unidad listada falla, este servicio aún intentará iniciar (a menos que también se useRequires=).Requires=: Especifica que este servicio requiere las unidades listadas. Si alguna de las unidades requeridas falla al iniciar, este servicio no iniciará.Wants=: Una forma más débil deRequires=. Si una unidad deseada falla, este servicio aún intentará iniciar.
Ejemplo:
[Unit]
Description=My Web Application
After=network.target mysql.service
Requires=mysql.service
Aquí, mywebapp.service está ordenado después de network.target y mysql.service, y requiere que mysql.service se inicie correctamente. Si mysql.service falla, mywebapp.service no iniciará.
Resolviendo Conflictos de Dependencia
Si un servicio falla debido a un problema de dependencia, journalctl generalmente indicará qué dependencia no pudo cumplirse. Por ejemplo, podría indicar Dependency failed for My Web Application seguido de detalles sobre el fallo de mysql.service.
Pasos para resolver:
- Verifica el servicio dependiente: Ejecuta
systemctl status <servicio_dependiente>(ej.,systemctl status mysql.service) yjournalctl -u <servicio_dependiente>para solucionar su fallo primero. - Verifica las directivas
After=yRequires=: Asegúrate de que reflejen correctamente el orden de inicio deseado y la rigurosidad. A veces, un servicio necesita esperar a que un puerto específico esté abierto, no solo a que finalice el trabajo de inicio de otra unidad. Para comprobaciones específicas,ExecStartPre=puede ayudar. Para demonios de red, la activación por socket o la lógica de reintento a nivel de aplicación suele ser más confiable.
Variables de Entorno y Rutas: Los Problemas Ocultos
Los servicios systemd se ejecutan en un entorno muy limpio y mínimo. Esto a menudo lleva a problemas donde los comandos que funcionan perfectamente en el shell de un usuario fallan cuando son ejecutados por systemd porque faltan variables de entorno cruciales (como PATH).
El Entorno Limpio de Systemd
Cuando systemd inicia un servicio, no hereda el entorno completo del usuario que inició systemctl start. La variable PATH, por ejemplo, a menudo se reduce, lo que significa que comandos como python o node podrían no encontrarse si no están en ubicaciones estándar como /usr/bin o /bin.
Síntoma: ExecStart=/usr/local/bin/myscript.sh falla con python: command not found, node: command not found, un error de biblioteca faltante o un mensaje de la aplicación que dice que una configuración requerida está vacía.
Solución: Haz explícito el entorno del servicio.
[Service]
WorkingDirectory=/opt/mywebapp
Environment="APP_ENV=production"
Environment="PATH=/opt/mywebapp/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/opt/mywebapp/venv/bin/gunicorn app:app
Para muchas variables, usa un archivo de entorno:
[Service]
EnvironmentFile=/etc/mywebapp/mywebapp.env
ExecStart=/opt/mywebapp/bin/server
Mantén ese archivo simple. EnvironmentFile= no es un script Bash. Usa líneas KEY=value, no export KEY=value, sustitución de comandos o condicionales de shell. También establece permisos restrictivos si el archivo contiene secretos:
sudo chown root:mywebapp /etc/mywebapp/mywebapp.env
sudo chmod 0640 /etc/mywebapp/mywebapp.env
Permisos: Reproduce el Fallo como el Usuario del Servicio
Los problemas de permisos son comunes porque las pruebas manuales a menudo se realizan como root o como tu usuario de inicio de sesión, mientras que la unidad se ejecuta como una cuenta de servicio dedicada.
Verifica el usuario configurado:
systemctl show mywebapp.service -p User -p Group
Luego ejecuta el mismo comando como ese usuario:
sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh
Si la aplicación necesita un directorio de trabajo, inclúyelo:
sudo -u mywebappuser bash -lc 'cd /opt/mywebapp && /usr/local/bin/mywebapp-start.sh'
Mira más allá del ejecutable. El usuario del servicio puede necesitar acceso de lectura a /etc/mywebapp/config.yml, acceso de escritura a /var/lib/mywebapp, acceso de ejecución en cada directorio padre o permiso para crear un socket Unix bajo /run/mywebapp. Una comprobación rápida puede ahorrar muchas suposiciones:
sudo -u mywebappuser test -r /etc/mywebapp/config.yml
sudo -u mywebappuser test -w /var/lib/mywebapp
namei -l /var/lib/mywebapp/uploads
Si el servicio falla solo al vincularse a un puerto bajo como 80 o 443, no lo ejecutes inmediatamente como root. Un proxy inverso, activación por socket o una capacidad específica pueden ser más seguros dependiendo del servicio.
Límites de Inicio y Bucles de Reinicio
Un servicio que falla repetidamente puede detenerse con un mensaje como start request repeated too quickly. Eso significa que el límite de velocidad de systemd se activó. El fallo original ocurrió antes, así que no te centres solo en el mensaje del límite de velocidad.
Usa:
journalctl -u mywebapp.service --since "30 minutes ago"
systemctl show mywebapp.service -p NRestarts -p Restart -p StartLimitBurst -p StartLimitIntervalUSec
Después de solucionar la causa raíz, limpia el estado fallido:
sudo systemctl reset-failed mywebapp.service
sudo systemctl start mywebapp.service
Ten cuidado con Restart=always. Es útil para demonios resilientes, pero durante la depuración puede inundar el diario y ocultar el primer error claro. Puedes detener temporalmente la unidad, revisar los registros e iniciarla manualmente una vez que hayas cambiado algo.
Valida la Unidad Antes de Recargar
Antes de reiniciar un servicio después de editar un archivo de unidad, valida el archivo y recarga systemd:
sudo systemd-analyze verify /etc/systemd/system/mywebapp.service
sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service
Si el servicio tiene anulaciones drop-in, inspecciona la versión combinada:
systemctl cat mywebapp.service
systemctl show mywebapp.service -p FragmentPath -p DropInPaths -p ExecStart
Esto detecta los casos incómodos: editaste un archivo bajo /usr/lib/systemd/system, pero un drop-in bajo /etc/systemd/system/mywebapp.service.d/override.conf aún cambia ExecStart; o arreglaste un archivo de unidad copiado que no es el que systemd cargó.
Un Orden Práctico de Operaciones
Cuando un servicio de producción está caído, usa un bucle corto y repetible:
- Ejecuta
systemctl status mywebapp.service --no-pager. - Lee
journalctl -u mywebapp.service --since "15 minutes ago". - Inspecciona
systemctl cat mywebapp.service. - Verifica el comando, usuario, directorio de trabajo, entorno y dependencias.
- Reproduce el comando como el usuario del servicio.
- Haz un cambio.
- Ejecuta
systemctl daemon-reloadsi la unidad cambió. - Reinicia y verifica el diario nuevamente.
Ese orden mantiene la investigación fundamentada. Si el diario dice Permission denied, corrige los permisos. Si dice No such file or directory, verifica las rutas desde el punto de vista de systemd. Si dice Dependency failed, depura la dependencia primero. Si dice que el proceso salió con estado 0/SUCCESS pero el servicio está fallado, verifica Type= y si la aplicación se demoniza o sale inmediatamente.
El objetivo no es memorizar cada directiva de systemd. Es seguir emparejando el mensaje de fallo con la capa que lo produjo.