GNU/Linux >> Tutoriales Linux >  >> Linux

¿Qué sucede detrás de escena de un contenedor Podman sin raíces?

Siempre vale la pena saber lo que sucede detrás de escena. Echemos un vistazo a lo que sucede bajo el capó de los contenedores Podman sin raíces. Explicaremos cada componente y luego desglosaremos todos los pasos involucrados.

El ejemplo

En nuestro ejemplo, intentaremos ejecutar un contenedor que ya ejecuta Buildah para crear una imagen de contenedor. Primero, creamos un Dockerfile simple llamado Containerfile que extrae una imagen de ubi8 y ejecuta un comando que le indica que se está ejecutando en un contenedor:

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

A continuación, ejecute el contenedor con el siguiente comando de Podman:

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

Este comando agrega el dispositivo adicional /dev/fuse , que se requiere para ejecutar Buildah dentro del contenedor. Montamos el volumen en Containerfile para que Buildah pueda encontrarlo y use la bandera de SELinux :Z decirle a Podman que lo vuelva a etiquetar. Para manejar el almacenamiento de contenedores de Buildah fuera del contenedor, también montamos los containers locales directorio que creé arriba. Y finalmente, ejecutamos el comando Buildah.

Aquí está el resultado real que veo cuando ejecuto este 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

Ahora, profundicemos en lo que realmente está sucediendo dentro del comando Podman.

Configuración de los espacios de nombres de usuario y montaje

Al configurar los espacios de nombres de usuarios y montajes, Podman primero verifica si ya hay un espacio de nombres de usuario configurado. Esto se hace viendo si hay un proceso de pausa ejecutándose para el usuario. La función del proceso de pausa es mantener vivo el espacio de nombres de usuario, ya que todos los contenedores sin raíz deben ejecutarse en el mismo espacio de nombres de usuario. Si no lo son, algunas cosas (como compartir el espacio de nombres de la red desde otro contenedor) serían imposibles.

Se requiere un espacio de nombres de usuario para permitir que rootless monte ciertos tipos de sistemas de archivos y acceda a más de un UID y GID.

Si existe el proceso de pausa, entonces se une su espacio de nombres de usuario. Esta acción se realiza muy temprano en su ejecución antes de que comience el tiempo de ejecución de Go porque un programa multiproceso no puede cambiar su espacio de nombres de usuario. Sin embargo, si el proceso de pausa no existe, entonces Podman lee el /etc/subuid y /etc/subgid archivos, buscando el nombre de usuario o UID del usuario que ejecuta el comando Podman. Una vez que Podman encuentra la entrada, utiliza los contenidos y el UID/GID actual del usuario para generar un espacio de nombres de usuario para ellos.

Por ejemplo, si el usuario se ejecuta como UID 1000 y tiene una entrada de USER:100000:65536 , Podman ejecuta las aplicaciones setuid y setgid, /usr/bin/newuidmap y /usr/bin/newgidmap , para configurar el espacio de nombres de usuario. El espacio de nombres de usuario obtiene la siguiente asignación:

0     3267      1
1     100000    65536

Tenga en cuenta que puede ver el espacio de nombres de usuario ejecutando:

$ podman unshare cat /proc/self/uid_map

Luego, Podman crea un proceso de pausa para mantener vivo el espacio de nombres, de modo que todos los contenedores puedan ejecutarse desde el mismo contexto y ver los mismos montajes. El siguiente proceso de Podman se unirá directamente al espacio de nombres sin necesidad de crearlo primero. Sin embargo, si no se pudo crear el espacio de usuario, Podman verifica si el comando aún puede ejecutarse sin un espacio de nombres de usuario. Algunos comandos como podman version no necesito uno En cualquier otro caso, un comando sin espacio de nombres de usuario fallará.

Luego, Podman procesa las opciones de la línea de comandos, verificando que sean correctas. Puedes usar podman-help y podman run --help para obtener una lista de las opciones disponibles y utilice las páginas del manual para obtener más descripciones.

Finalmente, Podman crea un espacio de nombres de montaje para montar el almacenamiento del contenedor.

tirando de la imagen

Al extraer la imagen, Podman verifica si la imagen del contenedor buildah/stable existe en el almacenamiento local de contenedores. Si es así, entonces Podman configura la red (consulte la siguiente sección). Sin embargo, si la imagen del contenedor no existe, Podman crea una lista de imágenes candidatas para extraer utilizando los registros de búsqueda definidos en /etc/containers/registries.conf .

Los containers/image se utilizará para extraer estas imágenes candidatas una a la vez, en un orden definido por registries.conf . Se utilizará la primera imagen que se extraiga con éxito.

  1. Los containers/image script usa DNS para encontrar la dirección IP para el registro.
  2. Este script TCP se conecta a la dirección IP a través de httpd puerto (80).
  3. El container/image envía una solicitud HTTP para el manifiesto de /buildah/stable:latest imagen del contenedor.
  4. Si el script no puede encontrar la imagen, utiliza el siguiente registro como sustituto y vuelve al paso 1. Sin embargo, si la imagen es encontrado, comienza a extraer cada capa de la imagen usando los containers/image biblioteca.

En este ejemplo, buildah/stable se encontró en quay.io/buildah/stable . Los containers/image script encuentra que hay siete capas en quay.io/buildah/stable y comienza a copiarlos todos simultáneamente desde el registro del contenedor al host. Copiarlos simultáneamente es eficiente.

A medida que cada capa se copia en el host, Podman llama a containers/storage biblioteca. Los containers/storage script vuelve a montar las capas en orden y para cada capa. Crea un punto de montaje superpuesto en ~/.local/share/containers/storage encima de la capa anterior. Si no hay capa anterior, crea la capa inicial.

Nota: En Podman sin raíz, en realidad usamos un fuse-overlayfs ejecutable para crear la capa. Rootfull usa el overlayfs del kernel conductor. Actualmente, el kernel no permite que los usuarios sin root monten sistemas de archivos superpuestos, pero pueden montar sistemas de archivos FUSE.

A continuación, containers/storage descompone el contenido de la capa en la nueva capa de almacenamiento. Como las capas no están alquitranadas, containers/storage chowns el UID/GID de los archivos en el tarball en el directorio de inicio. Tenga en cuenta que este proceso puede fallar si el UID o GID especificado en el archivo tar no se asignó al espacio de nombres del usuario. Consulte ¿Por qué Podman desarraigado no puede extraer mi imagen?

Creando el contenedor

Ahora es el momento de que Podman cree un nuevo contenedor basado en la imagen. Para lograr esto, Podman agrega el contenedor a la base de datos y luego le pregunta a containers/storage biblioteca para crear y montar un nuevo contenedor en c/storage . La nueva capa contenedora actúa como la capa final de lectura/escritura y se monta encima de la imagen.

Configurando la red

A continuación, tenemos que configurar la red. Para lograr esto, Podman encuentra y ejecuta /usr/bin/slirp4netns para configurar redes de contenedores. En Podman sin raíz, no podemos crear redes completas y separadas para contenedores, porque esta función no está permitida para usuarios que no sean raíz. En Podman sin raíz, usamos slirp4netns para configurar la red host y simular una VPN para el contenedor.

Nota: En contenedores rooteados, Podman usa los complementos CNI para configurar un puente.

Si el usuario especificó una asignación de puerto como -p 8080:80 , slirpnetns escucharía en la red host en el puerto 8080 y permitiría que el proceso del contenedor se vincule al puerto 80. slirp4netns El comando crea un dispositivo tap que se inyecta dentro del nuevo espacio de nombres de red, donde vive el contenedor. Cada paquete se vuelve a leer desde slirp4netns y emula una pila TCP/IP en el espacio del usuario. Cada conexión fuera del espacio de nombres de la red del contenedor se convierte en una operación de socket que el usuario sin privilegios puede realizar en el espacio de nombres de la red del host.

Manejo de volúmenes

Para manejar volúmenes, Podman lee todo el almacenamiento de contenedores. Reúne las etiquetas SELinux usadas y crea una nueva etiqueta sin usar para ejecutar el contenedor usando opencontainers/selinux biblioteca.

Dado que el usuario especificó dos volúmenes para montar en el contenedor y solicitó a Podman que volviera a etiquetar el contenido, Podman usa opencontainers/selinux para aplicar recursivamente la etiqueta SELinux a los archivos/directorios fuente de los volúmenes. Podman luego usa opencontainers/runtime-tools biblioteca para ensamblar una especificación de tiempo de ejecución de Open Containers Initiative (OCI):

  1. Podman le dice a runtime-tools para agregar sus valores predeterminados codificados para cosas como capacidades, entorno y espacios de nombres a la especificación.
  2. Podman usa la especificación de imagen OCI extraída de buildah/stable imagen para establecer contenido en la especificación, como el directorio de trabajo, el punto de entrada y variables de entorno adicionales.
  3. Podman toma la entrada del usuario y usa las runtime-tools biblioteca para agregar campos en la especificación para cada uno de los volúmenes, y establece el comando para el contenedor en buildah bud / .

En nuestro ejemplo, el usuario le dijo a Podman que quería usar el dispositivo /dev/fuse dentro del contenedor. En un contenedor raíz, Podman le diría al tiempo de ejecución de OCI que cree un /dev/fuse dispositivo dentro del contenedor, pero con Podman sin raíz, los usuarios no pueden crear dispositivos, por lo que Podman le dice a la especificación OCI que vincule el montaje /dev/fuse del host al contenedor.

Iniciando el monitor de contenedor conmon

Una vez que se tratan los volúmenes, Podman encuentra y ejecuta el conmon predeterminado. para el contenedor /usr/bin/conmon . Esta información se lee desde /usr/share/containers/libpod.conf . Podman luego le dice al conmon ejecutable para usar el tiempo de ejecución de OCI que también aparece en libpod.conf; normalmente, /usr/bin/runc o /usr/bin/crun . Podman también le dice a conmon para ejecutar podman container cleanup $CTRID para el contenedor cuando el contenedor sale.

Common hace lo siguiente al monitorear el contenedor:

  1. Common ejecuta el tiempo de ejecución de OCI, le entrega la ruta al archivo de especificaciones de OCI y señala el punto de montaje de la capa de contenedor en containers/storage . Este punto de montaje se llama rootfs.
  2. Common monitorea el contenedor hasta que sale e informa su código de salida.
  3. Controles comunes cuando el usuario se conecta al contenedor, proporcionando un socket para transmitir STDOUT y STDERR del contenedor.
  4. STDOUT y STDERR también se registran en un archivo para podman logs .

Después de ejecutar conmon , pero antes de que comience el tiempo de ejecución de OCI, Podman se conecta al socket "adjuntar" porque el contenedor no se ejecutó con -d . Necesitamos hacer esto antes de ejecutar el contenedor, de lo contrario, corremos el riesgo de perder todo lo que el contenedor escribió en sus flujos estándar antes de adjuntarlo. Hacerlo antes de que comience el contenedor nos da todo.

Lanzamiento del tiempo de ejecución de OCI

El tiempo de ejecución de OCI lee el archivo de especificaciones de OCI y configura el kernel para ejecutar el contenedor. es:

  1. Configura los espacios de nombres adicionales para el contenedor.
  2. Configura cgroups si el contenedor se ejecuta en cgroups V2 (cgroups V1 no admite cgroups sin raíz).
  3. Configura la etiqueta SELinux para ejecutar el contenedor.
  4. Lee el seccomp.json archivo (predeterminado en /usr/share/containers/seccomp.json ) y configura las reglas seccomp.
  5. Establece las variables de entorno.
  6. Bind monta los dos volúmenes especificados en las rutas en rootfs. Si la ruta de destino no existe en rootfs, el tiempo de ejecución de OCI crea el directorio de destino.
  7. Cambia de root a rootfs (hace que rootfs / dentro del contenedor).
  8. Bifurca el proceso del contenedor.
  9. Ejecuta cualquier programa gancho OCI, pasándoles el rootfs así como el PID 1 del contenedor.
  10. Ejecuta el comando especificado por el usuario buildah bud / con el PID 1 del contenedor.
  11. Sale del tiempo de ejecución de OCI, dejando conmon para monitorear el contenedor.

Y finalmente, conmon informa el éxito a Podman.

Ejecutando buildah proceso principal del contenedor

Ahora para el último grupo de pasos. Comienza cuando el contenedor inicia el proceso Buildah inicial. (Porque usamos Buildah en nuestro ejemplo). Buildah comparte los containers/image subyacentes y containers/storage bibliotecas con Podman, por lo que en realidad sigue la mayoría de los pasos definidos anteriormente que Podman usó para extraer sus imágenes y generar sus contenedores.

Podman se adjunta al conmon socket y continúa leyendo/escribiendo STDOUT a conmon . Tenga en cuenta que si el usuario hubiera especificado -d de Podman bandera, Podman saldría, pero el conmon continuaría monitoreando el contenedor.

Cuando el proceso del contenedor finaliza, el kernel envía un SIGCHLD al conmon proceso. A su vez, conmon :

  1. Registra el código de salida del contenedor.
  2. Cierra el archivo de registro del contenedor.
  3. Cierra STDOUT/STDERR del comando Podman.
  4. Ejecuta la limpieza del contenedor podman container cleanup $CTRID comando.

La limpieza del contenedor Podman luego elimina el slirp4netns red y le dice a containers/storage para desmontar todos los puntos de montaje del contenedor. Si el usuario especificó --rm entonces el contenedor se elimina por completo, en su lugar. La capa del contenedor se elimina de containers/storage y la definición del contenedor se elimina de la base de datos.

Dado que el comando Podman original se ejecutaba en primer plano, Podman espera conmon para salir, obtiene el código de salida del contenedor y luego sale con el código de salida del contenedor.

Resumiendo

Con suerte, esta explicación te ayudará a comprender toda la magia eso sucede bajo las sábanas cuando se ejecuta el comando Podman sin raíz.

¿Nuevo en contenedores? Descargue Containers Primer y aprenda los conceptos básicos de los contenedores de Linux.


Linux
  1. ¿Qué es un usuario de Linux?

  2. Detrás de escena con los contenedores de Linux

  3. ¿Por qué Podman desarraigado no puede sacar mi imagen?

  4. Ejecutar Podman sin root como usuario no root

  5. ¿Qué es el usuario debian-+?

Cómo Cirrus CLI usa Podman para lograr compilaciones sin raíz

Useradd vs Adduser:¿Cuál es la diferencia?

Vaya detrás de escena con una instalación de Postman y una guía práctica

¿Cuál es el algoritmo detrás del comando factor en Linux?

¿Cuál es el PID en el host de un proceso que se ejecuta dentro de un contenedor Docker?

¿Qué es el usuario MySQL de debian-sys-maint (y más)?