đ« Systemd dans un conteneur avec Podman
Interaction entre Systemd et les conteneurs
Systemd est le programme d’initialisation ou init le plus courant dans les systĂšmes Gnu/Linux modernes, c’est-Ă -dire le premier processus (PID 1) dĂ©marrĂ© par le systĂšme qui Ă©xecute tous les autres et permet de gĂ©rer l’ordre de dĂ©marrage, etc. C’est trĂšs utile pour pouvoir gĂ©rer un ensemble de processus qui tournent en parallĂšles sur le systĂšme.
Les phases d’initialisations d’un systĂšme sur une machine physique :
Dans le cas d’un conteneur, le kernel est dĂ©jĂ fonctionnel, le premier (et souvent le seul) processus lancĂ© dans le conteneur (PID 1) n’a pas besoin d’ĂȘtre un logiciel spĂ©cialisĂ© en init, on lance donc gĂ©nĂ©ralement directement le logiciel dont on a besoin.
De fait, un outil comme docker n’est pas conçu pour fonctionner avec un init tel que systemd. Ce n’est pas tant un souci technique (des mĂ©thodes existent) que philosophique qu’on pourrait rĂ©sumer simplement par :
Un conteneur doit contenir le minimum de choses : taille minimale et un seul processus. Si on veut plusieurs processus, on utilise plusieurs conteneurs.
On est donc là dans une approche assez microservice avec docker ou autre en orchestrateur de conteneur démarrant chacun un unique processus.
On voit bien lĂ que les 2 programmes que sont docker et systemd se recoupe un peu, chacun a sa maniĂšre gĂšre des processus et leur ordonnancement,
et l’usage de l’un ou l’autre dĂ©pendra de la couche ou l’ordonnancement se fait.
il est ainsi possible de :
- gĂ©rer des conteneurs via systemd, pour ce faire la meilleure mĂ©thode aujourd’hui semble podman avec quadlet.
- gĂ©rer avec systemd diffĂ©rents niveaux plus ou moins procÄ„e d’un conteneur avec uniquement systemd :
- Une isolation partielle du service avec différentes directives tel que
ProtectSystem,ProtectHome,DynamicUser, ⊠- Un vrai mécanisme de conteneur avec systemd-nspawn
- Une “portabilitĂ©” des services avec les portables service.
- Une isolation partielle du service avec différentes directives tel que
- dĂ©marrer systemd dans un conteneur : Ce qu’on va faire ici :
Pourquoi mettre systemd dans un conteneur ?
Les conteneurs sont trĂšs bien, mais la philosophie classique consistant Ă avoir des conteneurs minimale ne faisant tourner qu’un unique service se rĂ©vĂšle de fait pas toujours la plus pertinente.
En effet :
- Les outils d’orchestration de conteneurs ne sont pas forcĂ©ment plus pratique dans certain contexte qu’un systemd. Un administrateur systĂšme prĂ©fĂšrera ne pas avoir deux mĂ©canismes d’orchestration totalement disjoints.
- Les conteneurs OCI (utilisĂ© par docker et podman) sont trĂšs pratique et simple Ă partager, c’est une mĂ©thode pratique pour fournir du logiciel, pourquoi ne pas pouvoir fournir directement un logiciel complet plutĂŽt qu’une pile de composants Ă orchestrer ?
- Le fait de ne pas utiliser systemd avec les conteneurs, implique pour un logiciel qu’on envisage de packager pour une distribution comme debian de devoir dupliquĂ© un mĂȘme savoir (la gestion du service/processus) dans deux formats diffĂ©rents (service systemd vs paramĂštre docker/docker-compose/kubernetes).
Dans ces conditions, il me semble que pour un logiciel assez simple avec plusieurs composants qu’on envisage de packager maintenant ou Ă terme, il est plus simple de simplement gĂ©nĂ©rer un conteneur activant le/les mĂȘmes services systemd que le paquet de distribution.
Bien Ă©videmment, il faut choisir intelligemment les composant Ă intĂ©grer ou pas dedans. Selon les besoins, dans l’exemple ci-dessous, il n’aurait pas exemple Ă©tĂ© peut ĂȘtre plus pertinent que le conteneur requiert un service valkey externe plutĂŽt que de l’intĂ©grer directement.
Exemple :
Explication
J’ai rĂ©alisĂ© un exemple de conteneur avec systemd Ă l’intĂ©rieur. Il contient une application web minimale avec plusieurs Ă©lĂ©ments :
- Une base de donnée clé/valeur (
valkey). - Un serveur web python (
uvicorn) contenant une api (avecfastapi) et utilisant la BDD. - Un frontal web
nginxen proxy inverse.
J’utilise podman plutĂŽt que docker dans ce cas de figure, car l’intĂ©gration systemd est plutĂŽt poussĂ© et ne requiert aucun effort, podman dĂ©tecte par dĂ©fault automatiquement le conteneur utilisant systemd et fait le nĂ©cessaire pour que le tout soit fonctionnel.
Ordonnancement prévu des services :
Implementation
C’est relativement facile Ă faire, voici le Containerfile :
# On installe la base du systĂšme
FROM debian:trixie as build
RUN apt-get update && apt-get install -y systemd nginx valkey-server \
python3-fastapi python3-uvicorn python3-sdnotify python3-redis
# On ajoute les fichiers de notre application, notez le fichier webapp.service !
COPY nginx/conf.d /etc/nginx/conf.d
COPY www /var/www/html
COPY webapp.service /etc/systemd/system
COPY api/* /srv
RUN rm /etc/nginx/sites-available/* && rm /etc/nginx/sites-enabled/*
# On à besoin de systemd fonctionnel pour lancer systemctl donc un nouveau stage est nécessaire
FROM build
# On active ici nos services spéciaux, dans le cas présent on préferera un unique service ou target
# contenant toutes les dépendances
RUN systemctl enable webapp
# la commande magique qui lance systemd en processus principal
CMD [ "/lib/systemd/systemd" ]
EXPOSE 80 6379
Et le service webapp.service qu’on lance :
[Unit]
Description=Webapp
# On lance toutes les dépendances de notre service ici
Requires=multi-user.target
Wants=valkey.service
Wants=nginx.service
After=multi-user.target
After=valkey.service
After=nginx.service
[Service]
# Le service python qu'on lance implémente sd-notify donc on
Type=notify
NotifyAccess=all
EnvironmentFile=/srv/api_config.env
Restart=always
RestartSec=3
WorkingDirectory=/srv
ExecStartPre=mkdir -p /run/uvicorn
ExecStart=python3 -m uvicorn --workers ${WORKERS_NB} --uds /run/uvicorn/appname.sock api:app
ExecStopPost=rm -rf /run/uvicorn
[Install]
WantedBy=multi-user.target
Vous pouvez voir le détail ici.
On pourrait bien évidemment profiter de la puissance de systemd ici et ajouter facilement des services :
- Qui s’active en fonction d’Ă©vĂšnements : socket, modification de fichier, âŠ
- Qui s’exĂ©cute Ă interval rĂ©gulier avec les timers.
- et probablement d’autres trucs amusants auquel je n’ai pas pensĂ©.
Et les logs ?
Un point particulier Ă gĂ©rer dans ce cas est l’obtention des logs.
Dans le cas standard, la méthode classique pour avoir les logs du conteneur (test dans ce cas),
donne uniquement le contenu de la sortie standard, ce qui n’est pas trĂšs utile.
⯠podman logs test
systemd 257.5-2 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +IPE +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK +BTF -XKBCOMMON -UTMP +SYSVINIT +LIBARCHIVE)
Detected virtualization podman.
Detected architecture x86-64.
Welcome to Debian GNU/Linux trixie/sid!
bpf-restrict-fs: BPF LSM hook not enabled in the kernel, BPF LSM not supported.
Queued start job for default target graphical.target.
[ OK ] Created slice system-getty.slice - Slice /system/getty.
.........
[ OK ] Reached target getty.target - Login Prompts.
[ OK ] Started dbus.service - D-Bus System Message Bus.
[ OK ] Started systemd-logind.service - User Login Management.
[ OK ] Started valkey-server.service - Advanced key-value store.
[ OK ] Started nginx.service - A high performance web server and a reverse proxy server.
[ OK ] Reached target multi-user.target - Multi-User System.
[ OK ] Reached target graphical.target - Graphical Interface.
Starting webapp.service - Webapp...
Debian GNU/Linux trixie/sid 96206de07c7c console
idem avec journalctl CONTAINER_NAME=test
Ce qu’il nous faut ce sont les logs des services suivants :
valkey-server.servicenginx.servicewebapp.service
On peut donc lancer journalctl dans le conteneur avec exec :
podman exec test journalctl
# pour un service spécifique
podman exec test journalctl -u nginx.service
Il est probablement possible d’accĂ©der au log journalctl du conteneur d’une façon plus Ă©lĂ©gante depuis le journalctl de l’hĂŽte.
Une mĂ©thode semble ĂȘtre en partageant le dossier /var/log/journal (montage de volume du conteneur) avec le paramĂštre --directory de journalctl,
mais mon test n’a pas Ă©tĂ© concluant, car la version de systemd n’Ă©tant pas compatible l’hĂŽte n’Ă©tais pas en mesure de lire le journal du conteneur.
Une autre mĂ©thode probablement plus fiable, mais nĂ©cessitant des ajustements est d’utiliser systemd-journal-remote, qui permet de transmettre les logs par le rĂ©seau en http
Je pense qu’il doit exister d’autres solutions en creusant bien (compatibilitĂ© syslog ? partage de socket ?).
Pour conclure :
Systemd dans un conteneur, c’est possible et pas bien compliquĂ© đ.