El sistema de init es el primer proceso que arranca el kernel después de montarse el sistema de archivos raíz — el proceso con PID 1. Todo lo demás en el sistema es su descendiente directo o indirecto. En Debian desde Jessie (versión 8), ese proceso es systemd, y entender cómo piensa systemd cambia la forma en que administras cualquier sistema Linux moderno.
Qué es y por qué reemplazó a SysV init
SysV init funcionaba con scripts de shell en /etc/init.d/. Cada servicio tenía un script que recibía argumentos como start, stop, o restart, y dentro había código shell arbitrario que hacía lo que el empaquetador quisiera. El arranque era secuencial: un servicio tenía que terminar de iniciar antes de que el siguiente pudiera comenzar. Las dependencias entre servicios se declaraban con comentarios especiales (# Required-Start: $network) que un script externo interpretaba — nada lo verificaba en tiempo real. Si un demonio fallaba en silencio, el sistema seguía arrancando sin saber qué había pasado. Los logs llegaban a archivos dispersos bajo /var/log/, algunos con rotación, otros sin ella, ninguno con metadatos estructurados.
El problema fundamental no era la lentitud del arranque (aunque sí era lenta), sino la fragilidad: dependencias implícitas que fallaban en hardware rápido donde los tiempos de arranque rompían las suposiciones codificadas en los scripts, y la completa ausencia de control sobre los recursos que cada servicio podía consumir una vez arrancado.
systemd resuelve esto de otra forma. En lugar de scripts de shell imperativos, usa archivos de unidad declarativos (los llamados units) donde describes qué necesita un servicio, cuándo debe arrancar, bajo qué usuario, con qué límites de memoria o CPU. systemd lee esas declaraciones, construye un grafo de dependencias, y arranca en paralelo todo lo que puede arrancar en paralelo. Usa cgroups del kernel para aislar procesos y rastrear todos los hijos de un servicio — si matas el cgroup del servicio, matas todo lo que lanzó, sin excepciones. Y journald recoge los logs de todos los servicios en un diario estructurado y consultable.
Usas systemd cada vez que arrancas una máquina Debian, cada vez que haces systemctl restart nginx, o cada vez que quieres que algo se ejecute periódicamente sin tocar cron. Lo que se rompe si no entiendes la arquitectura es predecible: editas el archivo equivocado (el del paquete en lugar del override local), tus cambios desaparecen en la próxima actualización; no declaras las dependencias correctas y el servicio arranca antes de que la base de datos esté lista; no sabes dónde buscar los logs y tardas diez minutos en diagnosticar un fallo trivial.
El concepto central: la unit
La unit es la unidad atómica de configuración de systemd. Todo lo que systemd gestiona — un servicio, un socket, un montaje, un temporizador, un grupo de arranque — se representa como una unit con un archivo de texto en formato INI. El tipo de la unit lo determina la extensión del nombre del archivo:
| Extensión | Qué representa |
|---|---|
.service | Un demonio o proceso |
.socket | Un socket para activación bajo demanda |
.timer | Una tarea periódica (equivalente a cron) |
.target | Un grupo de units, análogo a los runlevels de SysV |
.mount | Un punto de montaje del sistema de archivos |
.path | Un disparador basado en cambios en el filesystem |
Cuando arranca el sistema, systemd activa default.target, que tiene dependencias hacia multi-user.target o graphical.target según la configuración, y así sucesivamente hasta que todo el árbol de dependencias está resuelto.
Dónde viven las units
Esto es crítico y es donde la gente comete errores que se manifiestan días después:
/usr/lib/systemd/system/— units instaladas por paquetes Debian. No edites estos archivos. Se sobreescriben con cada actualización del paquete./etc/systemd/system/— units locales y overrides. Todo lo que pongas aquí tiene precedencia sobre/usr/lib/systemd/system/. Aquí viven tus servicios propios y tus modificaciones a servicios existentes./run/systemd/system/— units generadas en tiempo de ejecución, volátiles.
# Ejemplo completo: unit de servicio propio en /etc/systemd/system/ # Archivo: /etc/systemd/system/sincronizador.service [Unit] Description=Sincronizador de datos hacia almacén remoto # systemd no arranca esta unit hasta que la red esté operativa After=network-online.target # Si postgresql.service falla al arrancar, esta unit ni lo intenta Requires=postgresql.service After=postgresql.service [Service] Type=simple # Nunca ejecutes servicios propios como root si no es estrictamente necesario User=sincronizador Group=sincronizador WorkingDirectory=/opt/sincronizador # La variable de entorno se carga desde un archivo externo, # no se embute en la unit (evita credenciales en texto plano aquí) EnvironmentFile=/etc/sincronizador/entorno ExecStart=/opt/sincronizador/bin/sincronizador --config /etc/sincronizador/config.yaml # Si el proceso muere inesperadamente, systemd lo reinicia tras 5 segundos. # "on-failure" excluye las paradas explícitas con systemctl stop. Restart=on-failure RestartSec=5s # Límite de memoria duro: el kernel mata el proceso si lo supera MemoryMax=512M # El proceso no puede usar más del 50% de un núcleo de CPU CPUQuota=50% [Install] # Esta línea define en qué target se activa la unit con systemctl enable. # multi-user.target es el equivalente moderno del runlevel 3. WantedBy=multi-user.target
Para activar esta unit en el sistema:
# Necesario siempre que creas o modificas un archivo de unit directamente systemctl daemon-reload # Habilitar = crear el symlink en multi-user.target.wants/ # para que arranque automáticamente en el próximo boot systemctl enable sincronizador.service # Arrancar ahora, sin esperar al reboot systemctl start sincronizador.service # Ver el estado y las últimas líneas del log en una sola vista systemctl status sincronizador.service
Qué está pasando debajo
Fíjate en la sección [Unit]: la distinción entre Requires= y After= no es redundante. Requires=postgresql.service establece una dependencia de activación — si PostgreSQL falla, systemd también falla esta unit. After=postgresql.service establece el orden de arranque — esta unit empieza solo después de que PostgreSQL haya alcanzado su estado activo. Sin After=, systemd podría intentar arrancar ambos en paralelo aunque haya declarado Requires=. Puedes tener After= sin Requires= (arrancar después pero no depender del éxito) o Requires= sin After= (dependencia de activación pero sin orden estricto). Son ortogonales.
En [Service], Type=simple le dice a systemd que el proceso que lanza ExecStart= es el propio servicio — systemd lo considera activo en cuanto el proceso arranca. Si el demonio hace doble fork (el patrón clásico de los demonios Unix), necesitarías Type=forking con un PIDFile=. La mayoría de los servicios modernos escritos para systemd usan Type=simple o Type=notify (cuando el servicio avisa explícitamente a systemd que está listo).
MemoryMax=512M y CPUQuota=50% no son sugerencias — systemd los implementa creando un cgroup para el servicio y configurando los controladores de cgroups del kernel. Si el proceso supera MemoryMax, el kernel lo mata con OOM killer. Esto es lo que SysV init nunca pudo hacer: el aislamiento de recursos es garantía del kernel, no del código del servicio.
El comando systemctl daemon-reload es obligatorio después de tocar cualquier archivo de unit directamente. systemd cachea las units en memoria; sin el reload, seguirá usando la versión anterior aunque el archivo haya cambiado en disco. Si olvidaste hacerlo y te preguntas por qué tu cambio no tiene efecto, ese es el motivo.
N° 65