GNU/Linux >> Tutoriels Linux >  >> Linux

Que se passe-t-il dans les coulisses d'un conteneur Podman sans racine ?

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.

  1. Les containers/image le script utilise DNS pour trouver l'adresse IP du registre.
  2. Ce script TCP se connecte à l'adresse IP via le httpd (80).
  3. Le container/image envoie une requête HTTP pour le manifeste de /buildah/stable:latest image du conteneur.
  4. 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) :

  1. 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.
  2. 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.
  3. 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 sur buildah 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 :

  1. 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.
  2. Conmon surveille le conteneur jusqu'à sa sortie et renvoie son code de sortie.
  3. Conmon gère le moment où l'utilisateur se connecte au conteneur, fournissant un socket pour diffuser les STDOUT et STDERR du conteneur.
  4. 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 :

  1. Configure les espaces de noms supplémentaires pour le conteneur.
  2. Configure les cgroups si le conteneur s'exécute sur cgroups V2 (cgroups V1 ne prend pas en charge les cgroups sans racine).
  3. Configure le libellé SELinux pour l'exécution du conteneur.
  4. Lit le seccomp.json fichier (par défaut /usr/share/containers/seccomp.json ) et configure les règles seccomp.
  5. Définit les variables d'environnement.
  6. 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.
  7. Bascule la racine vers le rootfs (rend le rootfs / à l'intérieur du conteneur).
  8. Déplique le processus de conteneur.
  9. Exécute tous les programmes de hook OCI, en leur transmettant le rootfs ainsi que le PID 1 du conteneur.
  10. Exécute la commande spécifiée par l'utilisateur buildah bud / avec le PID 1 du conteneur.
  11. 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 :

  1. Enregistre le code de sortie du conteneur.
  2. Ferme le fichier journal du conteneur.
  3. Ferme STDOUT/STDERR de la commande Podman.
  4. 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.


Linux
  1. Pourquoi Podman sans racine ne peut-il pas extraire mon image ?

  2. Utilisation de fichiers et d'appareils dans des conteneurs sans racine Podman

  3. Comment déboguer les problèmes avec les volumes montés sur des conteneurs sans racine

  4. Contrôler l'accès à Podman sans racine pour les utilisateurs

  5. Que se passe-t-il lorsque j'exécute la commande Cat /proc/cpuinfo ?

Comment Cirrus CLI utilise Podman pour réaliser des versions sans racine

Useradd vs Adduser :quelle est la différence ?

Allez dans les coulisses avec un guide d'installation et d'utilisation Postman

quel est l'algorithme derrière la commande factor sous Linux?

Quel est le PID dans l'hôte d'un processus exécuté dans un conteneur Docker ?

Qu'est-ce que l'utilisateur MySQL debian-sys-maint (et plus) ?