Dominando los Archivos de Servicio de Systemd: Una Guía Completa
Crea archivos de servicio systemd confiables con secciones de unidad correctas, comportamiento de reinicio, registros, seguridad y temporizadores.
Dominando los Archivos de Servicio de Systemd: Una Guía Completa
Los archivos de servicio de Systemd le indican a Linux cómo iniciar, detener, reiniciar y supervisar tu aplicación. Si tu servicio se inicia manualmente pero falla al arrancar, se reinicia de forma demasiado agresiva o escribe registros en el lugar incorrecto, el archivo de unidad suele ser donde debes buscar.
Esta guía se centra en crear y configurar archivos de unidad de servicio systemd desde cero. Verás las secciones principales, un ejemplo de servicio Python en funcionamiento, comandos comunes de solución de problemas y algunos controles de seguridad y recursos que vale la pena usar en producción.
Entendiendo los Archivos de Unidad de Systemd
Systemd utiliza archivos de unidad para describir varios recursos del sistema, como servicios, sockets, dispositivos, puntos de montaje y más. Un archivo de unidad de servicio, que normalmente termina con la extensión .service, define cómo systemd debe gestionar un demonio o aplicación específica.
Estos archivos están organizados en secciones, y cada sección contiene pares clave-valor que representan directivas de configuración. Las secciones principales en las que nos centraremos son [Unit], [Service] e [Install].
Anatomía de un Archivo de Servicio de Systemd
Un archivo de servicio systemd típico tiene la siguiente estructura:
[Unit]
Description=Una breve descripción del servicio.
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/mi_aplicacion --config /etc/mi_app.conf
Restart=on-failure
User=mi_usuario
Group=mi_grupo
[Install]
WantedBy=multi-user.target
Desglosemos cada sección y sus directivas comunes:
La Sección [Unit]
Esta sección proporciona metadatos sobre la unidad y define su relación con otras unidades. Se utiliza para dependencias y ordenamiento.
Description=: Un nombre legible para el servicio. Esto es lo que verás en la salida desystemctl status.Documentation=: URLs o rutas a la documentación del servicio.Requires=: Define dependencias fuertes. Si una unidad listada aquí falla al iniciar, esta unidad también fallará al iniciar.Wants=: Define dependencias débiles. Si una unidad listada aquí falla al iniciar, esta unidad aún intentará iniciarse.Before=: Asegura que esta unidad se inicie antes que las unidades listadas.After=: Controla solo el ordenamiento. Por ejemplo,After=network.targetinicia esta unidad después del objetivo de red básico, pero no garantiza la conectividad externa. Los servicios dependientes de la red pueden necesitarAfter=network-online.targetmás el servicio de espera en línea de la distribución.Conflicts=: Si se inicia una unidad listada aquí, esta unidad se detendrá, y viceversa.
La Sección [Service]
Esta sección configura el comportamiento del servicio en sí. Aquí es donde defines cómo iniciar, detener y gestionar el proceso.
Type=: Especifica el tipo de inicio del proceso. Los valores comunes incluyen:simple(predeterminado): El proceso principal es el especificado enExecStart=. Systemd asume que el servicio se inicia inmediatamente después de que se bifurca el procesoExecStart=.forking: El procesoExecStart=bifurca un hijo y el padre sale. Systemd considera que el servicio se ha iniciado cuando el padre sale. A menudo necesitas especificarPIDFile=con este tipo.oneshot: Similar asimple, pero se espera que el proceso salga después de completar su trabajo. Útil para scripts de configuración.notify: El demonio envía un mensaje de notificación a systemd cuando se ha iniciado correctamente. Este es el tipo preferido para demonios modernos que lo soportan.dbus: El servicio adquiere un nombre de D-Bus.
ExecStart=: El comando a ejecutar para iniciar el servicio. Para la mayoría de los tipos de servicio, usa un comandoExecStart=. Múltiples líneasExecStart=son válidas paraType=oneshot, donde se ejecutan secuencialmente.ExecStop=: El comando a ejecutar para detener el servicio.ExecReload=: El comando a ejecutar para recargar la configuración del servicio sin reiniciar.Restart=: Define cuándo se debe reiniciar automáticamente el servicio. Valores comunes:no(predeterminado): Nunca reiniciar.on-success: Reiniciar solo si el servicio sale limpiamente (código de salida 0).on-failure: Reiniciar si el servicio sale con un código de salida distinto de cero, es terminado por una señal o se agota el tiempo de espera.on-abnormal: Reiniciar si es terminado por una señal o se agota el tiempo de espera.on-abort: Reiniciar solo si es terminado de forma no limpia por una señal.always: Reiniciar siempre, independientemente del estado de salida.
RestartSec=: El tiempo de espera antes de reiniciar el servicio (predeterminado es 100ms).User=: El usuario bajo el cual ejecutar el servicio.Group=: El grupo bajo el cual ejecutar el servicio.WorkingDirectory=: El directorio al que cambiar antes de ejecutar los comandos.Environment=: Establece variables de entorno para el servicio.EnvironmentFile=: Lee variables de entorno desde un archivo.PIDFile=: Ruta al archivo PID (a menudo usado conType=forking).StandardOutput=/StandardError=: Controla hacia dónde van stdout y stderr, comojournal,nulloinherit. En distribuciones comunes basadas en systemd, la salida del servicio normalmente termina en el diario a menos que los valores predeterminados del gestor se hayan cambiado.
La Sección [Install]
Esta sección define cómo se debe habilitar o deshabilitar la unidad, típicamente creando enlaces simbólicos.
WantedBy=: Especifica el objetivo que debería "querer" este servicio cuando está habilitado. Valores comunes:multi-user.target: Para servicios que deben iniciarse cuando el sistema alcanza un estado de línea de comandos multiusuario.graphical.target: Para servicios que deben iniciarse cuando el sistema alcanza un estado de inicio de sesión gráfico.
Creando Tu Primer Archivo de Servicio de Systemd
Vamos a crear un archivo de servicio simple para un script Python hipotético llamado mi_app.py ubicado en /opt/mi_app/mi_app.py.
1. Crea el archivo de servicio:
Los archivos de servicio para aplicaciones personalizadas se colocan típicamente en /etc/systemd/system/. Nombremos nuestro archivo mi_app.service.
# Crea el directorio si no existe
sudo mkdir -p /etc/systemd/system/
# Crea el archivo de servicio usando un editor de texto
sudo nano /etc/systemd/system/mi_app.service
2. Agrega el siguiente contenido a mi_app.service:
[Unit]
Description=Mi Aplicación Python Personalizada
After=network.target
[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/mi_app/
ExecStart=/usr/bin/python3 /opt/mi_app/mi_app.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
Explicación del ejemplo:
Description: Identifica claramente nuestra aplicación.After=network.target: Asegura que la red esté disponible antes de iniciar.Type=simple: Asume quemi_app.pyes el proceso principal y no se bifurca.User=appuser,Group=appgroup: Especifica el usuario y grupo bajo los cuales debe ejecutarse la aplicación. Asegúrate de que estos usuarios y grupos existan en tu sistema y tengan los permisos adecuados. Es posible que necesites crearlos:sudo groupadd appgroup sudo useradd -r -g appgroup appuser sudo chown -R appuser:appgroup /opt/mi_app/WorkingDirectory: Establece el contexto para el script.ExecStart: El comando para ejecutar el script Python. Asegúrate de que/usr/bin/python3sea la ruta correcta a tu intérprete de Python y que el script sea ejecutable.Restart=on-failure: Si el script falla, systemd intentará reiniciarlo.WantedBy=multi-user.target: Este servicio se iniciará automáticamente cuando el sistema arranque en un entorno multiusuario.
3. Recarga la configuración del gestor de systemd:
Después de crear o modificar un archivo de servicio, debes indicarle a systemd que recargue su configuración.
sudo systemctl daemon-reload
4. Habilita e Inicia el Servicio:
- Habilitar: Esto hace que el servicio se inicie automáticamente al arrancar.
sudo systemctl enable mi_app.service - Iniciar: Esto inicia el servicio inmediatamente.
sudo systemctl start mi_app.service
5. Verifica el Estado del Servicio:
Para verificar si tu servicio se está ejecutando y ver posibles errores:
sudo systemctl status mi_app.service
Si hay problemas, el comando status a menudo mostrará mensajes de error o registros de journald.
6. Visualizando Registros:
Systemd se integra con journald para el registro. Puedes ver los registros de tu servicio usando:
sudo journalctl -u mi_app.service
También puedes seguir los registros en tiempo real:
sudo journalctl -f -u mi_app.service
Otros Comandos Útiles
- Detener el servicio:
sudo systemctl stop mi_app.service - Reiniciar el servicio:
sudo systemctl restart mi_app.service - Recargar configuración (si es compatible con la aplicación):
sudo systemctl reload mi_app.service - Deshabilitar el inicio automático al arrancar:
sudo systemctl disable mi_app.service
Configuración Avanzada y Mejores Prácticas
Consideraciones de Seguridad
- Ejecuta servicios como usuarios no root: Siempre especifica
User=yGroup=a menos que sea absolutamente necesario. Esto sigue el principio de privilegio mínimo. - Aísla servicios: Considera funciones de sandboxing como
PrivateTmp=true,ProtectSystem=strict,ProtectHome=trueyNoNewPrivileges=true. Pruébalas con tu aplicación porque pueden bloquear escrituras de archivos legítimas.PrivateTmp=true: Le da al servicio sus propios directorios privados/tmpy/var/tmp.ProtectSystem=strict: Hace que la mayor parte del sistema de archivos sea de solo lectura para el servicio. UsaReadWritePaths=para los directorios en los que el servicio debe escribir.NoNewPrivileges=true: Evita que el servicio obtenga nuevos privilegios.
Manejando Inicios Complejos
Type=forkingconPIDFile=: Para aplicaciones más antiguas que se bifurcan, asegúrate de quePIDFile=apunte al archivo correcto.Type=notify: Si tu aplicación lo soporta, esta es la forma más robusta para que systemd sepa cuándo está realmente lista.ExecStartPre=yExecStartPost=: Comandos para ejecutar antes y después deExecStart=. Útiles para tareas de configuración o limpieza.
Control de Recursos
Systemd te permite limitar el uso de recursos:
CPUWeight=: Peso relativo de CPU para el servicio.MemoryMax=: Memoria máxima que el servicio puede usar.IOWeight=: Peso relativo de E/S donde sea compatible con el kernel y la configuración de cgroup.
Ejemplo:
[Service]
# ... otras directivas ...
MemoryMax=512M
CPUWeight=50
Temporizadores vs. Cron
Los temporizadores de Systemd ofrecen una alternativa moderna a los trabajos cron tradicionales. Son más flexibles y se integran mejor con la gestión de registros y dependencias de systemd.
- Cron: Tareas programadas definidas en archivos
crontab. - Temporizadores de Systemd (unidades
.timer): Estas unidades programan unidades.service. Defines un archivo.timerque especifica cuándo debe ejecutarse un archivo.servicecorrespondiente.
Ejemplo:
Para ejecutar un script diariamente a las 3 AM:
mi_script.service: El servicio a ejecutar.[Unit] Description=Mi script diario [Service] Type=oneshot ExecStart=/opt/mis_scripts/ejecutar_diario.sh User=scriptusermi_script.timer: El temporizador que programa el servicio.[Unit] Description=Ejecutar mi script diario una vez al día [Timer] # Ejecutar a las 03:00 todos los días OnCalendar=*-*-* 03:00:00 # Ejecutar poco después del arranque si se perdió la hora programada mientras la máquina estaba apagada. Persistent=true [Install] WantedBy=timers.target
Para usar esto:
- Coloca ambos archivos en
/etc/systemd/system/. - Ejecuta
sudo systemctl daemon-reload. - Habilita e inicia el temporizador:
sudo systemctl enable mi_script.timerysudo systemctl start mi_script.timer.
Los temporizadores ofrecen ventajas como Persistent=true (ejecuta trabajos perdidos al arrancar), eventos de calendario (como hourly, daily, weekly) y una mejor integración con journalctl.
Solución de Problemas Comunes
- El servicio no se inicia: Verifica
systemctl status <nombre_servicio>yjournalctl -u <nombre_servicio>. Busca errores tipográficos, rutas incorrectas, dependencias faltantes o errores de permisos. Type=incorrecto: Si un servicio falla inmediatamente o se cuelga, elType=podría ser incorrecto. Pruebasimpleoforkingy asegúrate de quePIDFilesea correcto si usasforking.- Permiso denegado: Asegúrate de que el
User=yGroup=especificados tengan acceso de lectura/escritura a los archivos y directorios necesarios. - Variables de entorno: Si tu aplicación depende de variables de entorno específicas, asegúrate de que estén configuradas correctamente usando
Environment=oEnvironmentFile=. - Dependencias: Verifica que
After=,Wants=yRequires=coincidan con lo que quieres decir.After=ordena el inicio; no atrae otra unidad por sí mismo.
Antes de habilitar una nueva unidad en un host de producción, ejecuta:
sudo systemd-analyze verify /etc/systemd/system/mi_app.service
Esto detecta muchos errores de sintaxis y directivas antes de que confíes en el servicio al arrancar.
Conclusión Clave
Escribe el archivo de servicio más pequeño que describa con precisión tu aplicación, luego agrega política de reinicio, registro, restricciones de seguridad y límites de recursos deliberadamente. Después de cada cambio, ejecuta systemctl daemon-reload, verifica la unidad y comprueba systemctl status más journalctl -u antes de confiar en él en producción.