Il est toujours utile de savoir ce qui se passe dans les coulisses. Jetons un coup d'œil à ce qui se passe sous le capot des conteneurs Podman sans racine. Nous expliquerons chaque composant, puis détaillerons toutes les étapes impliquées.
L'exemple
Dans notre exemple, nous tenterons d'exécuter un conteneur qui exécute déjà Buildah pour créer une image de conteneur. Tout d'abord, nous créons un simple Dockerfile appelé Containerfile
qui extrait une image ubi8 et exécute une commande vous indiquant que vous exécutez dans un conteneur :
$ mkdir containers
$ cat > ~/Containerfile << _EOF
FROM ubi8
RUN echo “in buildah container”
_EOF
Ensuite, exécutez le conteneur avec la commande Podman suivante :
$ podman run --device /dev/fuse -v ~/Containerfile:/Containerfile:Z \
-v ~/containers:/var/lib/containers:Z buildah buildah bud /
Cette commande ajoute le périphérique supplémentaire /dev/fuse
, qui est nécessaire pour exécuter Buildah à l'intérieur du conteneur. Nous montons en volume dans Containerfile
afin que Buildah puisse le trouver et utiliser le drapeau SELinux :Z
pour dire à Podman de le réétiqueter. Pour gérer le stockage de conteneurs de Buildah en dehors du conteneur, nous montons également les containers
locaux répertoire que j'ai créé ci-dessus. Et enfin, nous exécutons la commande Buildah.
Voici la sortie réelle que je vois lors de l'exécution de cette commande :
$ podman run -ti --device /dev/fuse -v ~/Containerfile:/Containerfile:Z -v ~/containers:/var/lib/containers:Z buildah/stable buildah bud /
Trying to pull docker.io/buildah/stable...
denied: requested access to the resource is denied
Trying to pull registry.fedoraproject.org/buildah/stable...
manifest unknown: manifest unknown
Trying to pull quay.io/buildah/stable...
Getting image source signatures
Copying blob 907e338ec93d done
Copying blob a3ed95caeb02 done
Copying blob a3ed95caeCob02 done
Copying blob a3ed95caeb02 skipped: already exists
Copying blob d318c91bf2a8 done
Copying blob e721a8015139 done
Copying blob a3ed95caeb02 done
Copying blob 8dd367492bc7 done
Writing manifest to image destination
Storing signatures
STEP 1: FROM ubi8
Getting image source signatures
Copying blob c65691897a4d done
Copying blob 641d7cc5cbc4 done
Copying config 11f9dba4d1 done
Writing manifest to image destination
Storing signatures
STEP 2: RUN echo "in buildah container"
in buildah container
STEP 3: COMMIT
Getting image source signatures
Copying blob 6866631b657e skipped: already exists
Copying blob 48905dae4010 skipped: already exists
Copying blob 5f70bf18a086 skipped: already exists
Copying config 9c54016647 done
Writing manifest to image destination
Storing signatures
9c5401664748e032b43b8674dba90e9b853d6b47b679d056cb2a1e3118f9dab7
Maintenant, approfondissons ce qui se passe réellement dans la commande Podman.
Configuration des espaces de noms d'utilisateur et de montage
Lors de la configuration des espaces de noms d'utilisateur et de montage, Podman vérifie d'abord s'il existe déjà un espace de noms d'utilisateur configuré. Cela se fait en vérifiant si un processus de pause est en cours d'exécution pour l'utilisateur. Le rôle du processus de pause est de maintenir l'espace de noms d'utilisateur actif, car tous les conteneurs sans racine doivent être exécutés dans le même espace de noms d'utilisateur. S'ils ne le sont pas, certaines choses (comme le partage de l'espace de noms réseau à partir d'un autre conteneur) seraient impossibles.
Un espace de noms d'utilisateur est requis pour permettre au rootless de monter certains types de système de fichiers et d'accéder à plusieurs UID et GID.
Si le processus de pause existe, son espace de noms d'utilisateur est joint. Cette action est effectuée très tôt dans son exécution avant le démarrage de l'environnement d'exécution Go, car un programme multithread ne peut pas modifier son espace de noms d'utilisateur. Cependant, si le processus de pause ne fonctionne pas existent, alors Podman lit le /etc/subuid
et /etc/subgid
fichiers, en recherchant le nom d'utilisateur ou l'UID de l'utilisateur exécutant la commande Podman. Une fois que Podman a trouvé l'entrée, il utilise le contenu ainsi que l'UID/GID actuel de l'utilisateur pour générer un espace de noms d'utilisateur pour lui.
Par exemple, si l'utilisateur s'exécute en tant qu'UID 1000 et a une entrée de USER:100000:65536
, Podman exécute les applications setuid et setgid, /usr/bin/newuidmap
et /usr/bin/newgidmap
, pour configurer l'espace de noms d'utilisateur. L'espace de noms d'utilisateur obtient alors le mappage suivant :
0 3267 1
1 100000 65536
Notez que vous pouvez voir l'espace de noms d'utilisateur en exécutant :
$ podman unshare cat /proc/self/uid_map
Ensuite, Podman crée un processus de pause pour maintenir l'espace de noms en vie, afin que tous les conteneurs puissent s'exécuter à partir du même contexte et voir les mêmes montages. Le prochain processus Podman rejoindra directement l'espace de noms sans avoir besoin de le créer au préalable. Cependant, si l'espace utilisateur n'a pas pu être créé, Podman vérifie si la commande peut toujours s'exécuter sans espace de noms d'utilisateur. Certaines commandes comme podman version
n'en avez pas besoin. Dans tous les autres cas, une commande sans espace de noms d'utilisateur échouera.
Ensuite, Podman traite les options de ligne de commande, en vérifiant qu'elles sont correctes. Vous pouvez utiliser podman-help
et podman run --help
pour répertorier les options disponibles et utilisez les pages de manuel pour de plus amples descriptions.
Enfin, Podman crée un espace de noms de montage pour monter le stockage du conteneur.
Tirer l'image
Lors de l'extraction de l'image, Podman vérifie si l'image du conteneur buildah/stable
existe dans le stockage local de conteneurs. Si c'est le cas, Podman configure le réseau (voir la section suivante). Cependant, si l'image du conteneur n'existe pas, Podman crée une liste d'images candidates à extraire à l'aide des registres de recherche définis dans /etc/containers/registries.conf
.
Les containers/image
sera utilisée pour extraire ces images candidates une par une, dans un ordre défini par registries.conf
. La première image extraite avec succès sera utilisée.
- Les
containers/image
le script utilise DNS pour trouver l'adresse IP du registre. - Ce script TCP se connecte à l'adresse IP via le
httpd
(80). - Le
container/image
envoie une requête HTTP pour le manifeste de
image du conteneur./buildah/stable:latest - Si le script ne trouve pas l'image, il utilise le registre suivant comme substitut et revient à l'étape 1. Cependant, si l'image est trouvé, il commence à extraire chaque couche de l'image à l'aide de
containers/image
bibliothèque.
Dans cet exemple, buildah/stable
a été trouvé sur quay.io/buildah/stable
. Les containers/image
le script trouve qu'il y a sept couches dans quay.io/buildah/stable
et commence à les copier tous simultanément du registre de conteneurs vers l'hôte. Les copier simultanément est efficace.
Au fur et à mesure que chaque couche est copiée sur l'hôte, Podman appelle le containers/storage
bibliothèque. Les containers/storage
script réassemble les calques dans l'ordre, et pour chaque calque. Il crée un point de montage de superposition dans ~/.local/share/containers/storage
au-dessus de la couche précédente. S'il n'y a pas de calque précédent, il crée le calque initial.
Remarque : Dans Podman sans racine, nous utilisons en fait un fuse-overlayfs
exécutable pour créer la couche. Rootfull utilise les overlayfs
du noyau conducteur. Actuellement, le noyau n'autorise pas les utilisateurs sans racine à monter des systèmes de fichiers superposés, mais ils peuvent monter des systèmes de fichiers FUSE.
Ensuite, containers/storage
décompresse le contenu de la couche dans la nouvelle couche de stockage. Comme les couches ne sont pas tarées, containers/storage
chowns les UID/GIDs des fichiers dans l'archive tar dans le répertoire home. Notez que ce processus peut échouer si l'UID ou le GID spécifié dans le fichier tar n'a pas été mappé dans l'espace de noms d'utilisateur. Voir Pourquoi Podman sans racine ne peut-il pas extraire mon image ?
Création du conteneur
Il est maintenant temps pour Podman de créer un nouveau conteneur basé sur l'image. Pour ce faire, Podman ajoute le conteneur à la base de données, puis demande au containers/storage
bibliothèque pour créer et monter un nouveau conteneur dans c/storage
. Le nouveau calque de conteneur agit comme le dernier calque de lecture/écriture et est monté au-dessus de l'image.
Configurer le réseau
Ensuite, nous devons configurer le réseau. Pour ce faire, Podman trouve et exécute /usr/bin/slirp4netns
pour configurer la mise en réseau de conteneurs. Dans Podman sans racine, nous ne pouvons pas créer une mise en réseau complète et séparée pour les conteneurs, car cette fonctionnalité n'est pas autorisée pour les utilisateurs non root. Dans Podman sans racine, nous utilisons slirp4netns
pour configurer le réseau hôte et simuler un VPN pour le conteneur.
Remarque : Dans les conteneurs rootés, Podman utilise les plugins CNI pour configurer un pont.
Si l'utilisateur a spécifié un mappage de port comme -p 8080:80
, slirpnetns
écouterait sur le réseau hôte au port 8080 et permettrait au processus de conteneur de se lier au port 80. Le slirp4netns
La commande crée un périphérique tap qui est injecté dans le nouvel espace de noms réseau, où réside le conteneur. Chaque paquet est relu depuis slirp4netns
et émule une pile TCP/IP dans l'espace utilisateur. Chaque connexion en dehors de l'espace de noms du réseau de conteneurs est convertie en une opération de socket que l'utilisateur non privilégié peut effectuer dans l'espace de noms du réseau hôte.
Volumes de manutention
Afin de gérer les volumes, Podman lit tout le stockage du conteneur. Il rassemble les étiquettes SELinux utilisées et crée une nouvelle étiquette inutilisée pour exécuter le conteneur à l'aide de opencontainers/selinux
bibliothèque.
Étant donné que l'utilisateur a spécifié deux volumes à monter dans le conteneur et a demandé à Podman de réétiqueter le contenu, Podman utilise opencontainers/selinux
pour appliquer de manière récursive l'étiquette SELinux aux fichiers/répertoires sources des volumes. Podman utilise ensuite le opencontainers/runtime-tools
bibliothèque pour assembler une spécification d'exécution Open Containers Initiative (OCI) :
- Podman indique à
runtime-tools
pour ajouter ses valeurs par défaut codées en dur pour des éléments tels que les capacités, l'environnement et les espaces de noms à la spécification. - Podman utilise la spécification d'image OCI extraite de
buildah/stable
image pour définir le contenu dans la spécification, comme le répertoire de travail, le point d'entrée et des variables d'environnement supplémentaires. - Podman prend l'entrée de l'utilisateur et utilise les
runtime-tools
bibliothèque pour ajouter des champs dans la spécification pour chacun des volumes, et il définit la commande pour le conteneur surbuildah bud /
.
Dans notre exemple, l'utilisateur a dit à Podman qu'il voulait utiliser l'appareil /dev/fuse
à l'intérieur du conteneur. Sur un conteneur rooté, Podman dirait au runtime OCI de créer un /dev/fuse
périphérique à l'intérieur du conteneur, mais avec les utilisateurs de Podman sans racine ne sont pas autorisés à créer des périphériques, donc Podman indique à la place à la spécification OCI de lier le montage /dev/fuse
de l'hôte dans le conteneur.
Démarrage du moniteur de conteneur conmon
Une fois les volumes traités, Podman trouve et exécute le conmon
par défaut pour le conteneur /usr/bin/conmon
. Ces informations sont lues depuis /usr/share/containers/libpod.conf
. Podman indique alors le conmon
exécutable pour utiliser le runtime OCI également répertorié dans libpod.conf
; généralement, /usr/bin/runc
ou /usr/bin/crun
. Podman indique également conmon
pour exécuter le podman container cleanup $CTRID
pour le conteneur lorsque le conteneur sort.
Conmon effectue les opérations suivantes lors de la surveillance du conteneur :
- Conmon exécute le runtime OCI, lui transmet le chemin d'accès au fichier de spécification OCI et pointe vers le point de montage de la couche conteneur dans
containers/storage
. Ce point de montage est appelé rootfs. - Conmon surveille le conteneur jusqu'à sa sortie et renvoie son code de sortie.
- Conmon gère le moment où l'utilisateur se connecte au conteneur, fournissant un socket pour diffuser les STDOUT et STDERR du conteneur.
- STDOUT et STDERR sont également enregistrés dans un fichier pour les
podman logs
.
Après avoir exécuté conmon
, mais avant le démarrage de l'exécution OCI, Podman se connecte au socket "attach" car le conteneur n'a pas été exécuté avec -d
. Nous devons le faire avant d'exécuter le conteneur, sinon nous risquons de perdre tout ce que le conteneur a écrit dans ses flux standard avant de nous attacher. Le faire avant que le conteneur ne commence nous donne tout.
Lancement de l'environnement d'exécution OCI
Le runtime OCI lit le fichier de spécification OCI et configure le noyau pour exécuter le conteneur. Il :
- Configure les espaces de noms supplémentaires pour le conteneur.
- Configure les cgroups si le conteneur s'exécute sur cgroups V2 (cgroups V1 ne prend pas en charge les cgroups sans racine).
- Configure le libellé SELinux pour l'exécution du conteneur.
- Lit le
seccomp.json
fichier (par défaut/usr/share/containers/seccomp.json
) et configure les règles seccomp. - Définit les variables d'environnement.
- Bind monte les deux volumes spécifiés sur les chemins dans le rootfs. Si le chemin de destination n'existe pas dans le rootfs, le runtime OCI crée le répertoire de destination.
- Bascule la racine vers le rootfs (rend le rootfs
/
à l'intérieur du conteneur). - Déplique le processus de conteneur.
- Exécute tous les programmes de hook OCI, en leur transmettant le rootfs ainsi que le PID 1 du conteneur.
- Exécute la commande spécifiée par l'utilisateur
buildah bud /
avec le PID 1 du conteneur. - Quitter le runtime OCI, en laissant
conmon
pour surveiller le conteneur.
Et enfin, conmon
rapporte le succès à Podman.
Exécuter le buildah
processus principal du conteneur
Passons maintenant au dernier groupe d'étapes. Cela commence lorsque le conteneur lance le processus Buildah initial. (Parce que nous avons utilisé Buildah dans notre exemple.) Buildah partage les containers/image
sous-jacents et containers/storage
bibliothèques avec Podman, il suit donc en fait la plupart des étapes définies ci-dessus que Podman a utilisées pour extraire ses images et générer ses conteneurs.
Podman s'attache au conmon
socket et continue à lire/écrire STDOUT vers conmon
. Notez que si l'utilisateur avait spécifié le -d
de Podman flag, Podman quitterait, mais le conmon
continuerait à surveiller le conteneur.
Lorsque le processus de conteneur se termine, le noyau envoie un SIGCHLD au conmon
processus. À son tour, conmon
:
- Enregistre le code de sortie du conteneur.
- Ferme le fichier journal du conteneur.
- Ferme STDOUT/STDERR de la commande Podman.
- Exécute le
podman container cleanup $CTRID
commande.
Le nettoyage du conteneur Podman supprime ensuite le slirp4netns
réseau et indique containers/storage
pour démonter tous les points de montage du conteneur. Si l'utilisateur a spécifié --rm
alors le conteneur est entièrement supprimé à la place. La couche conteneur est supprimée de containers/storage
, et la définition de conteneur est supprimée de la base de données.
Étant donné que la commande Podman d'origine s'exécutait au premier plan, Podman attend conmon
pour quitter, obtient le code de sortie du conteneur, puis quitte avec le code de sortie du conteneur.
Conclusion
J'espère que cette explication vous aidera à comprendre toute la magie cela se produit sous les couvertures lors de l'exécution de la commande Podman sans racine.
Nouveauté dans les conteneurs ? Téléchargez le Containers Primer et apprenez les bases des conteneurs Linux.