Browse Source

article: Running qemu-user-static with Podman

master
Nicolas Massé 6 months ago
parent
commit
2e83da7476
  1. 145
      content/english/blog/qemu-user-static-with-podman/index.md
  2. 146
      content/french/blog/qemu-user-static-with-podman/index.md

145
content/english/blog/qemu-user-static-with-podman/index.md

@ -0,0 +1,145 @@
---
title: "Running qemu-user-static with Podman"
date: 2025-06-10T00:00:00+02:00
opensource:
- Podman
- Qemu
topics:
- Containers
---
Recently, I had to run ARM64 containers on a customer's x86 laptop.
Easy, you might say: just install qemu-user-static on the laptop!
And you also have a ready-made container on Docker Hub, just in case!
That's it?
Not so sure...
<!--more-->
## Current situation
While it is easy to install **qemu-user-static** on **Fedora**, this is not the case for **CentOS Stream** or **Red Hat Enterprise Linux**.
You can always retrieve the package from a Fedora repository and install it on these Fedora downstream distributions.
This works, but it remains an orphaned package that will never be updated.
What about the image [docker.io/multiarch/qemu-user-static](https://hub.docker.com/r/multiarch/qemu-user-static) that can be found on Docker Hub?
It hasn't been updated in two years, and the latest version of qemu available is 7.2.
Considering that version 10 of qemu was released recently, this is a bit messy...
## TL;DR
Two one-liners, one for building the image and one for executing it.
Build the container image **localhost/qemu-user-static**:
```sh
sudo podman build -f https://red.ht/qemu-user-static -t localhost/qemu-user-static /tmp
```
Run **qemu-user-static**:
```sh
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
```
That's it!
You can now run a container image that is in a different hardware architecture than your machine.
Example:
```
$ arch
x86_64
$ podman run -it --rm --platform linux/arm64/v8 docker.io/library/alpine
/ # arch
aarch64
```
## Image building
I chose to base my image on the official Fedora images (version 42 at the time of writing this article).
If you have already worked with the **docker.io/multiarch/qemu-user-static** image, you will see that I have modernized it a bit:
- No more scripts from the **qemu** repo, the **systemd-binfmt** component provides everything you need.
- No more options to pass during execution, the script unregisters all binaries registered with the **binfmt** subsystem and registers all its `qemu-*-static` binaries instead.
The result is stripped down:
```dockerfile
FROM quay.io/fedora/fedora:42
RUN dnf install -y qemu-user-static \
&& dnf clean all
ADD container-entrypoint /
ENTRYPOINT ["/container-entrypoint"]
CMD []
```
The **container-entrypoint** script is also reduced to the bare minimum:
```sh
#!/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
```
## Execution
Once the image has been built, it must be executed so that the `qemu-*-static` binaries are registered with the **binfmt** system.
But there is a small subtlety: on a Linux distribution such as Fedora, CentOS Stream, or RHEL, the SELinux system keeps a close eye on things!
And if something tries to break through the containerization engine's security, SELinux detects it and the action is prohibited.
And that's exactly what happens when you naively run the previously built image: the `qemu-*-static` binaries have a SELinux label that prevents them from being executed by the containerization engine when it is about to launch PID 1 of the container to be emulated.
The solution? Launch the container with SELinux options that give the container the correct SELinux labels so that the action is allowed.
This is the role of the `--security-opt` options:
- `label=filetype:container_file_t`: the files in the container image are labeled with the SELinux type **container_file_t**.
- `label=label=level:s0`: the files in the container image are labeled with the SELinux level **s0**.
As you may have recognized, these two options ensure that the files in our image **localhost/qemu-user-static** have the SELinux label for shared volumes.
These files can therefore be shared between containers.
The options `--security-opt label=type:spc_t` and `--privileged` allow the container to run as root, mount the pseudo file system **binfmt_misc**, and register the `qemu-*-static` binaries.
The complete command line is:
```sh
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
```
If you forget these security options, when you try to run a container in a different architecture, podman will terminate without error but without executing anything...
With a bit of luck, you will then see the following error message in your logs:
```
[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
```
Although it may seem cryptic at first glance, the message says it all: you are trying to execute (`{ read execute }`) a binary (`/usr/bin/qemu-x86_64-static`) that has the label `system_u:object_r:container_file_t:s0:c1022,c1023` from a process that has the label `system_u:system_r:container_t:s0:c53,c117` (note the difference at the end of the label!) and this access is denied (`avc: denied`).
## Conclusion
This issue has been a thorn in my side for the past few weeks, and I'm glad I finally found a solution!
I hope you find it as useful as I did.

146
content/french/blog/qemu-user-static-with-podman/index.md

@ -0,0 +1,146 @@
---
title: "Faire fonctionner qemu-user-static avec Podman"
date: 2025-06-10T00:00:00+02:00
opensource:
- Podman
- Qemu
topics:
- Containers
---
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...
<!--more-->
## É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](https://hub.docker.com/r/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** :
```sh
sudo podman build -f https://red.ht/qemu-user-static -t localhost/qemu-user-static /tmp
```
Exécuter **qemu-user-static** :
```sh
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é :
```dockerfile
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 :
```sh
#!/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 :
```sh
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.
Loading…
Cancel
Save