Faire fonctionner qemu-user-static avec Podman
Récemment, j’ai eu à faire tourner des conteneurs ARM64 sur le laptop x86 d’un client. Facile me direz-vous : tu n’as qu’à installer qemu-user-static sur le laptop ! Et t’as aussi un conteneur tout prêt sur Docker Hub, au cas où !
La messe est dite ? Pas si sûr…
État des lieux
En effet, s’il est facile d’installer qemu-user-static sur Fedora, ce n’est pas le cas de CentOS Stream ou Red Hat Enterprise Linux. On peut toujours récupérer le paquet d’un repository Fedora et l’installer sur ces downstream de Fedora. Ça marche mais ça reste un paquet orphelin qui ne sera jamais mis à jour…
Et l’image docker.io/multiarch/qemu-user-static que l’on peut trouver sur le Docker Hub ? Elle n’a pas été mise à jour depuis 2 ans et la dernière version disponible de qemu est la 7.2. Quand on sait que la version 10 de qemu a été publiée récemment, ça fait désordre…
TL;DR
Deux one-liners, un pour la construction de l’image et un pour son exécution.
Construire l’image de conteneur localhost/qemu-user-static :
sudo podman build -f https://red.ht/qemu-user-static -t localhost/qemu-user-static /tmp
Exécuter qemu-user-static :
sudo podman run --rm --privileged --security-opt label=filetype:container_file_t --security-opt label=level:s0 --security-opt label=type:spc_t localhost/qemu-user-static
Ça y est ! Vous pouvez maintenant exécuter une image de conteneur qui est dans une architecture matérielle différente de celle de votre machine.
Exemple :
$ arch
x86_64
$ podman run -it --rm --platform linux/arm64/v8 docker.io/library/alpine
/ # arch
aarch64
Construction de l’image
J’ai choisi de baser mon image sur les images officielles Fedora (version 42 lors de l’écriture de cet article). Si vous avez déjà travaillé avec l’image docker.io/multiarch/qemu-user-static, vous verrez que j’ai un peu modernisé tout ça :
- Plus de script en provenance du repo qemu, le composant systemd-binfmt fournit tout le nécessaire.
- Plus d’option à passer lors de l’exécution, le script désenregistre tous les binaires enregistrés auprès de sous-système binfmt et enregistre tous ses binaires
qemu-*-static
à la place.
Le résultat est dépouillé :
FROM quay.io/fedora/fedora:42
RUN dnf install -y qemu-user-static \
&& dnf clean all
ADD container-entrypoint /
ENTRYPOINT ["/container-entrypoint"]
CMD []
Le script container-entrypoint est lui aussi réduit à son strict nécessaire :
#!/bin/sh
set -Eeuo pipefail
if [ ! -d /proc/sys/fs/binfmt_misc ]; then
echo "No binfmt support in the kernel."
echo " Try: '/sbin/modprobe binfmt_misc' from the host"
exit 1
fi
if [ ! -f /proc/sys/fs/binfmt_misc/register ]; then
echo "Mounting /proc/sys/fs/binfmt_misc..."
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
fi
echo "Cleaning up..."
find /proc/sys/fs/binfmt_misc -type f -name 'qemu-*' -exec sh -c 'echo -1 > {}' \;
echo "Registering..."
exec /usr/lib/systemd/systemd-binfmt
Exécution
Une fois l’image construite, il faut l’exécuter pour que les binaires qemu-*-static
soient enregistrés auprès du système binfmt.
Mais il y a une petite subtilité : sur une distribution Linux type Fedora, CentOS Stream ou RHEL, le système SELinux veille au grain !
Et si quelque chose tente de percer l’étanchéité du moteur de conteneurisation, SELinux le détecte et l’action est interdite.
Et c’est exactement ce qui se produit quand on exécute naïvement l’image construite précédemment : les binaires qemu-*-static
ont une étiquette SELinux qui interdit leur exécution par le moteur de conteneurisation au moment où il s’apprête à lancer le PID 1 du conteneur à émuler.
La solution ? Lancer le conteneur avec les options SELinux qui donne au conteneur les bonnes étiquettes SELinux afin que l’action soit autorisée.
C’est le rôle des options --security-opt
:
label=filetype:container_file_t
: les fichiers de l’image de conteneur sont étiquetés avec le type SELinux container_file_t.label=label=level:s0
: les fichiers de l’image de conteneur sont étiquetés avec le niveau SELinux s0.
Ces deux options, vous l’avez peut-être reconnu, font que les fichiers de notre image localhost/qemu-user-static ont l’étiquette SELinux des volumes partagés. Le partage de ces fichiers est donc autorisé entre les conteneurs.
Les options --security-opt label=type:spc_t
et --privileged
permettent au conteneur de s’exécuter en root, de pouvoir procéder au montage du pseudo système de fichier binfmt_misc et d’enregistrer les binaires qemu-*-static
.
La ligne de commande complète est :
sudo podman run --rm --privileged --security-opt label=filetype:container_file_t --security-opt label=level:s0 --security-opt label=type:spc_t localhost/qemu-user-static
Si vous oubliez ces options de sécurité, lorsque vous voudrez exécuter un conteneur dans une architecture différente, podman se terminera sans erreur mais sans rien exécuter…
Avec un peu de chance, vous repèrerez alors dans vos logs le message d’erreur suivant :
[10051.131634] audit: type=1400 audit(1749488918.807:3124): avc: denied { read execute } for pid=32544 comm="dumb-init" path="/usr/bin/qemu-x86_64-static" dev="overlay" ino=434591 scontext=system_u:system_r:container_t:s0:c53,c117 tcontext=system_u:object_r:container_file_t:s0:c1022,c1023 tclass=file permissive=0
[10051.131656] audit: type=1701 audit(1749488918.807:3125): auid=10018 uid=1000 gid=1000 ses=1 subj=system_u:system_r:container_t:s0:c53,c117 pid=32544 comm="dumb-init" exe="/usr/bin/qemu-x86_64-static" sig=11 res=1
Même si ça peut sembler cryptique au premier abord, le message dit l’essentiel : tu essayes d’éxécuter ({ read execute }
) un binaire (/usr/bin/qemu-x86_64-static
) qui a l’étiquette system_u:object_r:container_file_t:s0:c1022,c1023
depuis un processus qui a l’étiquette system_u:system_r:container_t:s0:c53,c117
(notez la différence sur la fin de l’étiquette !) et cet accès est refusé (avc: denied
).
Conclusion
Ce sujet a été le caillou dans ma chaussure durant ces dernières semaines, je suis content d’être parvenu à trouver une solution ! Et j’espère que la dite solution vous sera autant utile à vous qu’elle l’a été pour moi.