Hemos estado siguiendo el tema del uso de systemd en contenedores desde hace tiempo. Ya en 2014, nuestro ingeniero de seguridad, Daniel Walsh, escribió un artículo , y un par de años después, otro titulado , en el que constató que la situación no había mejorado mucho. En particular, escribió que «lamentablemente, dos años después, si buscas en Google "Docker system", lo primero que aparece es su antiguo artículo. Así que es hora de hacer un cambio». Además, ya hemos hablado de .
. En este artículo, mostraremos qué ha cambiado desde entonces y cómo Podman puede ayudarnos en este asunto.
Existen muchas razones para ejecutar systemd dentro de un contenedor, tales como:
- Contenedores multiserver – muchos quieren llevar sus aplicaciones multiserver de máquinas virtuales a contenedores. Aunque lo mejor sería descomponer tales aplicaciones en microservicios, no todos saben hacerlo o simplemente no tienen tiempo. Por lo tanto, ejecutar estas aplicaciones como servicios iniciados por systemd a partir de archivos de unidad tiene sentido.
- Archivos de unidad de Systemd – la mayoría de las aplicaciones que se ejecutan dentro de contenedores están construidas a partir de código que previamente se ejecutaba en máquinas virtuales o físicas. Estas aplicaciones tienen un archivo de unidad que fue escrito para ellas y entiende cómo deben iniciarse. Así que es mejor iniciar los servicios con métodos soportados y no vulnerando nuestro propio servicio init.
- Systemd es un administrador de procesos. Se encarga de gestionar servicios (detener, reiniciar servicios o eliminar procesos huérfanos) mejor que cualquier otra herramienta.
Sin embargo, hay muchas razones para no ejecutar systemd en contenedores. La principal es que systemd/journald controla la salida de los contenedores, y herramientas como o asumen que los contenedores escribirán el log directamente en stdout y stderr. Por lo tanto, si planeas gestionar contenedores a través de herramientas de orquestación como las mencionadas, deberías pensar seriamente en el uso de contenedores basados en systemd. Además, los desarrolladores de Docker y Moby han estado a menudo en contra de usar systemd en contenedores.
La llegada de Podman
Nos complace informar que la situación finalmente ha avanzado. El equipo de Red Hat responsable del lanzamiento de contenedores ha decidido desarrollar . Ha recibido el nombre de y ofrece la misma interfaz de línea de comandos (CLI) que Docker. Y casi todos los comandos de Docker se pueden usar de la misma manera en Podman. A menudo realizamos seminarios que ahora se llaman , y la primera diapositiva aconseja escribir: alias docker=podman.
Muchos lo hacen.
Con nuestro Podman no estamos en contra de los contenedores basados en systemd. De hecho, systemd se utiliza con mayor frecuencia como la subsistema init de Linux, y no permitirle funcionar adecuadamente en los contenedores significa ignorar cómo miles de personas están acostumbradas a ejecutar contenedores.
Podman sabe qué hacer para que systemd funcione correctamente en un contenedor. Necesita cosas como montar tmpfs en /run y /tmp. Le gusta cuando el entorno 'contenedor' está habilitado y espera permisos de escritura en su parte del directorio cgroup y en la carpeta /var/log/journald.
Al iniciar un contenedor en el que el primer comando es init o systemd, Podman configura automáticamente tmpfs y Cgroups para que el inicio de systemd ocurra sin problemas. Para bloquear este modo de inicio automático, se utiliza la opción --systemd=false. Tenga en cuenta que Podman utiliza el modo systemd solo cuando detecta que debe ejecutar un comando systemd o init.
Aquí hay un extracto del manual:
man podman run
…--systemd=true|false
Iniciar un contenedor en modo systemd. Activado por defecto.
Si se ejecuta un comando systemd o init dentro del contenedor, Podman configurará los puntos de montaje tmpfs en los siguientes directorios:
/run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal
Además, como señal de detención, se utilizará por defecto SIGRTMIN+3.
Todo esto permite que systemd funcione en un contenedor aislado sin ninguna modificación.
NOTA: systemd intenta escribir en el sistema de archivos cgroup. Sin embargo, SELinux, por defecto, impide que los contenedores hagan esto. Para permitir la escritura, habilite el parámetro booleano container_manage_cgroup:
setsebool -P container_manage_cgroup true
Ahora veamos cómo se ve un Dockerfile para iniciar systemd en un contenedor utilizando Podman:
# cat Dockerfile
FROM fedora
RUN dnf -y install httpd; dnf clean all; systemctl enable httpd
EXPOSE 80
CMD [ "/sbin/init" ]
Y eso es todo.
Ahora construyamos el contenedor:
# podman build -t systemd .
Decimos a SELinux que permita a systemd modificar la configuración de Cgroups:
# setsebool -P container_manage_cgroup true
Muchos, por cierto, olvidan este paso. Afortunadamente, solo es necesario hacerlo una vez y la configuración se guarda después de reiniciar el sistema.
Ahora simplemente ejecutamos el contenedor:
# podman run -ti -p 80:80 systemd
systemd 239 running in system mode. (+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid)
Detected virtualization container-other.
Detected architecture x86-64.
Welcome to Fedora 29 (Container Image)!
Set hostname to <1b51b684bc99>.
Failed to install release agent, ignoring: Read-only file system
File /usr/lib/systemd/system/systemd-journald.service:26 configures an IP firewall (IPAddressDeny=any), but the local system does not support BPF/cgroup based firewalling.
Proceeding WITHOUT firewalling in effect! (This warning is only shown for the first loaded unit using IP firewalling.)
[ OK ] Listening on initctl Compatibility Named Pipe.
[ OK ] Listening on Journal Socket (/dev/log).
[ OK ] Started Forward Password Requests to Wall Directory Watch.
[ OK ] Started Dispatch Password Requests to Console Directory Watch.
[ OK ] Reached target Slices.
…
[ OK ] Started The Apache HTTP Server.
Listo, el servicio se ha iniciado y funciona:
$ curl localhost
<html xml_lang="en" lang="en">
…
</html>
NOTA: ¡No intentes repetir esto en Docker! Aún se necesitan movimientos complicados para ejecutar este tipo de contenedores a través del demonio. (Se requerirán campos y paquetes adicionales para que todo funcione sin problemas en Docker, o se deberá ejecutar en un contenedor privilegiado. Para más detalles, consulta .)
Un par de cosas interesantes sobre Podman y systemd
Podman funciona mejor que Docker en archivos de unidad de systemd
Si los contenedores deben iniciarse al arrancar el sistema, se pueden insertar simplemente los comandos correspondientes de Podman en el archivo de unidad de systemd, el cual iniciará el servicio y lo monitorizará. Podman utiliza el modelo de bifurcación estándar al ejecutar (fork-exec). En otras palabras, los procesos de contenedor son hijos del proceso de Podman, por lo que systemd puede monitorearlos fácilmente.
Docker utiliza un modelo cliente-servidor, y los comandos CLI de Docker también se pueden colocar directamente en el archivo de unidad. Sin embargo, una vez que el cliente de Docker se conecta al demonio de Docker, este (el cliente) se convierte simplemente en otro proceso que maneja stdin y stdout. A su vez, systemd no tiene conocimiento de la relación entre el cliente de Docker y el contenedor que se ejecuta bajo el demonio de Docker, por lo que en esta modelo, systemd no puede monitorear el servicio.
Activación de systemd a través de un socket
Podman maneja correctamente la activación a través de un socket. Dado que Podman utiliza el modelo fork-exec, puede pasar el socket a sus procesos de contenedor hijo. Docker no puede hacer esto ya que utiliza un modelo cliente-servidor.
El servicio varlink, que utiliza Podman para interactuar con clientes remotos y contenedores, se activa a través de un socket. El paquete cockpit-podman, escrito en Node.js y que forma parte del proyecto cockpit, permite a las personas interactuar con los contenedores de Podman a través de una interfaz web. El demonio web que ejecuta cockpit-podman envía mensajes al socket varlink, que es escuchado por systemd. Luego, systemd activa el programa Podman para recibir mensajes y comenzar a gestionar los contenedores. La activación de systemd a través del socket permite prescindir de un demonio en funcionamiento continuo al implementar APIs remotas.
Además, estamos desarrollando otro cliente para Podman llamado podman-remote, que implementa la misma CLI de Podman, pero llama a varlink para iniciar contenedores. Podman-remote puede funcionar sobre sesiones SSH, lo que permite interactuar de forma segura con contenedores en diferentes máquinas. Con el tiempo, planeamos habilitar podman-remote para soportar MacOS y Windows, junto con Linux, para que los desarrolladores en estas plataformas puedan ejecutar una máquina virtual Linux con Podman varlink funcionando y tener la plena sensación de que los contenedores se ejecutan en su máquina local.
SD_NOTIFY
Systemd permite retrasar el inicio de servicios auxiliares hasta que se inicie el servicio contenedorizado que necesitan. Podman puede pasar el socket SD_NOTIFY al servicio contenedorizado, para que este servicio notifique a systemd que está listo para funcionar. Y de nuevo, Docker, que utiliza un modelo cliente-servidor, no puede hacer esto.
Planes
Estamos planeando agregar el comando podman generate systemd CONTAINERID, que generará un archivo de unidad systemd para gestionar un contenedor específico. Esto debe funcionar tanto en modos root como rootless para contenedores no privilegiados. Incluso hemos visto una solicitud para crear un entorno de ejecución compatible con OCI para systemd-nspawn.
Conclusión
Ejecutar systemd en un contenedor es una necesidad bastante comprensible. Y gracias a Podman, finalmente tenemos un entorno de ejecución de contenedores que no es hostil a systemd, sino que permite usarlo fácilmente.
Fuente: habr.com
