Análisis de los principios subyacentes de Docker

Autor: vitovzhong, ingeniero de desarrollo de aplicaciones de Tencent TEG

La esencia de un contenedor es un proceso que comparte el mismo núcleo con otros procesos en el host. Sin embargo, a diferencia de los procesos que se ejecutan directamente en el host, el proceso del contenedor se ejecuta en su propio espacio de nombres independiente. El espacio de nombres aísla los recursos entre procesos, de modo que los procesos ayb pueden ver S recursos, pero el proceso c no.

1. Evolución

El deseo de un entorno unificado de desarrollo, pruebas y producción es anterior a la aparición de Docker. Primero entendamos qué soluciones han aparecido antes de Docker.

1.1 vagabundo

Vagarant es la primera solución técnica con la que el autor entró en contacto para resolver la configuración del entorno inconsistente. Está escrito en Ruby y publicado por HashCorp en enero de 2010. La capa inferior de Vagrant es una máquina virtual y la primera opción es virtualbox. Las máquinas virtuales que se han configurado una a una se denominan cajas. Los usuarios pueden instalar libremente bibliotecas dependientes y servicios de software dentro de la máquina virtual y liberar la caja. Con comandos simples, puede abrir la caja y configurar el entorno.

// 拉取一个ubuntu12.04的box
$ vagrant init hashicorp/precise32

// 运行该虚拟机
$ vagrant up

// 查看当前本地都有哪些box
$ vagrant box list


Si necesita ejecutar varios servicios, también puede escribir un archivo vagrant para ejecutar servicios mutuamente dependientes juntos, que es el sabor de docker-compose hoy en día.

config.vm.define("web") do |web|web.vm.box = "apache"
end
config.vm.define("db") do |db|db.vm.box = "mysql”
end

1.2 LXC (contenedor LinuX)

En 2008, Linux 2.6.24 incorporó características de cgroups en la red troncal. Linux Container es un proyecto desarrollado por Canonical basado en tecnologías como namespace y cgroups y dirigido al mundo de los contenedores, el objetivo es crear un entorno de contenedores que se ejecute en un sistema Linux y tenga un buen aislamiento. Por supuesto, se vio por primera vez en el sistema operativo Ubuntu.

En 2013, Docker se lanzó oficialmente en la conferencia PyCon. En ese momento, Docker se desarrolló e implementó en Ubuntu 12.04. Era solo una herramienta basada en LXC, que protegía los detalles de uso de LXC (similar al blindaje vagabundo de la máquina virtual subyacente), lo que permite a los usuarios crear una línea de comandos de ejecución de Docker. Entorno propio de contenedores.

2. Desarrollo tecnológico

La tecnología de contenedores es una tecnología de virtualización a nivel de sistema operativo, que se puede resumir como el uso de cgroup del kernel de Linux, espacio de nombres y otras tecnologías para encapsular y aislar procesos. Mucho antes de Docker, Linux ha proporcionado las tecnologías básicas que utiliza Docker de hoy. Docker se hizo popular en todo el mundo de la noche a la mañana, pero la acumulación de tecnología no es instantánea. Tomamos algunos nodos de tecnología clave para presentarlos.

2.1 Chroot

El software se divide principalmente en software de sistema y software de aplicación, y el programa que se ejecuta en el contenedor no es software de sistema. El proceso en el contenedor se ejecuta esencialmente en la máquina host y comparte el mismo kernel con otros procesos en la máquina host. Y cada software de aplicación necesita un entorno necesario para ejecutarse, incluidas algunas dependencias de bibliotecas lib y similares. Por lo tanto, para evitar conflictos entre las bibliotecas de diferentes aplicaciones, naturalmente nos preguntamos si podemos aislarlas para que puedan ver diferentes bibliotecas. Basado en esta simple idea, en 1979, la llamada al sistema chroot salió por primera vez. Demos un ejemplo para sentirlo. Para el host en la nube solicitado en devcloud, un rootfs del sistema alpino ahora está listo en mi directorio de inicio, de la siguiente manera:

Ejecutar en este directorio:

chroot rootfs/ /bin/bash

Luego imprima / etc / os-release y verá "Alpine Linux", lo que indica que el bash recién ejecutado está aislado de rootfs en el host devcloud.

2.1 Espacio de nombres

En pocas palabras, el espacio de nombres lo proporciona el kernel de Linux, una tecnología utilizada para el aislamiento de recursos entre procesos, de modo que los procesos ayb pueden ver recursos S, mientras que el proceso c no. Fue una característica que se agregó al kernel en Linux 2.4.19 en 2002. Con la introducción del espacio de nombres de usuario en Linux 3.8 en 2013, se implementaron todos los espacios de nombres requeridos por los contenedores que ahora conocemos.

Linux proporciona varios espacios de nombres para aislar varios recursos diferentes. La esencia de un contenedor es un proceso, pero a diferencia de un proceso que se ejecuta directamente en el host, el proceso del contenedor se ejecuta en su propio espacio de nombres independiente. Por lo tanto, el contenedor puede tener su propio sistema de archivos raíz, su propia configuración de red, su propio espacio de proceso e incluso su propio espacio de ID de usuario.

Veamos un ejemplo simple, tengamos una comprensión perceptiva de qué es el espacio de nombres y dónde podemos verlo intuitivamente. En el host en la nube de devcloud, ejecute: ls-l / proc / self / ns Lo que ve es el espacio de nombres admitido por el sistema actual.

Luego usamos el comando unshare para ejecutar un bash de modo que no use el espacio de nombres pid actual:

unshare --pid --fork --mount-proc bash

Luego ejecute:  ps -a para ver qué procesos están bajo el espacio de nombres pid actual:

Ejecute en el nuevo bash: ls -l / proc / self / ns, encuentre que el espacio de nombres pid del bash actual es diferente del anterior.

Dado que la ventana acoplable se implementa en función de la función de espacio de nombres del kernel, simplemente podemos autenticar y ejecutar el comando:

 docker run –pid host --rm -it alpine sh

Ejecute un contenedor alpino simple y deje que comparta el mismo espacio de nombres pid con el host. Luego ejecute el comando ps -a dentro del contenedor y encontrará que el número de procesos es el mismo que en la máquina devcloud; ejecute el comando ls -l / proc / self / ns / y también verá que el espacio de nombres pid dentro del contenedor es el mismo que en la máquina devcloud.

2.2 cgrupos

Cgroups es una especie de espacio de nombres. Es un mecanismo de administración de recursos adoptado para realizar la virtualización. Determina qué recursos asignados a los contenedores podemos administrarlos nosotros y cuántos recursos se asignan a los contenedores. El proceso en el contenedor se ejecuta en un entorno aislado y, cuando se utiliza, es como si estuviera funcionando en un sistema independiente del host. Esta característica hace que las aplicaciones encapsuladas en contenedores sean más seguras que las que se ejecutan directamente en el host. Por ejemplo, puede establecer un límite superior de uso de memoria. Una vez que la memoria utilizada por el grupo de procesos (contenedor) alcanza el límite y luego solicita memoria, iniciará OOM (memoria insuficiente), de modo que no se vea afectada por un consumo excesivo de memoria por parte de un proceso El funcionamiento de otros procesos.

Veamos un ejemplo para sentirlo. Para ejecutar un contenedor apline en la máquina devcloud, solo se pueden usar las primeras 2 CPU y 1.5 núcleos:

docker run --rm -it --cpus "1.5" --cpuset-cpus 0,1 alpine

Luego, abra una nueva terminal para ver qué recursos del sistema podemos controlar:

cat /proc/cgroups

El lado más a la izquierda es el recurso que se puede configurar. Luego, necesitamos encontrar en qué directorio se coloca la información que controla la asignación de recursos:

mount | grep cgroup

Luego encontramos la configuración de cgroups de la imagen alpina que acabamos de ejecutar:

cat /proc/`docker inspect --format='{
    
    {.State.Pid}}' $(docker ps -ql)`/cgroup

De esta manera, al unir los dos, puede ver la configuración de recursos de este contenedor. Primero verifiquemos si el uso de la CPU es de 1.5 núcleos:

cat /sys/fs/cgroup/cpu,cpuacct/docker/c1f68e86241f9babb84a9556dfce84ec01e447bf1b8f918520de06656fa50ab4/cpu.cfs_period_us

Salida 100000, que se puede considerar como una unidad, y luego mire la cuota:

cat /sys/fs/cgroup/cpu,cpuacct/docker/c1f68e86241f9babb84a9556dfce84ec01e447bf1b8f918520de06656fa50ab4/cpu.cfs_quota_us

La salida 150.000 y dividir por la unidad es exactamente 1,5 núcleos establecidos, y luego verificar si se utilizan los dos primeros núcleos:

cat /sys/fs/cgroup/cpuset/docker/c1f68e86241f9babb84a9556dfce84ec01e447bf1b8f918520de06656fa50ab4/cpuset.cpus

Salida 0-1.

En la actualidad, la configuración de recursos del contenedor se asigna de acuerdo con nuestra configuración, pero ¿es realmente posible limitar el uso de 1,5 núcleos en CPU0-CPU1? Echemos un vistazo al uso actual de la CPU:

docker stats $(docker ps -ql)

Debido a que el programa no se ejecuta en alpine, el uso de la CPU es 0, ahora volvemos a la terminal alpine donde se ejecutó por primera vez el comando docker y ejecutamos un bucle sin fin:

i=0; while true; do i=i+i; done

Observemos el uso actual de la CPU:

Cerca de 1, pero ¿por qué no 1,5? Debido a que el bucle infinito que se acaba de ejecutar solo puede ejecutarse en un núcleo, abrimos una terminal nuevamente, ingresamos al espejo alpino y ejecutamos las instrucciones del bucle infinito, y vemos que el uso de la CPU es estable en 1.5, lo que indica que el uso de recursos es realmente Restringido.

Ahora tenemos una cierta comprensión de la tecnología negra de que el contenedor acoplable realiza el aislamiento de recursos entre procesos. Solo en términos de aislamiento, Vagrant ya lo ha hecho. Entonces, ¿por qué Docker es tan popular en el mundo? Esto se debe a que permite a los usuarios empaquetar el entorno del contenedor en una imagen para su distribución, y la imagen se construye de forma jerárquica e incremental, lo que puede reducir en gran medida el umbral de uso de los usuarios.

3. Almacenamiento

La imagen es la unidad básica de implementación de Docker. Contiene archivos de programa y el entorno de los recursos de los que depende el programa. Docker Image se monta dentro del contenedor con un punto de montaje. Un contenedor puede entenderse aproximadamente como una instancia en tiempo de ejecución de un espejo. De forma predeterminada, también se puede considerar como una capa de escritura agregada a la capa de espejo. Por lo tanto, en general, si realiza cambios en el contenedor, todos se incluyen en esta capa de escritura.

3.1 Sistema de archivos unido (UFS)

Union File System significa literalmente "Union File System". Combina varios directorios de archivos con diferentes ubicaciones físicas y los monta en un directorio determinado para formar un sistema de archivos abstracto.

Como se muestra en la figura anterior, desde la perspectiva de UFS a la derecha, lowerdir y upperdir son dos directorios diferentes. UFS combina los dos para obtener la capa fusionada y mostrársela al llamador. Desde la perspectiva de la ventana acoplable de la izquierda, lowerdir es un espejo y upperdir es equivalente a la capa de escritura predeterminada del contenedor. Los archivos modificados en el contenedor en ejecución se pueden guardar como una nueva imagen mediante el comando docker commit.

3.2 Gestión del almacenamiento de imágenes de Docker

Con el concepto de capas de UFS, podemos entender un Dockerfile simple como este:

FROM alpine
COPY foo /foo
COPY bar /bar

El significado de la salida en el momento de la compilación.

Pero, ¿dónde se almacena el archivo de imagen extraído por Docker Pull en la máquina local y cómo se administra? Verifiquémoslo realmente. Confirme el controlador de almacenamiento que utiliza actualmente Docker en devcloud (el valor predeterminado es overlay2):

docker info --format '{
    
    {.Driver}}'

Y la ruta de almacenamiento después de descargar la imagen (almacenada en / var / lib / docker de forma predeterminada):

docker info --format '{
    
    {.DockerRootDir}}'

En la actualidad, mi ventana acoplable ha modificado la ruta de almacenamiento predeterminada y la configuró en / data / docker-data. Tomemos como ejemplo para mostrarlo. Primero mire la estructura de este directorio:

tree -L 1 /data/docker-data

Preste atención a los directorios de imágenes y overlay2. El primero es donde se almacena la información del espejo y el segundo es donde se almacena el contenido del archivo de cada capa. Echemos un vistazo más de cerca a la estructura del directorio de imágenes:

tree -L 2 /data/docker-data/image/

Preste atención a este directorio imagedb y luego tome nuestra última imagen alpina como ejemplo para ver cómo Docker administra la imagen. Instrucciones de ejecución:

docker pull alpine:latest

Luego verifique su ID de imagen: docker image ls alpine: latest

Recuerde este ID a24bb4013296, ahora puede ver los cambios en el directorio imagedb:

tree -L 2 /data/docker-data/image/overlay2/imagedb/content/ | grep
a24bb4013296

Hay un archivo de ID de imagen adicional, que es un archivo de formato json, que contiene la información de parámetros de la imagen:

jq .
/data/docker-data/image/overlay2/imagedb/content/sha256/a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e

A continuación, veamos qué cambios sucederán después de ejecutar un espejo. Ejecute un contenedor alpino y déjelo reposar durante 10 minutos:

docker run --rm -d alpine sleep 600

Luego encuentre su punto de montaje de superposición:

docker inspect --format='{
    
    {.GraphDriver.Data}}' $(docker ps -ql) | grep MergedDir

Combinado con el sistema de archivos UFS mencionado en la sección anterior, puede ls:

ls /data/docker-data/overlay2/74e92699164736980c9e20475388568f482671625a177cb946c4b136e4d94a64/merged

Es el sistema de archivos que se presenta en el contenedor alpine después de la fusión. Primero ingrese al contenedor:

docker exec -it $(docker ps -ql) sh

Inmediatamente después de abrir una nueva terminal para verificar el contenedor en ejecución y compararlo con el espejo, cuáles son los cambios:

docker diff $(docker ps -ql)

En el directorio / root, se ha agregado un archivo de registro histórico de sh. Luego agregamos manualmente un archivo hello.txt en el contenedor:

echo 'Hello Docker' > hello.txt

En este momento, echemos un vistazo a los cambios en el directorio upperDir de la capa de escritura que el contenedor agrega al espejo de forma predeterminada:

ls /data/docker-data/overlay2/74e92699164736980c9e20475388568f482671625a177cb946c4b136e4d94a64/diff

Esto verifica que el controlador overlay2 fusiona el contenido de la imagen y la capa de escritura para que el contenedor lo utilice como sistema de archivos. Varios contenedores en ejecución comparten una imagen básica y cada uno tiene una capa de escritura independiente, lo que ahorra espacio de almacenamiento.

En este momento, también podemos responder dónde se almacena el contenido real de la imagen:

cat /data/docker-data/overlay2/74e92699164736980c9e20475388568f482671625a177cb946c4b136e4d94a64/lower

Ver estas capas:

ls /data/docker-data/overlay2/l/ZIIZFSQUQ4CIKRNCMOXXY4VZHY/

Es el contenido reflejado del UFS de bajo nivel.

para resumir

Esta vez, compartí con ustedes las tecnologías subyacentes utilizadas por Docker, incluido el espacio de nombres, los cgroups y el sistema de archivos conjunto overlay2, y me concentré en cómo se desarrolla e implementa el entorno de aislamiento en el host. A través de la operación manual real, tengo una sensación real de estos conceptos. Espero presentarles el mecanismo de implementación de la red Docker la próxima vez.

Bienvenido a seguir nuestro número de video: programador Tencent

Último vídeo: el patito amarillo del programador

Se ha abierto el intercambio oficial de tecnología Tencent WeChat Group

Únase al grupo y agregue WeChat: journeylife1900

(Observaciones: Tencent Technology)

Supongo que te gusta

Origin blog.csdn.net/Tencent_TEG/article/details/109505143
Recomendado
Clasificación