systemd en Profundidad: Units, Targets y Dependencias

systemd es el sistema de inicio y gestor de servicios estándar en la mayoría de distribuciones Linux modernas, responsable de arrancar el sistema, gestionar servicios, montar sistemas de archivos y controlar recursos. Más allá del uso básico con systemctl start/stop, systemd ofrece capacidades avanzadas como activación por socket, timers como alternativa a cron, y control de recursos con cgroups. Esta guía cubre en profundidad los tipos de units, la gestión de targets, dependencias y la creación de servicios personalizados.

Requisitos Previos

  • Linux con systemd: Ubuntu 16.04+, CentOS 7+, Debian 9+, Rocky Linux 8+
  • Conocimientos básicos de systemctl y edición de archivos de texto
  • Acceso root o privilegios sudo

Tipos de Units en systemd

systemd gestiona el sistema mediante units, cada una con un tipo específico:

TipoExtensiónPropósito
Service.serviceProcesos y daemons
Socket.socketActivación por socket IPC/red
Target.targetAgrupación de units (como runlevels)
Timer.timerEjecución programada
Mount.mountPuntos de montaje
Path.pathMonitoreo de rutas del sistema de archivos
Slice.sliceJerarquía cgroups para recursos
Scope.scopeProcesos externos (no iniciados por systemd)
# Listar todas las units activas por tipo
systemctl list-units --type=service --state=running
systemctl list-units --type=timer
systemctl list-units --type=socket

# Ver todas las units (incluidas las inactivas y con fallos)
systemctl list-units --all

# Ver units fallidas
systemctl --failed

# Ver información detallada de una unit
systemctl cat nginx.service
systemctl show nginx.service

Anatomía de un archivo .service

# Ver la estructura de un servicio del sistema
systemctl cat sshd.service
# O directamente
cat /lib/systemd/system/ssh.service

Las secciones principales son:

[Unit]
Description=Descripción legible del servicio
After=network.target      # Iniciar después de
Requires=other.service    # Dependencia estricta
Wants=optional.service    # Dependencia suave

[Service]
Type=simple              # simple, forking, oneshot, notify, dbus, idle
ExecStart=/usr/bin/mi-app --opciones
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
User=www-data
WorkingDirectory=/var/www

[Install]
WantedBy=multi-user.target

Gestión de Targets

Los targets son unidades que agrupan otras units, equivalentes a los runlevels en SysV init:

# Ver el target por defecto
systemctl get-default

# Cambiar el target por defecto
systemctl set-default multi-user.target   # Sin interfaz gráfica
systemctl set-default graphical.target    # Con interfaz gráfica

# Targets principales y sus equivalencias
# poweroff.target  = runlevel 0 (apagar)
# rescue.target    = runlevel 1 (modo rescate, un solo usuario)
# multi-user.target = runlevel 3 (multiusuario sin GUI)
# graphical.target = runlevel 5 (multiusuario con GUI)
# reboot.target    = runlevel 6 (reiniciar)

# Cambiar al modo de rescate temporalmente
systemctl isolate rescue.target

# Ver units incluidas en un target
systemctl list-dependencies multi-user.target --no-pager

# Crear un target personalizado
cat > /etc/systemd/system/mi-app.target << 'EOF'
[Unit]
Description=Entorno Mi Aplicación
Requires=multi-user.target
After=multi-user.target
AllowIsolate=yes
EOF

Dependencias entre Units

systemd gestiona el orden de inicio y las dependencias entre units con varias directivas:

Directivas de Dependencia

# Ejemplo completo de dependencias
cat > /etc/systemd/system/ejemplo-dependencias.service << 'EOF'
[Unit]
Description=Ejemplo de dependencias systemd

# ORDEN (no implica dependencia, solo secuencia)
After=network-online.target postgresql.service   # Iniciar después de estas units
Before=nginx.service                             # Iniciar antes que nginx

# DEPENDENCIAS ESTRICTAS (si fallan, esta unit también falla)
Requires=network-online.target                  # DEBE estar activa
BindsTo=backend.service                         # Se detiene si backend se detiene

# DEPENDENCIAS SUAVES (intenta iniciarlas, pero no falla si no están disponibles)
Wants=redis.service                             # Intenta iniciar redis
PartOf=mi-app.target                            # Parte de este target

# CONFLICTOS (no puede estar activa al mismo tiempo)
Conflicts=modo-mantenimiento.service

[Service]
Type=simple
ExecStart=/usr/bin/mi-aplicacion
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

Visualizar el Grafo de Dependencias

# Ver dependencias de una unit
systemctl list-dependencies nginx.service
systemctl list-dependencies nginx.service --reverse  # Ver qué depende de nginx

# Generar grafo de dependencias en formato DOT (requiere graphviz)
systemd-analyze dot nginx.service | dot -Tsvg > /tmp/nginx-deps.svg

# Analizar tiempo de arranque
systemd-analyze
systemd-analyze blame          # Tiempo por cada service
systemd-analyze critical-chain # Cadena crítica del arranque

Crear Servicios Personalizados

Servicio Simple (Tipo simple)

# Crear un servicio para una aplicación web Python/Node
cat > /etc/systemd/system/mi-webapp.service << 'EOF'
[Unit]
Description=Mi Aplicación Web
Documentation=https://mi-empresa.com/docs
After=network.target
Wants=network-online.target

[Service]
Type=simple
# Ejecutar como usuario no privilegiado
User=webapp
Group=webapp
WorkingDirectory=/opt/mi-webapp

# Variables de entorno
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=-/opt/mi-webapp/.env  # El - ignora si no existe

# Comandos
ExecStartPre=/opt/mi-webapp/scripts/pre-start.sh
ExecStart=/usr/bin/node /opt/mi-webapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID

# Reinicio automático
Restart=always
RestartSec=10s
StartLimitInterval=60s
StartLimitBurst=3

# Seguridad
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/mi-webapp/data /var/log/mi-webapp

# Logs
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mi-webapp

[Install]
WantedBy=multi-user.target
EOF

# Habilitar y arrancar el servicio
systemctl daemon-reload
systemctl enable --now mi-webapp.service
systemctl status mi-webapp.service

Servicio Oneshot (Tareas de un solo disparo)

# Útil para scripts de inicialización
cat > /etc/systemd/system/inicializar-db.service << 'EOF'
[Unit]
Description=Inicializar base de datos
After=postgresql.service
Requires=postgresql.service

[Service]
Type=oneshot
RemainAfterExit=yes  # El servicio queda como "activo" después de ejecutar
User=postgres
ExecStart=/usr/local/bin/init-database.sh

[Install]
WantedBy=multi-user.target
EOF

Service con Instancias (@)

# Un servicio parametrizado con instancias múltiples
cat > /etc/systemd/system/[email protected] << 'EOF'
[Unit]
Description=Worker %i de la aplicación
After=network.target

[Service]
Type=simple
User=worker
ExecStart=/opt/app/worker.py --id=%i --queue=queue_%i
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

# Iniciar múltiples instancias del worker
systemctl enable --now [email protected]
systemctl enable --now [email protected]
systemctl enable --now [email protected]

# Ver estado de todas las instancias
systemctl status 'worker@*.service'

Activación por Socket

La activación por socket permite que systemd escuche en un socket e inicie el servicio solo cuando llegue una conexión, reduciendo el uso de recursos:

# Crear el socket unit
cat > /etc/systemd/system/mi-api.socket << 'EOF'
[Unit]
Description=Socket para Mi API
PartOf=mi-api.service

[Socket]
ListenStream=127.0.0.1:8080  # Puerto TCP
Accept=false                  # Un servicio maneja todas las conexiones

[Install]
WantedBy=sockets.target
EOF

# El servicio correspondiente
cat > /etc/systemd/system/mi-api.service << 'EOF'
[Unit]
Description=Mi API (activada por socket)
Requires=mi-api.socket
After=mi-api.socket

[Service]
Type=simple
ExecStart=/opt/mi-api/server
StandardInput=socket  # Recibir conexiones del socket
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

# Habilitar solo el socket (inicia el servicio automáticamente al haber conexiones)
systemctl enable --now mi-api.socket

systemd Timers

Los timers de systemd son una alternativa poderosa a cron con mejor integración y logging:

# Crear un timer para backup diario
cat > /etc/systemd/system/backup-diario.service << 'EOF'
[Unit]
Description=Backup diario del sistema

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
EOF

cat > /etc/systemd/system/backup-diario.timer << 'EOF'
[Unit]
Description=Timer para backup diario
Requires=backup-diario.service

[Timer]
# Ejecutar todos los días a las 2:30 AM
OnCalendar=*-*-* 02:30:00
# Añadir aleatoriedad para evitar picos (hasta 30 min de retraso)
RandomizedDelaySec=1800
# Ejecutar si se perdió el momento (el sistema estaba apagado)
Persistent=true
Unit=backup-diario.service

[Install]
WantedBy=timers.target
EOF

# Habilitar el timer
systemctl enable --now backup-diario.timer

# Ver todos los timers y cuándo se ejecutarán
systemctl list-timers

# Sintaxis de OnCalendar
# minutely        = *-*-* *:*:00
# hourly          = *-*-* *:00:00
# daily           = *-*-* 00:00:00
# weekly          = Mon *-*-* 00:00:00
# monthly         = *-*-01 00:00:00

Control de Recursos con systemd

systemd integra cgroups para limitar recursos por servicio:

# Limitar recursos de un servicio
cat > /etc/systemd/system/mi-servicio-limitado.service << 'EOF'
[Unit]
Description=Servicio con límites de recursos

[Service]
Type=simple
ExecStart=/opt/mi-app/server

# Límites de CPU
CPUQuota=50%           # Máximo 50% de un núcleo CPU
CPUWeight=100          # Peso relativo (defecto: 100)

# Límites de memoria
MemoryMax=512M         # Máximo 512 MB de RAM
MemorySwapMax=0        # Sin swap

# Límites de I/O
IOWeight=100           # Peso de I/O relativo
IOReadBandwidthMax=/dev/sda 50M   # Máximo 50 MB/s de lectura
IOWriteBandwidthMax=/dev/sda 10M  # Máximo 10 MB/s de escritura

# Límites de procesos/hilos
TasksMax=50            # Máximo 50 tareas (threads + procesos)
EOF

# Modificar límites sin editar el archivo (override temporal)
systemctl set-property mi-servicio-limitado.service CPUQuota=25%
systemctl set-property mi-servicio-limitado.service MemoryMax=256M

# Ver uso de recursos actual
systemctl status mi-servicio-limitado.service
systemd-cgtop  # Monitor en tiempo real de recursos por cgroup

Solución de Problemas

Servicio que no inicia y no hay logs claros:

# Ver el estado detallado
systemctl status mi-servicio.service -l

# Ver todos los logs del servicio desde el inicio
journalctl -u mi-servicio.service --no-pager

# Ver solo los últimos errores
journalctl -u mi-servicio.service -p err -n 50

# Verificar que el archivo de servicio no tiene errores de sintaxis
systemd-analyze verify mi-servicio.service

Error "Unit not found" al usar systemctl:

# Verificar que el archivo existe
ls -la /etc/systemd/system/mi-servicio.service
# Recargar la configuración de systemd
systemctl daemon-reload
# Ver si systemd reconoce el servicio
systemctl list-unit-files | grep mi-servicio

Servicio que se reinicia en bucle:

# Ver el historial de inicios y fallos
journalctl -u mi-servicio.service -n 100
# Ajustar la política de reinicio para diagnóstico
systemctl edit mi-servicio.service
# Añadir: [Service]
# Restart=no
# Reintentar manualmente
systemctl start mi-servicio.service

Timer no se ejecuta en el momento esperado:

systemctl list-timers --all
# Verificar la sintaxis OnCalendar
systemd-analyze calendar "*-*-* 02:30:00"
# Verificar que el timer está habilitado y activo
systemctl is-enabled backup-diario.timer
systemctl is-active backup-diario.timer

Conclusión

systemd es mucho más que un gestor de servicios: es un framework completo para la gestión del sistema Linux que incluye resolución de dependencias, activación por demanda, timers avanzados y control de recursos integrado. Dominar los tipos de units, las directivas de dependencia y las opciones de seguridad de servicios permite crear infraestructuras más robustas y fáciles de mantener. La integración nativa con journald hace que el diagnóstico de problemas sea significativamente más eficiente que con sistemas de init anteriores.