Dominando Systemd: Creando tu Primer Archivo de Unidad de Servicio Personalizado
Aprende los fundamentos de la gestión de servicios de Systemd creando un Archivo de Unidad personalizado. Este tutorial desglosa las secciones esenciales `[Unit]`, `[Service]` y `[Install]`, proporcionando instrucciones paso a paso para definir, habilitar, iniciar y verificar un servicio básico en segundo plano en Linux usando `systemctl`.
Dominando Systemd: Creando tu Primer Archivo de Unidad de Servicio Personalizado
Una unidad de servicio personalizada de systemd es lo que usas cuando un script o una pequeña aplicación ha superado una sesión de terminal, una ventana de screen o un frágil workaround de cron. Tal vez tengas un worker que debería reiniciarse después de un fallo. Tal vez una pequeña API interna necesita iniciarse después de que la red esté lista. Tal vez un script de respaldo debería ejecutarse como un servicio controlado para que sus registros vivan en el journal y los operadores puedan usar los mismos comandos systemctl que usan para todo lo demás.
La parte útil de systemd no es que el archivo de unidad sea complicado. Es que el archivo de unidad hace explícito el proceso: qué se ejecuta, quién lo ejecuta, cuándo inicia, cómo se detiene, dónde van los registros y qué debe hacer systemd cuando falla. Una vez que esas decisiones están escritas, el servicio se vuelve mucho más fácil de operar.
Este tutorial construye un pequeño servicio desde cero. El ejemplo es intencionalmente simple, pero los patrones son los mismos que usarías para un worker en segundo plano, un consumidor de colas, un exportador de métricas o un daemon interno.
Comienza con un comando real, no un archivo de unidad
Una buena unidad de servicio comienza con un comando que ya funciona manualmente. Antes de escribir la configuración de systemd, asegúrate de poder ejecutar el programa directamente y entender qué hace en primer plano.
Para este ejemplo, crea un pequeño script reportero:
sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh
Agrega este contenido:
#!/usr/bin/env bash
set -euo pipefail
while true; do
echo "$(date --iso-8601=seconds) reporter heartbeat"
sleep 10
done
Hazlo ejecutable y pruébalo:
sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh
Detenlo con Ctrl-C después de ver algunas líneas. Observa que el script escribe en la salida estándar en lugar de agregar directamente a /var/log/reporter.log. Eso es deliberado. Para la mayoría de los servicios personalizados, permitir que systemd capture stdout y stderr en el journal es más limpio que hacer que cada script gestione sus propios permisos de archivo de registro, rotación y comportamiento de fallo.
Crea un usuario de servicio dedicado
Evita ejecutar servicios de aplicación como root a menos que realmente necesiten privilegios de root. Un script de heartbeat no los necesita. Una aplicación web generalmente no. Un worker que lee de una cola y escribe en una base de datos generalmente no.
Crea un usuario de sistema bloqueado:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter
Si tu distribución usa una ruta diferente para nologin, verifícala con:
command -v nologin || command -v false
El usuario del servicio debe poseer solo los archivos que necesita escribir. En este ejemplo, el script escribe en el journal a través de systemd, por lo que no necesita ser propietario de /opt/my-custom-service.
Escribe la unidad de servicio
Las unidades de sistema personalizadas administradas por el administrador normalmente viven en /etc/systemd/system/. Las unidades de paquetes de proveedores comúnmente viven en /usr/lib/systemd/system/ o /lib/systemd/system/, dependiendo de la distribución. No edites los archivos de unidad del proveedor directamente cuando puedas evitarlo; usa /etc/systemd/system/ para tus propias unidades y drop-ins para anulaciones.
Crea la unidad:
sudo nano /etc/systemd/system/my-reporter.service
Usa esto como una primera versión práctica:
[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
La sección [Unit] describe las relaciones. After=network-online.target controla el orden; no activa el target network-online por sí mismo. Wants=network-online.target le pide a systemd que inicie ese target también. Si tu servicio no necesita la red, elimina ambas líneas y mantén la unidad más simple.
La sección [Service] describe el proceso. Type=simple es adecuado para un proceso en primer plano que no se bifurca a sí mismo en segundo plano. Ese es el caso común para los servicios modernos. Si un daemon heredado se bifurca, escribe un archivo PID y devuelve el control al shell, entonces puedes necesitar Type=forking, pero no lo uses solo porque la palabra suena más a daemon.
ExecStart debe ser una ruta absoluta. Las características del shell como pipes, redirecciones y && no se interpretan a menos que ejecutes explícitamente un shell, por ejemplo ExecStart=/bin/bash -lc 'command one && command two'. Prefiere un script cuando el comando necesite lógica de shell; es más fácil de probar y más fácil de leer.
Restart=on-failure le dice a systemd que reinicie el servicio después de salidas anormales. No se reiniciará después de un systemctl stop limpio. RestartSec=5s evita que un bucle de reinicio apretado golpee la máquina.
Las opciones de hardening aquí son modestas pero útiles. NoNewPrivileges=true evita que el proceso y sus hijos obtengan nuevos privilegios a través de binarios setuid o capacidades de archivo. PrivateTmp=true le da al servicio una vista privada de /tmp. Estas suelen ser seguras para servicios simples, pero pruébalas con aplicaciones reales porque algunos software esperan rutas temporales compartidas.
Carga e inicia la unidad
Después de agregar o cambiar un archivo de unidad, recarga la configuración del administrador de systemd:
sudo systemctl daemon-reload
Inicia el servicio ahora:
sudo systemctl start my-reporter.service
Verifica su estado:
systemctl status my-reporter.service
Quieres ver Active: active (running). Si falló, no adivines. Lee los registros:
journalctl -u my-reporter.service -n 50 --no-pager
Sigue los registros en vivo mientras pruebas:
journalctl -u my-reporter.service -f
Si la ruta del script es incorrecta, faltan permisos, el usuario no existe o el comando sale inmediatamente, systemd generalmente lo dirá claramente en el journal.
Habilita el inicio al arrancar
Iniciar un servicio y habilitar un servicio son acciones diferentes. start lo ejecuta ahora. enable lo conecta al target de arranque para que se inicie en futuros arranques.
sudo systemctl enable my-reporter.service
Puedes hacer ambos en un solo comando después de que la unidad haya sido probada:
sudo systemctl enable --now my-reporter.service
Para ver si está habilitado:
systemctl is-enabled my-reporter.service
Haz que los fallos sean más fáciles de diagnosticar
Los fallos más comunes en el primer servicio son problemas ordinarios de Linux disfrazados de systemd.
Si ves status=203/EXEC, systemd no pudo ejecutar el comando. Verifica la ruta, el bit ejecutable, la línea shebang y los finales de línea. Un script copiado de Windows con finales de línea CRLF puede fallar aunque se vea bien en un editor.
Si ves errores de permiso, recuerda que el servicio se ejecuta como reporter, no como tu usuario de shell. Prueba con:
sudo -u reporter /opt/my-custom-service/reporter.sh
Si el servicio se inicia y se detiene inmediatamente, el proceso probablemente sale. Type=simple espera que el comando siga ejecutándose. Un comando de configuración de una sola vez debe usar Type=oneshot, no simple.
Si faltan registros, verifica si la aplicación escribe en archivos en lugar de stdout/stderr, o si cambia de usuario internamente. Para la mayoría de los servicios pequeños, escribir en stdout es la opción menos sorprendente.
Comandos de gestión útiles
Una vez que la unidad está en su lugar, la operación diaria es sencilla:
sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart
systemctl cat es especialmente útil en máquinas con anulaciones drop-in porque muestra los fragmentos de unidad efectivos que systemd está leyendo.
Un archivo de unidad personalizado no necesita ser ingenioso. Necesita ser aburrido, explícito y comprobable. Haz que el comando funcione manualmente, ejecútalo como un usuario dedicado, escribe la unidad más pequeña que describa el servicio con precisión, recarga systemd y usa el journal cuando algo falle. Ese flujo de trabajo escala desde un script reportero de juguete hasta daemons de producción reales.
Agrega entorno y configuración de manera limpia
Tarde o temprano el servicio necesita configuración: un puerto, una URL de base de datos, un flag de funcionalidad o una ruta. Evita enterrar esos valores dentro del archivo de unidad cuando varían según el entorno. Un patrón común es un archivo de entorno:
sudo nano /etc/my-reporter.env
Ejemplo:
REPORT_INTERVAL=10
REPORT_LABEL=production
Bloquea el archivo si contiene algo sensible:
sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env
Luego refiérelo desde la unidad:
[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh
En el script, lee la variable con un valor predeterminado:
interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"
Para secretos, ten cuidado. Una variable de entorno puede exponerse a través de la inspección del proceso o metadatos del servicio dependiendo de la configuración del sistema y los permisos. Para valores altamente sensibles, prefiere un gestor de secretos adecuado, un archivo de credenciales con permisos estrictos o las características de credenciales más nuevas de systemd si tu distribución las soporta. El hábito importante es decidir deliberadamente en lugar de esparcir contraseñas en los archivos de unidad porque es conveniente.
Usa anulaciones drop-in para cambios locales
Si un paquete instala una unidad y necesitas cambiar una configuración, no edites el archivo del proveedor. Usa un drop-in:
sudo systemctl edit my-reporter.service
Eso abre un archivo de anulación en /etc/systemd/system/my-reporter.service.d/. Por ejemplo:
[Service]
RestartSec=15s
Recarga y reinicia después de guardar:
sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service
Verifica el resultado combinado:
systemctl cat my-reporter.service
Los drop-ins importan porque las actualizaciones de paquetes pueden reemplazar las unidades del proveedor. Tus anulaciones en /etc permanecen visibles e intencionales.
Piensa en el comportamiento de apagado
Iniciar es solo la mitad del ciclo de vida. Un servicio también debe detenerse limpiamente. Por defecto, systemd envía SIGTERM, espera y luego puede enviar SIGKILL si el proceso no sale. Para muchos servicios simples, eso está bien. Para workers de colas, procesadores de subidas y escritores de bases de datos, es posible que necesites manejar la terminación para que el proceso termine o abandone el trabajo actual de manera segura.
Puedes ajustar el tiempo de espera:
[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM
No establezcas tiempos de espera de parada extremadamente largos a menos que tengas una razón. Los apagados largos ralentizan las implementaciones, los reinicios y la recuperación de incidentes. Un worker generalmente debe dejar de aceptar nuevo trabajo, terminar el elemento que está procesando y salir dentro de un tiempo limitado.
Previene bucles de reinicio ruidosos
Restart=on-failure es útil, pero un servicio roto aún puede reiniciarse repetidamente. Agrega límites cuando el modo de fallo podría ser ruidoso:
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
Esto le dice a systemd que deje de intentar después de demasiados fallos dentro del intervalo. Cuando soluciones el problema, restablece el estado fallido:
sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service
Ese comando también es útil durante las pruebas. Un servicio puede permanecer en un estado fallido incluso después de haber corregido el script o los permisos.
Valida las unidades antes de confiar en ellas
Systemd tiene un verificador útil:
systemd-analyze verify /etc/systemd/system/my-reporter.service
No detectará todos los problemas de la aplicación, pero puede detectar errores de sintaxis, configuraciones desconocidas en tu versión de systemd y algunos problemas de orden. Ejecútalo después de ediciones grandes o al copiar una unidad entre distribuciones. Las características de systemd varían según la versión, por lo que una opción de hardening que funciona en un nuevo servidor Fedora puede no existir en una distribución empresarial más antigua.
También verifica la vista de dependencias de la unidad cuando el orden de inicio sea confuso:
systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service
El primer comando muestra lo que el servicio incorpora o de lo que depende. La vista inversa muestra lo que depende de él. Esto es útil cuando un servicio se inicia inesperadamente o cuando deshabilitar una unidad afecta a otra.
Decide si el servicio es a nivel de sistema o de usuario
Este artículo utiliza un servicio de sistema en /etc/systemd/system/. Esa es la opción correcta para servicios de máquina que deben iniciarse al arrancar y ejecutarse independientemente de una sesión de inicio de sesión. Systemd también soporta servicios de usuario, generalmente gestionados con systemctl --user, para procesos en segundo plano por usuario.
No uses un servicio de usuario para daemons de infraestructura solo para evitar sudo. Los servicios de usuario tienen diferentes reglas de ciclo de vida, manejo del entorno y comportamiento de inicio de sesión. Para workers de aplicaciones, exportadores y agentes a nivel de host, un servicio de sistema con un usuario dedicado con privilegios mínimos suele ser más fácil de razonar.
Mantén la primera unidad aburrida
Es tentador agregar cada directiva de hardening que encuentres en línea: dispositivos privados, rutas de solo lectura, filtros de syscall, limitación de capacidades, restricciones de espacio de nombres y más. Esas son herramientas valiosas, pero agrégalas una a la vez después de que el servicio funcione. Cuando un servicio fuertemente restringido falla al iniciar, los principiantes a menudo no pueden distinguir si la unidad está mal, la aplicación está mal o una configuración de sandbox bloqueó un archivo que necesita.
Un buen camino de producción es incremental: servicio funcional, usuario dedicado, registro confiable, política de reinicio, hardening básico, luego sandboxing más fuerte después de que tengas una prueba que demuestre que la aplicación aún se comporta correctamente.