Tabla de Contenidos
Broken by design: systemd
Desde su lanzamiento y forzada implementación de SystemD, ha resonado en muchos foros, canales de IRC, comunidades y redes sociales el tema de SystemD.
A pesar de que las opiniones son mayormente negativas, la mayoría de los argumentos han sido desestimados y tildados de mera resistencia al cambio, o morigerados con la idea de que a pesar de sus fallas, su diseño es grandioso. Esta visión incorpora la noción de que las fallas de SystemD se pueden reparar sin desecharlo y sin incurrir en grandes costos de desarrollo, y por lo tanto no son un mayor obstáculo para adoptar a SystemD.
Esta idea es equivocada: SystemD está roto por diseño (“broken by design”), y más allá de ofrecer mejoras atractivas sobre los viejos sistemas de inicio (initd: SystemV Init), también incorpora grandes regresiones en muchas de las áreas donde se espera que GNU/Linux sea excelente: seguridad, estabilidad, y no tener que reiniciar para actualizar el sistema.
El primer gran problema: PID-1
En los sistemas Unix, el proceso cuyo ID es 1 (PID 1) es especial. Los procesos huérfanos (incluyendo un caso especial: los demonios que se vuelven huérfanos a si mismos) son adoptados por el proceso con PID 1. Existe también una semántica especial de señales hacia con PID 1, y tal vez es más importante, si PID 1 finaliza repentinamente (“crashes”) o termina, el sistema completo se cae (kernel panic).
Entre las razones por las cuales SystemD quiere o necesita correr como PID 1, está el hacerse cargo de los demonios cuyo mal comportamiento hace que se vuelvan huérfanos a sí mismos, impidiendo que su inmediato padre conozca su PID para esperarlo (wait) o enviarle una señal.
Desafortunadamente, esto también trae las otras propiedades de PID 1, incluyendo el detener por completo el sistema si crashea. Esto es importante debido a que SystemD es complejo. Mucho más complejo que los sistemas de inicio tradicionales. Y cuando se dice complejo, no se habla en el sentido de líneas de código, sino en términos de las posibles entradas y caminos de ejecución que pueden ser activados en tiempo de ejecución. Mientras que los sistemas de inicio tradicionales básicamente no lidian con ninguna señal excepto SIGCHLD de procesos huérfanos al finalizar su ejecución y cambios manuales de runlevel realizados por el administrador, SystemD lidia con todo tipo de entradas y señales, incluyendo inserción y remoción de dispositivos, cambios en puntos de montaje y sistemas de archivos, e inclusive una API pública basada en DBus. Esto a su vez implica alocación de recursos, parseo de archivos y mensajes, manipulación de strings, etc. Lo que lleva a:
El segundo gran problema: superficie de ataque
En un sistema sin SystemD, luego de haber realizado el hardening queda a lo sumo un único proceso con privilegios de root que tiene una superficie de ataque: sshd. El resto o corre con un usuario sin privilegios, o no tiene un canal para proveer entrada excepto la entrada local de root. Utilizar SystemD al menos duplica la superficie de ataque.
Este riesgo incrementado y poco razonable no es inherente a la meta de SystemD de arreglar el sistema de inicio tradicional. Sin embargo es inherente a la filosofía de diseño de SystemD de poner todo dentro del proceso de inicio.
El tercer gran problema: reiniciar para actualizar
Fundamentamente, actualizar nunca debe requerir un reinicio salvo que el componente actualizado sea el kernel. Incluso, por actualizaciones de seguridad, es posible disponer de un parche en caliente que sea aplicado como un módulo del kernel cargable para mitigar el riesgo de seguridad hasta que sea apropiado reiniciar con el nuevo kernel.
Desafortunadamente, al mover grandes cantidades de funcionalidades que necesitarán ser actualizadas dentro de PID 1, SystemD hace imposible actualizar sin reiniciar. Esto hará que “Linux” se vuelva la burla de los fans de Windows, tal como pasó con Ubuntu tiempo atrás.
Posibles contra argumentos
Respecto a la seguridad, se podría pensar por qué no usar SystemD en los sistemas de escritorio, y dejar a los servidores con algo diferente. Pero esta línea de razonamiento está equivocada en al menos tres formas:
- Muchas de las características por las que se pondera a SystemD son orientadas a servidores. Gestionar demonios con transacciones no es una característica útil en sistemas de escritorio. El público al que apuntan este tipo de cosas es claramente los administradores de servidores.
- El escritorio se está volviendo cada vez más rápidamente irrelevante. La futura plataforma será móvil y deberá lidiar con la realidad de ejecutar aplicaciones no confiables. Mientras que el escritorio hace que la distinción de cuentas de usuario locales irrelevantes, la llegada de los ecosistemas de aplicaciones móviles plagados de aplicaciones potencialmente maliciosas vuelve a la “seguridad local” mas importante que nunca.
- El grupo de desarrolladores que fomenta a SystemD, incluyendo posiblemente a su autor, no se contenta con que SystemD sea una opción entre muchas. Al proveer una API pública para que sea utilizada por otras aplicaciones, SystemD se ha vuelto difícil de no usar una vez que alcance cierto umbral de adopción.
Con respecto a las actualizaciones, la herramienta de control de SystemD, systemctl, tiene un comando daemon-reexec que serializa el estado de SystemD, se re-ejecuta a sí mismo, y continúa sin interrupción. Esto puede utilizarse para pasar a una nueva versión sin reiniciar. Muchos programas ya utilizan esta técnica.
Desafortunadamente, esto trae de vuelta al problema del PID 1 siendo especial. Para aplicaciones normales, si la re-ejecución falla, lo peor que puede pasar es que el proceso muera y se reinicie (manualmente o por un proceso monitor) si es necesario. Sin embargo, para el PID 1, si falla la re-ejecución, el sistema completo se cae (kernel panic).
Por cualquier común razón por la que pueda fallar, la llamada al sistema execve retorna failure en la imagen original del proceso, permitiendo que el programa maneje el error. Sin embargo, la falla de execve no es totalmente atómica:
- El kernel puede fallar en configurar la imagen para el nuevo proceso luego de que la imagen original ya haya sido destruida; la principal situación en la cual esto puede suceder es cuando se han agotado los recursos disponibles.
- Inclusive luego de que el kernel haya configurado exitosamente la nueva imagen y transfiera la ejecución al nuevo proceso, es posible que ocurran fallas previas a la transferencia de control a la aplicación. Esto puede ocurrir en el linker dinámico (falta de recursos disponibles u otra falla transitoria mientras se mapean las librerías requeridas o archivos de configuración) o en el código de inicio de libc. Utilizando libc con linkeado estático o incluso linkeado dinámico sin librerías adicionales elimina estos casos de fallo, pero SystemD está pensado para ser usado con glibc.
Además, SystemD puede fallar en restaurar su estado serializado debido a fallos de alocación de recursos, o si la nueva y vieja versión difieren lo suficiente para que el viejo estado no sea utilizable por la nueva versión.
Entonces si no es SystemD, ¿qué utilizar? La discusión del grupo de desarrollo de Debian acerca de si adoptar SystemD o no básicamente degeneró en una falsa dicotomía entre SystemD y upstart. Y, exceptuando a algunos viejos gruñones, mantener el viejo SysVInit no es una opción atractiva. Entonces a pesar de todos sus fallos, ¿es SystemD la mejor opción? No.
Ninguna de las cosas que SystemD hace bien son revolucionarias. Muchas otras veces han surgido posibles reemplazantes para el viejo initd: daemontools, ruinit, Supervisor, entre otros han resuelto el problema de “el viejo init está no sirve más” (cada uno con sus propios defectos). Su fallo en desplazar al viejo init en las principales distribuciones no tiene nada que ver con su capacidad para resolver el problema, y tiene todo que ver con marketing. No hay nada grande y revolucionario en SystemD. Su popularidad es el resultado de una estrategia de marketing agresivo y dictatorial, que incluye elementos como:
- Deglutiendo otros componentes “esenciales” del sistema como udev, y haciendo que sean difíciles de utilizar sin SystemD (a pesar que existe eudev).
- Configurando un bloqueo de API (es decir, haciendo que las interfaces de DBus provistas por SystemD se vuelvan una API necesaria en que los programas de nivel usuario deban depender).
- Dictando políticas mas que limitando su alcance para que sea el usuario, administrador o integrador (de una distribución) quien se encargue de integrarlo. Esto elimina debates y dificultades para lograr consenso, por lo tanto acelerando su adopción a expensas de la flexibilidad y diversidad.
Entonces, ¿cómo debe hacerse correctamente un init?
La forma Unix: con programas simples auto-contenidos que hagan una simple tarea y la hagan bien.
Primero, se debe sacar todo fuera de PID 1.
La forma SystemD: tomando ventaja de las propiedades especiales de PID 1 en la mayor medida posible. Esto resulta en un alcance siempre en expansión y exacerba todos los problemas descritos anteriormente (y probablemente muchos otros que aún no se han descubierto).
La forma correcta: acabar con todo lo especial acerca de PID 1 haciendo que PID 1 no haga nada excepto iniciar el verdadero script de inicio. De esta manera no hay forma de que init falle en tiempo de ejecución, y no hay necesidad de actualizarlo una vez que haya iniciado correctamente.
Luego, desde el script de inicio, correr un sistema de supervisión de procesos fuera de PID 1 para manejar demonios como procesos hijos inmediatos (sin ejecutar en background). Como se mencionó anteriormente existen diferentes alternativas. No es claro que alguna esté lo suficientemente pulida o sea lo suficientemente robusta para satisfacer las principales distribuciones en la actualidad. Pero tampoco lo es SystemD, sus defensores sólo tienen talento para ocultar la suciedad debajo de la alfombra.
Lo que las alternativas existentes tienen, sin embargo, es un mejor diseño, principalmente en la forma de tener un alcance bien definido, más que un Katamari Damacy.
Si ninguna de ellas está lista para debutar en primera, entonces aquellos deseosos de remplazar al viejo init en sus distribuciones favoritas necesitan avanzar y pulir una de las soluciones existentes, o escribir una mejor implementación basada en los mismos principios. Cualquiera de estas opciones será mucho menos trabajo que arreglar lo que está mal con SystemD.
Cualquiera sea el sistema elegido, el criterio más importante es que sea transparente a las aplicaciones. Por más de 30 años, la elección del sistema de inicio ha sido irrelevante para todos excepto integradores y administradores. Las aplicaciones a nivel usuario no tienen ninguna razón para conocer o preocuparse si se está utilizando SysVInit con runlevels, upstart, o un sistema avanzado de supervisión de procesos (o incluso /bin/sh). Irónicamente, este tipo de modularidad e intercambiabilidad es lo que ha hecho posibles a los sistemas; si hubiésemos comenzado con un sistema monolítico, orientado a un bloqueo de API, cambiar el sistema de inicio por algo nuevo e innovativo hoy no sería una opción.
De este artículo se desprende la idea de que con SystemD han creado prácticamente una especie de segundo kernel. Una pieza insustituible que abarca una cantidad impresionante de funciones heterogéneas, y una vez adoptado pareciera que no hay vuelta atrás.
Han tomado una pieza de software simple y efectiva y la han usado como medio para que Red Hat sea quien tenga el control sobre la comunidad.