2 changed files with 291 additions and 0 deletions
@ -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. |
||||
@ -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…
Reference in new issue