đŸ«™ Systemd dans un conteneur avec Podman

| ~ 6 mins | 1264 mots

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 :

Phase d’initialisation d’un systùme

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.

Phase d’initialisation d’un systùme

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 :

Phase d’initialisation d’un systùme

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 :

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 :

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 :

Ordonnancement 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 :

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 :

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Ă© 😀.