đ« 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
nginx
en 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.service
nginx.service
webapp.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Ă© đ.