Por que systemd reemplazo a SysV init y Upstart
Analisis tecnico sobre la transicion a systemd. Descubre como los cgroups y los sockets preabiertos resolvieron las limitaciones de dependencias y seguimiento de procesos en Linux.
Por qué ganó systemd
La historia de systemd comienza con dos tareas que SysV init no podía realizar. SysV init (System V init, el sistema de inicio que Linux heredó de AT&T Unix) no tenía forma de describir las dependencias de un servicio, ni manera de identificar qué procesos pertenecían a un servicio una vez que estaba en ejecución. systemd respondió a ambas con funciones del kernel a las que un script de shell no puede acceder: grupos de control (cgroups) para el seguimiento de procesos y sockets de escucha preabiertos para la gestión del orden de inicio. El resto de la historia explica cómo esas dos soluciones se extendieron al resto del espacio de usuario, donde comenzaron las objeciones, y varias de esas objeciones eran acertadas.
Lo que hacía realmente SysV init
En un sistema SysV, el PID 1 (el identificador de proceso 1, el primer proceso que inicia el kernel) leía /etc/inittab, seleccionaba un nivel de ejecución (runlevel) y ejecutaba los scripts correspondientes a ese nivel. Los scripts residían en /etc/init.d/. Los enlaces simbólicos en /etc/rc3.d/ determinaban cuáles se ejecutaban y en qué orden; así, /etc/rc3.d/S20nginx apuntaba a /etc/init.d/nginx y se invocaba con el argumento start.
#!/bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
### END INIT INFO
case "$1" in
start)
start-stop-daemon --start --quiet --pidfile /run/nginx.pid \
--exec /usr/sbin/nginx
;;
stop)
start-stop-daemon --stop --quiet --retry TERM/30/KILL/5 \
--pidfile /run/nginx.pid
;;
esacEl 20 en S20nginx es una posición, no una dependencia. Indica que este script se ejecuta después de S19 y antes de S21. No explica el motivo, por lo que nada puede verificarlo y no es posible ejecutar de forma segura dos scripts no relacionados al mismo tiempo sin que un humano determine que es seguro.
El programa rc ejecutaba cada script secuencialmente y esperaba a que finalizara. Un script que se bloqueaba durante treinta segundos esperando una dirección de red detenía todo el arranque durante esos treinta segundos, incluso para servicios que no requieren red.
La cabecera LSB (Linux Standard Base) al principio de ese script fue un intento de solucionar esto desde dentro. Debian 6.0 en 2011 estableció insserv como predeterminado: leía Required-Start de cada script, construía un grafo y renumeraba los enlaces simbólicos. Debian pudo entonces ejecutar scripts independientes simultáneamente con startpar. Eso ayudó, pero no resolvió el problema de fondo. La dependencia seguía basándose en que un script finalizara. Que S20nginx devuelva 0 significa que una función de shell ha terminado. No significa que nginx esté aceptando conexiones.
Las cinco limitaciones que ningún script de init podía solucionar
- Inicio en paralelo. El ordenamiento por nombre de archivo impone un orden total sobre cada servicio de la máquina, por lo que el arranque es tan lento como la suma de sus partes.
- Disponibilidad. Un script de inicio finaliza cuando ha ejecutado el fork del daemon, no cuando el daemon puede atender una petición, por lo que el siguiente script a menudo se inicia demasiado pronto.
- Supervisión. Un daemon realiza un doble fork y su proceso padre termina, lo que lo desvincula de la terminal y lo reasigna al PID 1. El proceso init detecta la salida de un hijo y pierde cualquier vínculo fiable con el proceso que sobrevivió.
- Inicio bajo demanda. inetd (el super-servidor de internet) podía lanzar un daemon cuando llegaba una conexión, pero era un sistema independiente con su propio archivo de configuración y no gestionaba el orden de los demás elementos durante el arranque.
- Control de recursos. Nada en un script de init podía limitar la memoria de un servicio o su cuota de CPU.
ulimitse aplicaba a un solo proceso ynicesolo afectaba al planificador, por lo que un proceso hijo descontrolado de un servicio parecía cualquier otro proceso en el sistema.
La brecha de supervisión es la que más afectaba en el día a día. El archivo PID era la solución alternativa: el daemon escribía su ID de proceso en /run/nginx.pid y la función de parada leía el archivo. Si el daemon sufría una terminación forzosa, el archivo permanecía allí. El kernel reutilizaba entonces ese número para otra tarea y start-stop-daemon --stop --pidfile enviaba una señal a lo que fuera que lo poseyera ahora. Un archivo PID obsoleto es la forma en que un script de init termina el proceso equivocado.
launchd resolvió primero el problema de los sockets
Apple lanzó launchd en Mac OS X 10.4 en 2005, escrito por Dave Zarzycki. Un solo proceso reemplazó a init, rc, xinetd, crond y watchdogd.
La idea que merecía ser copiada fue la activación por socket. launchd crea primero todos los sockets de escucha y luego inicia los daemons. Un cliente que se conecta a un daemon que aún no se ha iniciado no recibe una conexión rechazada, porque el kernel mantiene la conexión en la cola de espera (backlog) de ese socket hasta que el daemon llama a accept(). El orden entre dos daemons deja de ser algo que un humano deba declarar. El socket se encarga de ello.
launchd se construyó sobre Mach IPC (comunicación entre procesos), que pertenece al kernel XNU de Apple y no tiene equivalente en Linux. Portar el código nunca fue realista. La idea se trasladó de todos modos.
Upstart convirtió los eventos en la unidad de trabajo
Upstart de Canonical, escrito por Scott James Remnant, se lanzó en Ubuntu 6.10 en octubre de 2006. Fedora 9 hasta Fedora 14 lo utilizaron, al igual que RHEL 6 y Chrome OS. Reemplazó el nivel de ejecución (runlevel) por un evento, y un trabajo especificaba qué eventos debían iniciarlo y detenerlo.
# /etc/init/example.conf
start on filesystem and net-device-up IFACE!=lo
stop on runlevel [!2345]
respawn
respawn limit 10 5
exec /usr/local/bin/exampledSurgieron dos problemas a medida que aumentaba el número de trabajos. El primero es la dirección. Un trabajo dice "iníciame cuando ocurra esto", por lo que el conocimiento de qué depende de qué reside en el archivo incorrecto: un servicio sabe lo que necesita, pero no puede saber quién lo necesitará el próximo año. Añadir un servicio a menudo significaba editar un trabajo existente para que emitiera un nuevo evento.
El segundo es el seguimiento. Upstart seguía a un demonio que realizaba bifurcaciones (forking) contando las llamadas fork() con ptrace, que se configuraban como expect fork o expect daemon. Si se calculaba mal el número de bifurcaciones, Upstart supervisaba un proceso que ya había terminado o esperaba una bifurcación que ya había ocurrido. El síntoma es que initctl start se queda bloqueado sin error, algo que el archivo de trabajo no ofrece forma de explicar.
Upstart también requería que los colaboradores firmaran el acuerdo de contribución de Canonical. Eso no fue un fallo de ingeniería, pero sí influyó en quién trabajaba en él.
Repensando PID 1, abril de 2010
El 30 de abril de 2010, Lennart Poettering publicó una entrada titulada "Rethinking PID 1". Kay Sievers trabajó en el proyecto junto a él. El argumento constaba de cuatro partes.
- Iniciar menos servicios. Muchos servicios pueden esperar hasta que algo realmente los solicite.
- Dejar de declarar el orden cuando un socket puede implicarlo. Abrir todos los sockets en una sola pasada y luego iniciar todo a la vez.
- Rastrear procesos con grupos de control (cgroups) en lugar de archivos PID.
- Describir un servicio en un archivo declarativo, de modo que una misma descripción funcione en todas las distribuciones.
La primera versión se lanzó ese mismo año. Fedora 14 incluyó systemd como opción en noviembre de 2010, y Fedora 15 lo estableció como predeterminado en mayo de 2011.
Por qué los cgroups hicieron que la supervisión fuera fiable
Un cgroup (grupo de control) es una funcionalidad del kernel para agrupar procesos, integrada en Linux 2.6.24 en 2008. systemd coloca cada servicio en su propio cgroup. Un proceso hijo hereda el cgroup de su padre, y un proceso sin privilegios no puede salir de él por sí mismo. Por lo tanto, el doble fork no oculta nada: PID 1 mantiene el conjunto exacto de procesos que pertenecen a una unidad en todo momento. Detener un servicio significa terminar todo lo que se encuentra en su cgroup, que es lo que hace por defecto KillMode=control-group.
systemctl status imprime ese grupo:
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-08-11 09:14:22 UTC; 3min ago
Main PID: 1042 (nginx)
Tasks: 3 (limit: 4653)
Memory: 6.1M (peak: 7.4M)
CGroup: /system.slice/nginx.service
├─1042 "nginx: master process /usr/sbin/nginx"
├─1043 "nginx: worker process"
└─1044 "nginx: worker process"Ese bloque es la respuesta completa al problema de los archivos PID obsoletos. No existe ningún archivo que pueda quedar obsoleto, ya que la lista es un estado del kernel.
El mismo árbol aplica límites, ya que los cgroups se diseñaron para la contabilidad antes de que nadie los utilizara para el seguimiento. MemoryMax=, CPUQuota= y TasksMax= ocupan una línea cada uno. Establecer un límite estricto de memoria y CPU en un servicio es hoy un archivo de configuración adicional, mientras que en 2009 era un parche para un script de shell que nadie escribió.
Por qué todas las distribuciones realizaron el cambio entre 2011 y 2015
- Fedora 15, mayo de 2011.
- openSUSE 12.1, noviembre de 2011.
- Mageia 2, mayo de 2012.
- Arch Linux, predeterminado para instalaciones nuevas desde octubre de 2012.
- RHEL 7, junio de 2014.
- SLES 12, octubre de 2014.
- Debian 8, abril de 2015.
- Ubuntu 15.04, abril de 2015.
Las razones fueron mayormente triviales, motivo por el cual el cambio fue rápido.
- Un archivo de unidad funciona en cualquier distribución, por lo que los proyectos upstream comenzaron a distribuir un archivo
.servicey las distribuciones dejaron de mantener un script de shell por paquete y por versión. - El seguimiento de sesiones de escritorio se trasladó a
systemd-loginddespués de que ConsoleKit dejara de recibir mantenimiento alrededor de 2012. GNOME necesitaba logind, por lo que una distribución sin systemd debía encontrar un reemplazo. Ese reemplazo,elogind, es logind de systemd extraído y mantenido por separado. - udev, el gestor de dispositivos, se fusionó en el árbol de fuentes de systemd en abril de 2012. Las distribuciones que distribuían udev pasaron a seguir el repositorio de systemd. Gentoo creó un fork de
eudevcomo respuesta. - Los contenedores hicieron que el seguimiento fiable de procesos y los límites por servicio fueran más importantes, ya que ambos son características de cgroups. La cuestión de qué supervisor gestiona un proceso de contenedor sigue vigente siempre que usted haga que una pila de Docker Compose se reinicie tras un arranque.
La decisión de Debian fue la más notoria. El Comité Técnico votó en febrero de 2014, el resultado fue un empate y el presidente, Bdale Garbee, emitió el voto decisivo a favor de systemd. Ubuntu anunció días después que seguiría a Debian en lugar de continuar con Upstart. Un grupo de desarrolladores de Debian creó un fork de la distribución llamado Devuan en noviembre de 2014 y lanzó Devuan 1.0 en mayo de 2017.
Las objeciones, expuestas de forma justa
Alcance. Un solo proyecto ahora distribuye PID 1, el demonio de registro, la gestión de sesiones de inicio de sesión, el gestor de dispositivos, un demonio de configuración de red, un resolvedor de DNS (sistema de nombres de dominio), un cliente NTP (protocolo de tiempo de red), un ejecutor de contenedores y un cargador de arranque. La defensa habitual, que sostiene que son binarios separados que no es obligatorio instalar, es cierta pero no responde a la objeción. Una vez que un escritorio requiere logind, y logind se libera del árbol de systemd, la elección deja de ser libre. Eso es lo que significaba el acoplamiento en el argumento, y sucedió.
El diario binario. journald escribe en un formato binario indexado en lugar de texto plano. Obtiene funciones que el texto nunca ofreció: filtrado por unidad y por prioridad, campos estructurados y metadatos que el programa emisor no puede falsificar, porque journald registra la unidad y el cgroup por sí mismo. journalctl -u nginx -p err --since "-1h" reemplaza un grep con una expresión regular de fecha. El coste también es real. En una máquina que no arranca, no puede leer el registro con less desde una consola de rescate. Debe apuntar journalctl al disco montado:
sudo journalctl --directory /mnt/var/log/journal --boot -1 --priority errExiste una segunda trampa aquí que atrapa a los usuarios una vez. journald mantiene los registros en /run/log/journal, que es memoria, a menos que exista /var/log/journal. En una máquina donde no existe, journalctl -b -1 no tiene nada que mostrar después de un reinicio, que es el momento exacto en el que lo necesitaba. Compruébelo y arréglelo:
journalctl --disk-usage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journaldjournalctl --disk-usage ahora debería informar de diarios archivados bajo /var/log/journal. Si también desea texto plano, establezca ForwardToSyslog=yes en /etc/systemd/journald.conf y mantenga rsyslog instalado.
Depuración del arranque. Cuando una unidad se bloquea, la consola muestra una línea y nada más:
[ *** ] A start job is running for Wait for Network to be Configured (1min 32s / no limit)Las herramientas para profundizar existen: systemctl list-jobs mientras está bloqueada, systemd-analyze blame y systemd-analyze critical-chain después, y systemd.log_level=debug en la línea de comandos del kernel. La versión justa de la queja es que un script de init podía ser leído de principio a fin por cualquiera que conociera sh, mientras que una unidad bloqueada requiere saber a cuál de una docena de comandos recurrir. Ese es un coste real. Se paga una vez por administrador, y fue pagado por muchos administradores al mismo tiempo.
Un valor predeterminado que cambia para todos. systemd 230 en 2016 cambió el valor predeterminado de logind para que los procesos de usuario sobrantes se eliminaran al cerrar la sesión. Las sesiones desvinculadas de tmux y screen morían cuando terminaba la sesión que las inició. Las distribuciones distribuyeron KillUserProcesses=no en /etc/systemd/logind.conf, y la respuesta admitida es loginctl enable-linger <user>. Un valor predeterminado en un proyecto cambió un hábito del que dependían millones de personas, que es lo que significa "demasiado espacio de usuario en un solo lugar" en la práctica.
Una dependencia predeterminada es una superficie de seguridad. En marzo de 2024, la puerta trasera en xz-utils apuntó a sshd en Debian y Ubuntu. El OpenSSH original no enlaza libsystemd. Esas distribuciones lo parchearon para que sshd pudiera informar de su disponibilidad a systemd, y libsystemd arrastró a liblzma, donde residía la puerta trasera. El protocolo de disponibilidad en sí es un único datagrama enviado al socket nombrado en $NOTIFY_SOCKET, por lo que nunca se requirió ninguna biblioteca para ello. La respuesta de systemd fue cargar bibliotecas de compresión con dlopen, por lo que ya no se enlazan de forma predeterminada. Una clase relacionada de error muestra la misma forma: en 2017, un valor de User= que comenzaba con un dígito se trataba como no válido y la unidad se ejecutaba como root en lugar de fallar, por lo que un error tipográfico se convirtió en una escalada de privilegios. Las versiones posteriores se niegan a iniciar la unidad.
Historial en su propio prompt de systemctl
Cada problema anterior es ahora una directiva en un archivo que puede leer.
- El arranque en serie se convirtió en
After=yWants=, ysystemd-analyze critical-chainmuestra qué retuvo realmente su arranque. - La preparación se convirtió en
Type=notify, donde el servicio escribeREADY=1en$NOTIFY_SOCKETcuando puede atender peticiones.Type=forkingconPIDFile=todavía existe para daemons antiguos, y es el tipo que falla constart operation timed out. Terminating.cuando el archivo PID nunca aparece. - La supervisión se convirtió en el cgroup, por lo que
Restart=on-failureconRestartSec=reemplaza a un script contenedor, yStartLimitBurst=evita que un bucle de fallos se ejecute indefinidamente. - inetd se convirtió en una unidad
.socketsituada junto a la unidad.service. ulimitse convirtió enMemoryMax=,CPUQuota=yTasksMax=.- La línea
su - appuser -cen un script de init se convirtió enUser=,NoNewPrivileges=yesyProtectSystem=strict, por lo que ejecutar un servicio como usuario sin privilegios es la forma predeterminada de una unidad en lugar de trabajo adicional.
[Unit]
Description=Example API
Wants=network-online.target
After=network-online.target postgresql.service
Requires=postgresql.service
[Service]
Type=notify
ExecStart=/usr/local/bin/exampled
Restart=on-failure
RestartSec=2
MemoryMax=512M
CPUQuota=50%
TasksMax=128
User=exampled
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
StateDirectory=exampled
[Install]
WantedBy=multi-user.targetUna línea en ese archivo es el error que todo el mundo comete una vez. Requires=postgresql.service es un requisito, no un orden: indica que su unidad falla si Postgres falla, y no dice que se inicie Postgres primero. Sin After=postgresql.service, ambos se inician en el mismo momento y su servicio se conecta a un puerto en el que aún no hay nada escuchando. Ambos son independientes a propósito, porque a veces se desea uno sin el otro. ProtectSystem=strict monta el sistema de archivos como solo lectura para este servicio, que es la razón por la que StateDirectory= está ahí: le da al servicio una ruta de escritura bajo /var/lib.
El lugar más claro para ver 2005 en un servidor 2026 es SSH en Ubuntu 24.04, que incluye systemd 255 desde agosto de 2026. ssh.service está activado por socket de forma predeterminada: ssh.socket mantiene el socket de escucha y sshd se inicia cuando llega una conexión. Por lo tanto, Port 2222 en /etc/ssh/sshd_config no tiene efecto, porque sshd no es el proceso que abrió el puerto. El cambio pertenece a la unidad de socket.
sudo systemctl edit ssh.socket[Socket]
ListenStream=
ListenStream=2222El ListenStream= vacío borra el valor heredado de la unidad empaquetada. Si lo omite, obtendrá ambos puertos, porque systemd añade a una lista en lugar de reemplazarla. Luego aplique y verifique, manteniendo una segunda sesión SSH abierta todo el tiempo:
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -lntp | grep 2222ss debería listar un socket en el puerto 2222 propiedad de systemd, no de sshd. Ese es el diseño de launchd, veinte años después, en su VPS. Si prefiere el comportamiento antiguo, sudo systemctl disable --now ssh.socket seguido de sudo systemctl enable --now ssh.service le proporciona un sshd de ejecución prolongada que vuelve a leer Port de su propia configuración.
Cuáles de estos detalles encuentre depende de la versión que ejecute, por lo que vale la pena conocer la diferencia entre una versión LTS y una versión intermedia de Ubuntu antes de planificar una actualización. En más de una máquina, el hecho de que un archivo de unidad sea idéntico en todas partes es la razón por la que gestionar varios servidores desde un solo lugar es ahora un problema de configuración en lugar de un problema de scripting de shell. Y cuando escriba sus propias unidades, el par de servicio y temporizador realiza el trabajo que habría dividido entre un script de init y una línea de cron en 2009.
FAQ
¿Por qué las distribuciones de Linux reemplazaron SysV init por systemd?
Por dos razones de ingeniería y una de mantenimiento. SysV init ordenaba los servicios por nombre de archivo, lo cual es una posición y no una dependencia, y perdía el rastro de cualquier daemon que se bifurcara de su proceso padre, razón por la cual los archivos PID obsoletos podían terminar el proceso incorrecto. systemd resolvió el orden mediante la activación por socket y directivas de dependencia, y resolvió el seguimiento mediante grupos de control (cgroups). La razón de mantenimiento determinó la velocidad: un archivo de unidad funciona en todas las distribuciones, por lo que los proyectos upstream distribuían un archivo .service y los mantenedores de las distribuciones dejaron de escribir un script de shell por cada paquete. Fedora 15 realizó el cambio en mayo de 2011 y Ubuntu 15.04 fue la última gran resistencia, en abril de 2015.
¿Es systemd un binario gigante?
No. El árbol de código fuente compila muchos programas separados. El PID 1 es /usr/lib/systemd/systemd, mientras que journald, logind y udevd son procesos separados con sus propios binarios; ejecute ls /usr/lib/systemd/ para verlos en su propio equipo. La crítica que persiste se refiere al acoplamiento de las versiones más que al tamaño del binario: estos programas se lanzan juntos y comparten interfaces privadas, por lo que las distribuciones tienden a adoptarlos como un conjunto, y software como GNOME llegó a requerir logind específicamente.
¿Puedo seguir ejecutando Linux sin systemd?
Sí. Devuan distribuye sysvinit, Gentoo utiliza OpenRC por defecto, Void usa runit, Alpine usa busybox init con OpenRC y Slackware mantiene scripts al estilo BSD. El costo es el trabajo de compatibilidad. El software de escritorio que espera logind necesita elogind, que es el logind de systemd mantenido como un paquete independiente, y una cantidad creciente de software de servidor ahora solo incluye un archivo .service, por lo que usted debe escribir y mantener el script de inicio por su cuenta.
¿Por qué el journal es binario en lugar de un archivo de texto plano?
Porque journald almacena campos estructurados con un índice, lo que permite el filtrado por unidad, el filtrado por prioridad y metadatos que el programa emisor no puede falsificar: journald registra la unidad, el cgroup y el UID real por sí mismo en lugar de confiar en la línea de registro. El precio es que necesita journalctl para leerlo, incluso desde un sistema de rescate, donde debe apuntar al disco montado con journalctl --directory /mnt/var/log/journal. Si también desea texto, configure ForwardToSyslog=yes en /etc/systemd/journald.conf.
¿Qué reemplazó la edición de mis scripts en /etc/init.d?
Los archivos de configuración adicionales (drop-in files). No edite la unidad en /usr/lib/systemd/system/, porque una actualización del paquete la sobrescribirá. Ejecute sudo systemctl edit nginx.service y systemd creará /etc/systemd/system/nginx.service.d/override.conf, que se fusiona sobre la unidad empaquetada. systemctl cat nginx.service muestra el resultado fusionado y systemd-delta enumera todas las anulaciones (overrides) en la máquina. Después de cualquier edición manual, ejecute sudo systemctl daemon-reload, o el siguiente comando mostrará Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk.