Seguiamo da tempo il tema dell'uso di systemd nei contenitori. Già nel 2014 il nostro ingegnere della sicurezza Daniel Walsh scrisse un articolo , e qualche anno dopo un altro, intitolato , nel quale constatava che la situazione non era migliorata di molto. In particolare, scriveva che "sfortunatamente, anche due anni dopo, cercando 'Docker system', la prima cosa che appare è ancora il suo vecchio articolo. È tempo di cambiare qualcosa". Inoltre, abbiamo già parlato di .
In questo articolo mostreremo cosa è cambiato nel tempo e come Podman possa aiutarci in questo problema.
Ci sono molte ragioni per eseguire systemd all'interno di un contenitore, come ad esempio:
- Contenitori multi-servizio – molti vogliono estrarre le proprie applicazioni multi-servizio da macchine virtuali e eseguirle in contenitori. Ovviamente sarebbe meglio suddividere tali applicazioni in microservizi, ma non tutti sono in grado di farlo o semplicemente non hanno tempo. Quindi eseguire tali applicazioni sotto forma di servizi avviati da systemd tramite file di unità ha un senso.
- File di unità Systemd – la maggior parte delle applicazioni che funzionano all'interno dei contenitori sono composte da codice che era stato precedentemente eseguito su macchine virtuali o fisiche. Queste applicazioni hanno un file di unità, creato appositamente per esse, che sa come avviarle. Quindi è comunque meglio avviare i servizi utilizzando metodi supportati, piuttosto che hackerare il proprio servizio init.
- Systemd è un gestore di processi. Gestisce i servizi (termina, riavvia i servizi o uccide processi zombie) meglio di qualsiasi altro strumento.
D'altra parte, ci sono molte ragioni per non eseguire systemd nei contenitori. La principale è che systemd/journald controlla l'output dei contenitori, e strumenti come o si aspettano che i contenitori scrivano i log direttamente in stdout e stderr. Quindi, se stai pensando di gestire i contenitori tramite strumenti di orchestrazione come quelli sopra citati, dovresti seriamente considerare l'uso di contenitori basati su systemd. Inoltre, gli sviluppatori di Docker e Moby sono stati spesso fortemente contrari all'uso di systemd nei contenitori.
L'avvento di Podman
Siamo lieti di annunciare che la situazione finalmente si è sbloccata. Il team di Red Hat responsabile del lancio dei container ha deciso di sviluppare . Ha ricevuto il nome e offre la stessa interfaccia a riga di comando (CLI) di Docker. Praticamente tutti i comandi di Docker possono essere utilizzati in modo identico in Podman. Spesso organizziamo seminari, che ora si chiamano , e la prima slide invita a scrivere: alias docker=podman.
Molti lo fanno.
Con il nostro Podman non siamo affatto contrari ai container basati su systemd. Infatti, systemd è frequentemente utilizzato come sistema init in Linux e non dare a esso la possibilità di funzionare normalmente nei container significa ignorare come migliaia di persone sono abituate a lanciare container.
Podman sa cosa fare affinché systemd funzioni correttamente in un container. Ha bisogno di cose come il montaggio di tmpfs su /run e /tmp. Gli piace quando è attivato l'ambiente "container" e attende i permessi di scrittura nella sua parte del catalogo cgroup e nella cartella /var/log/journald.
Quando avvii un container il cui primo comando è init o systemd, Podman configura automaticamente tmpfs e Cgroups affinché l'avvio di systemd avvenga senza problemi. Per bloccare questo avvio automatico, si utilizza l'opzione —systemd=false. Nota che Podman utilizza la modalità systemd solo quando rileva la necessità di eseguire un comando systemd o init.
Ecco un estratto dal manuale:
man podman run
…–systemd=true|false
Avvia un container in modalità systemd. Attivata per impostazione predefinita.
Se un comando systemd o init viene eseguito all'interno del container, Podman configurerà i punti di montaggio tmpfs nelle seguenti directory:
/run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal
Inoltre, SIGRTMIN+3 verrà utilizzato come segnale di arresto per impostazione predefinita.
Tutto ciò consente a systemd di funzionare in un container isolato senza alcuna modifica.
NOTA: systemd tenta di scrivere nel file system cgroup. Tuttavia, SELinux impedisce per impostazione predefinita ai container di farlo. Per consentire la scrittura, attiva il parametro booleano container_manage_cgroup:
setsebool -P container_manage_cgroup true
Ora vediamo come appare un Dockerfile per avviare systemd in un container utilizzando Podman:
# cat Dockerfile
FROM fedora
RUN dnf -y install httpd; dnf clean all; systemctl enable httpd
EXPOSE 80
CMD [ "/sbin/init" ]
Ecco tutto.
Adesso costruiamo il container:
# podman build -t systemd .
Diciamo a SELinux di consentire a systemd di modificare la configurazione dei Cgroups:
# setsebool -P container_manage_cgroup true
Molti, per dire la verità, dimenticano questo passaggio. Fortunatamente, è sufficiente farlo una sola volta e la configurazione viene salvata dopo il riavvio del sistema.
Ora basta avviare il container:
# 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.
Tutto, il servizio è avviato e funzionante:
$ curl localhost
<html xml_lang="en" lang="en">
…
</html>
NOTA: Non provate a ripetere questo su Docker! Ci vogliono ancora degli aggiustamenti per avviare questo tipo di container tramite il demone. (Saranno necessari campi e pacchetti aggiuntivi affinché tutto funzioni in modo fluido su Docker, oppure dovrete avviare in un container con privilegi. Maggiori dettagli in .)
Altre due cose interessanti su Podman e systemd
Podman funziona meglio di Docker nei file unit di systemd
Se i container devono essere avviati all'avvio del sistema, è possibile inserire semplicemente i comandi Podman appropriati nel file unit di systemd, che avvierà il servizio e lo monitorerà. Podman utilizza il modello standard di fork-exec. In altre parole, i processi dei container sono figli del processo di Podman, quindi systemd può facilmente monitorarli.
Docker utilizza il modello client-server e i comandi CLI di Docker possono essere posizionati direttamente nel file unit. Tuttavia, dopo che il client di Docker si connette al demone Docker, esso (il client) diventa un normale processo che gestisce stdin e stdout. Di conseguenza, systemd non ha idea della connessione tra il client di Docker e il container che opera sotto il demone Docker, e quindi, all'interno di questo modello, systemd non può monitorare il servizio.
Attivazione di systemd tramite socket
Podman gestisce correttamente l'attivazione tramite socket. Poiché Podman utilizza il modello fork-exec, può inoltrare il socket ai propri processi container figlio. Docker non può farlo, poiché utilizza il modello client-server.
Il servizio varlink, utilizzato da Podman per interagire con i client remoti e i container, viene attivato attraverso un socket. Il pacchetto cockpit-podman, scritto in Node.js e parte del progetto cockpit, consente agli utenti di interagire con i container Podman attraverso un'interfaccia web. Il demone web, su cui gira cockpit-podman, invia messaggi al socket varlink, che è in ascolto da systemd. Di conseguenza, systemd attiva il programma Podman per ricevere messaggi e iniziare a gestire i container. L'attivazione di systemd tramite socket consente di evitare un demone sempre attivo nella realizzazione di API remoti.
Inoltre, stiamo sviluppando un altro client per Podman chiamato podman-remote, che implementa lo stesso Podman CLI, ma invoca varlink per avviare i container. Podman-remote può funzionare su sessioni SSH, permettendo un'interazione sicura con i container su diverse macchine. Nel tempo, prevediamo di utilizzare podman-remote per supportare MacOS e Windows insieme a Linux, affinché gli sviluppatori su queste piattaforme possano avviare una macchina virtuale Linux con Podman varlink in esecuzione e avere la completa sensazione che i container vengano eseguiti sulla macchina locale.
SD_NOTIFY
Systemd consente di posticipare l'avvio dei servizi ausiliari fino a quando non viene avviato il servizio containerizzato di cui hanno bisogno. Podman può inoltrare il socket SD_NOTIFY al servizio containerizzato, affinché questo avvisi systemd della sua prontezza. E ancora, Docker, che utilizza un modello client-server, non lo fa.
In programma
Prevediamo di aggiungere il comando podman generate systemd CONTAINERID, che genererà un file unit di systemd per gestire un determinato container. Questo dovrebbe funzionare sia in modalità root che in modalità rootless per i container non privilegiati. Abbiamo anche visto una richiesta per la creazione di un ambiente di esecuzione compatibile con OCI systemd-nspawn.
Conclusione
Eseguire systemd in un container è una necessità comprensibile. E grazie a Podman, finalmente abbiamo un ambiente di esecuzione dei container che non è in conflitto con systemd, ma rende facile utilizzarlo.
Fonte: habr.com
