GNU/Linux >> Linux Esercitazione >  >> Linux

Cosa succede dietro le quinte di un container Podman senza radici?

Vale sempre la pena sapere cosa sta succedendo dietro le quinte. Diamo un'occhiata a cosa succede sotto il cofano dei contenitori Podman senza radici. Spiegheremo ogni componente e poi analizzeremo tutti i passaggi coinvolti.

L'esempio

Nel nostro esempio, tenteremo di eseguire un contenitore che sta già eseguendo Buildah per creare un'immagine del contenitore. Innanzitutto, creiamo un semplice Dockerfile chiamato Containerfile che estrae un'immagine ubi8 ed esegue un comando che ti dice che stai eseguendo in un contenitore:

$ mkdir containers
$ cat > ~/Containerfile << _EOF
FROM ubi8
RUN echo “in buildah container”
_EOF

Quindi, esegui il contenitore con il seguente comando Podman:

$ podman run --device /dev/fuse -v ~/Containerfile:/Containerfile:Z \
     -v ~/containers:/var/lib/containers:Z buildah buildah bud /

Questo comando aggiunge il dispositivo aggiuntivo /dev/fuse , necessario per eseguire Buildah all'interno del contenitore. Montiamo il volume in Containerfile in modo che Buildah possa trovarlo e utilizzare il flag SELinux :Z per dire a Podman di ribattezzarlo. Per gestire lo stoccaggio del container di Buildah al di fuori del container, montiamo anche i containers locali directory che ho creato sopra. E infine, eseguiamo il comando Buildah.

Ecco l'output effettivo che vedo durante l'esecuzione di questo comando:

$ 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

Ora, scaviamo in profondità ciò che sta effettivamente accadendo all'interno del comando Podman.

Configurazione dell'utente e mount degli spazi dei nomi

Quando si impostano gli spazi dei nomi utente e di montaggio, Podman verifica prima se esiste già uno spazio dei nomi utente configurato. Questo viene fatto vedendo se c'è un processo di pausa in esecuzione per l'utente. Il ruolo del processo di pausa è di mantenere attivo lo spazio dei nomi utente, poiché tutti i contenitori senza root devono essere eseguiti nello stesso spazio dei nomi utente. In caso contrario, alcune cose (come condividere lo spazio dei nomi di rete da un altro container) sarebbero impossibili.

È necessario uno spazio dei nomi utente per consentire senza root di montare determinati tipi di filesystem e accedere a più di un UID e GID.

Se il processo di pausa esiste, il relativo spazio dei nomi utente viene unito. Questa azione viene eseguita molto presto nella sua esecuzione prima dell'avvio del runtime Go perché un programma multithread non può modificare il proprio spazio dei nomi utente. Tuttavia, se il processo di pausa non esiste, quindi Podman legge il /etc/subuid e /etc/subgid file, cercando il nome utente o l'UID dell'utente che esegue il comando Podman. Una volta che Podman trova la voce, utilizza i contenuti e l'UID/GID corrente dell'utente per generare uno spazio dei nomi utente per loro.

Ad esempio, se l'utente è in esecuzione come UID 1000 e ha una voce di USER:100000:65536 , Podman esegue le app setuid e setgid, /usr/bin/newuidmap e /usr/bin/newgidmap , per configurare lo spazio dei nomi utente. Lo spazio dei nomi utente ottiene quindi la seguente mappatura:

0     3267      1
1     100000    65536

Nota che puoi vedere lo spazio dei nomi utente eseguendo:

$ podman unshare cat /proc/self/uid_map

Successivamente, Podman crea un processo di pausa per mantenere vivo lo spazio dei nomi, in modo che tutti i contenitori possano essere eseguiti dallo stesso contesto e vedere gli stessi mount. Il prossimo processo Podman si unirà direttamente allo spazio dei nomi senza che sia necessario crearlo prima. Tuttavia, se non è stato possibile creare lo spazio utente, Podman verifica se il comando può ancora essere eseguito senza uno spazio dei nomi utente. Alcuni comandi come podman version non ne ho bisogno. In ogni altro caso, un comando senza spazio dei nomi utente avrà esito negativo.

Quindi, Podman elabora le opzioni della riga di comando, verificando che siano corrette. Puoi usare podman-help e podman run --help per elencare le opzioni disponibili e utilizzare le pagine man per ulteriori descrizioni.

Infine, Podman crea uno spazio dei nomi di montaggio per montare lo storage del contenitore.

Estrazione dell'immagine

Quando estrae l'immagine, Podman controlla se l'immagine del contenitore buildah/stable esiste nell'archiviazione container locale. In tal caso, Podman configura la rete (vedere la sezione successiva). Tuttavia, se l'immagine del contenitore non esiste, Podman crea un elenco di immagini candidate da estrarre utilizzando i registri di ricerca definiti in /etc/containers/registries.conf .

I containers/image la libreria verrà utilizzata per estrarre queste immagini candidate una alla volta, in un ordine definito da registries.conf . Verrà utilizzata la prima immagine da estrarre correttamente.

  1. I containers/image script utilizza il DNS per trovare l'indirizzo IP per il registro.
  2. Questo script TCP si connette all'indirizzo IP tramite httpd porta (80).
  3. Il container/image invia una richiesta HTTP per il manifest di /buildah/stable:latest immagine contenitore.
  4. Se lo script non riesce a trovare l'immagine, utilizza il registro successivo come sostituto e torna al passaggio 1. Tuttavia, se l'immagine è trovato, inizia a estrarre ogni livello dell'immagine usando i containers/image biblioteca.

In questo esempio, buildah/stable è stato trovato su quay.io/buildah/stable . I containers/image script rileva che ci sono sette livelli in quay.io/buildah/stable e inizia a copiarli tutti contemporaneamente dal registro contenitori all'host. Copiarli contemporaneamente è efficiente.

Poiché ogni livello viene copiato nell'host Podman chiama containers/storage biblioteca. I containers/storage script riassembla i livelli in ordine e per ogni livello. Crea un punto di montaggio in sovrapposizione in ~/.local/share/containers/storage sopra il livello precedente. Se non esiste un livello precedente, crea il livello iniziale.

Nota: In Podman senza root, utilizziamo effettivamente un fuse-overlayfs eseguibile per creare il livello. Rootfull usa overlayfs del kernel autista. Attualmente, il kernel non consente agli utenti rootless di montare filesystem sovrapposti, ma possono montare filesystem FUSE.

Successivamente, containers/storage decomprime il contenuto del livello nel nuovo livello di archiviazione. Poiché i livelli sono privi di tar, containers/storage chown l'UID/GID dei file nel tarball nella home directory. Si noti che questo processo può non riuscire se l'UID o il GID specificato nel file tar non è stato mappato nello spazio dei nomi utente. Vedi Perché Podman senza root non può estrarre la mia immagine?

Creazione del container

Ora è il momento per Podman di creare un nuovo contenitore basato sull'immagine. Per fare ciò, Podman aggiunge il container al database, quindi chiede a containers/storage libreria per creare e montare un nuovo contenitore in c/storage . Il nuovo livello contenitore funge da livello di lettura/scrittura finale ed è montato sopra l'immagine.

Configurazione della rete

Successivamente, dobbiamo configurare la rete. Per fare ciò, Podman trova ed esegue /usr/bin/slirp4netns per configurare una rete di contenitori. In Podman senza root, non è possibile creare una rete completa e separata per i contenitori, poiché questa funzione non è consentita per gli utenti non root. In Podman senza root, utilizziamo slirp4netns per configurare la rete host e simulare una VPN per il container.

Nota: Nei container root, Podman utilizza i plugin CNI per configurare un bridge.

Se l'utente ha specificato una mappatura delle porte come -p 8080:80 , slirpnetns ascolterebbe sulla rete host alla porta 8080 e consentirebbe al processo del contenitore di collegarsi alla porta 80. Il slirp4netns Il comando crea un dispositivo tap che viene iniettato all'interno del nuovo spazio dei nomi di rete, in cui risiede il contenitore. Ogni pacchetto viene riletto da slirp4netns ed emula uno stack TCP/IP nello spazio utente. Ogni connessione al di fuori dello spazio dei nomi della rete container viene convertita in un'operazione socket che l'utente senza privilegi può eseguire nello spazio dei nomi della rete host.

Gestione dei volumi

Per gestire i volumi, Podman legge tutta la memoria del contenitore. Raccoglie le etichette SELinux utilizzate e crea una nuova etichetta non utilizzata per eseguire il contenitore utilizzando opencontainers/selinux biblioteca.

Poiché l'utente ha specificato due volumi da montare nel contenitore e ha chiesto a Podman di rietichettare il contenuto, Podman utilizza opencontainers/selinux per applicare ricorsivamente l'etichetta SELinux ai file/directory di origine dei volumi. Podman utilizza quindi opencontainers/runtime-tools libreria per assemblare una specifica di runtime Open Containers Initiative (OCI):

  1. Podman dice a runtime-tools per aggiungere le sue impostazioni predefinite hardcoded per cose come capacità, ambiente e spazi dei nomi alle specifiche.
  2. Podman utilizza le specifiche dell'immagine OCI estratte da buildah/stable image per impostare il contenuto nelle specifiche, come la directory di lavoro, il punto di ingresso e variabili di ambiente aggiuntive.
  3. Podman prende l'input dell'utente e usa gli runtime-tools library per aggiungere campi nelle specifiche per ciascuno dei volumi e imposta il comando per il contenitore su buildah bud / .

Nel nostro esempio, l'utente ha detto a Podman che voleva utilizzare il dispositivo /dev/fuse all'interno del contenitore. Su un container rootato, Podman direbbe al runtime OCI di creare un /dev/fuse dispositivo all'interno del contenitore, ma con gli utenti Podman senza root non sono autorizzati a creare dispositivi, quindi Podman dice invece alle specifiche OCI di associare mount /dev/fuse dall'host al container.

Avvio del monitor container conmon

Una volta che i volumi sono stati gestiti, Podman trova ed esegue il conmon predefinito per il contenitore /usr/bin/conmon . Queste informazioni vengono lette da /usr/share/containers/libpod.conf . Podman poi dice a conmon eseguibile per utilizzare il runtime OCI elencato anche in libpod.conf; di solito, /usr/bin/runc o /usr/bin/crun . Podman dice anche a conmon per eseguire podman container cleanup $CTRID per il container quando il container esce.

Conmon esegue le seguenti operazioni durante il monitoraggio del contenitore:

  1. Conmon esegue il runtime OCI, consegnandogli il percorso del file delle specifiche OCI e puntando al punto di montaggio del livello del contenitore in containers/storage . Questo punto di montaggio è chiamato rootfs.
  2. Conmon monitora il container finché non esce e restituisce il codice di uscita.
  3. Conmon gestisce quando l'utente si collega al container, fornendo un socket per lo streaming di STDOUT e STDERR del container.
  4. Anche STDOUT e STDERR vengono registrati in un file per i podman logs .

Dopo aver eseguito conmon , ma prima dell'avvio del runtime OCI, Podman si collega al socket "attach" perché il contenitore non è stato eseguito con -d . Dobbiamo farlo prima di eseguire il contenitore, altrimenti rischiamo di perdere tutto ciò che il contenitore ha scritto nei suoi flussi standard prima di collegarlo. Farlo prima che il container si avvii ci dà tutto.

Avvio del runtime OCI

Il runtime OCI legge il file delle specifiche OCI e configura il kernel per eseguire il contenitore. Esso:

  1. Imposta gli spazi dei nomi aggiuntivi per il contenitore.
  2. Configura i cgroup se il contenitore è in esecuzione su cgroups V2 (cgroups V1 non supporta cgroup senza root).
  3. Imposta l'etichetta SELinux per l'esecuzione del contenitore.
  4. Legge il seccomp.json file (predefinito su /usr/share/containers/seccomp.json ) e imposta le regole seccomp.
  5. Imposta le variabili d'ambiente.
  6. Bind monta i due volumi specificati sui percorsi in rootfs. Se il percorso di destinazione non esiste in rootfs, il runtime OCI crea la directory di destinazione.
  7. Passa da root a rootfs (rende il rootfs / all'interno del contenitore).
  8. Fork il processo del contenitore.
  9. Esegue qualsiasi programma hook OCI, passando loro i rootf e il PID 1 del contenitore.
  10. Esegue il comando specificato dall'utente buildah bud / con il PID 1 del contenitore.
  11. Esce dal runtime OCI, lasciando conmon per monitorare il contenitore.

E infine, conmon riporta il successo a Podman.

Esecuzione di buildah processo principale del contenitore

Ora per l'ultimo gruppo di passaggi. Inizia quando il contenitore avvia il processo Buildah iniziale. (Perché nel nostro esempio abbiamo usato Buildah.) Buildah condivide i containers/image sottostanti e containers/storage librerie con Podman, quindi in realtà segue la maggior parte dei passaggi sopra definiti che Podman ha utilizzato per estrarre le sue immagini e generare i suoi contenitori.

Podman si collega al conmon socket e continua a leggere/scrivere STDOUT su conmon . Nota che se l'utente avesse specificato -d di Podman flag, Podman sarebbe uscito, ma il conmon continuerebbe a monitorare il container.

Quando il processo del contenitore termina, il kernel invia un SIGCHLD a conmon processi. A sua volta, conmon :

  1. Registra il codice di uscita del contenitore.
  2. Chiude il file di registro del contenitore.
  3. Chiude STDOUT/STDERR del comando Podman.
  4. Esegue la podman container cleanup $CTRID comando.

La pulizia del contenitore Podman rimuove quindi slirp4netns rete e dice a containers/storage per smontare tutti i punti di montaggio del contenitore. Se l'utente ha specificato --rm quindi il contenitore viene invece completamente rimosso. Il livello del contenitore viene rimosso da containers/storage , e la definizione del contenitore viene rimossa dal DB.

Poiché il comando Podman originale era in esecuzione in primo piano, Podman attende conmon per uscire, ottiene il codice di uscita dal contenitore, quindi esce con il codice di uscita del contenitore.

Conclusione

Si spera che questa spiegazione ti aiuti a capire tutta la magia ciò accade sotto le coperte quando si esegue il comando Podman senza root.

Nuovo per i container? Scarica Containers Primer e impara le basi dei container Linux.


Linux
  1. Che cos'è un utente Linux?

  2. Dietro le quinte con i container Linux

  3. Perché Podman senza root non può estrarre la mia immagine?

  4. Esecuzione di Podman senza root come utente non root

  5. qual è l'algoritmo dietro il comando factor in Linux?

In che modo Cirrus CLI utilizza Podman per ottenere build senza root

Utilizzo di file e dispositivi in ​​contenitori Podman rootless

Useradd vs Adduser:qual è la differenza?

Vai dietro le quinte con un'installazione postino e una guida pratica

Cos'è l'utente debian-+?

Cos'è l'utente MySQL debian-sys-maint (e altro)?