Piense detenidamente antes de usar Docker-in-Docker para CI o entorno de prueba

Piense detenidamente antes de usar Docker-in-Docker para CI o entorno de prueba

Docker-in-Docker es un entorno de demonio Docker virtualizado que se ejecuta dentro del propio contenedor para crear imágenes de contenedor. El objetivo principal de la creación de Docker-in-Docker fue ayudar a desarrollar el propio Docker. Mucha gente lo usa para ejecutar Jenkins CI. Esto parece normal al principio, pero luego surgen problemas que pueden evitarse instalando Docker en un contenedor Jenkins CI. Este artículo le explica cómo hacer esto. Si está interesado en la solución final sin detalles, simplemente lea la última sección del artículo, "Resolver el problema".

Piense detenidamente antes de usar Docker-in-Docker para CI o entorno de prueba

Docker en Docker: "Bueno"

Hace más de dos años puse en Docker bandera –privilegiado y escrito primera versión de dind. El objetivo era ayudar al equipo central a desarrollar Docker más rápido. Antes de Docker-in-Docker, el ciclo de desarrollo típico era el siguiente:

  • truco de piratería;
  • construir;
  • detener un demonio Docker en ejecución;
  • lanzar un nuevo demonio Docker;
  • pruebas;
  • repetir el ciclo.

Si querías hacer un ensamblaje hermoso y reproducible (es decir, en un contenedor), entonces se volvía más complejo:

  • truco de piratería;
  • asegúrese de que se esté ejecutando una versión funcional de Docker;
  • construir un nuevo Docker con el antiguo Docker;
  • detener el demonio Docker;
  • iniciar un nuevo demonio Docker;
  • prueba;
  • detener el nuevo demonio Docker;
  • repetir.

Con la llegada de Docker-in-Docker, el proceso se ha vuelto más sencillo:

  • truco de piratería;
  • montaje + lanzamiento en una etapa;
  • repetir el ciclo.

¿No es mucho mejor así?

Piense detenidamente antes de usar Docker-in-Docker para CI o entorno de prueba

Docker-in-Docker: "Malo"

Sin embargo, contrariamente a la creencia popular, Docker-in-Docker no es 100% estrellas, ponis y unicornios. Lo que quiero decir es que hay varias cuestiones que un desarrollador debe tener en cuenta.

Uno de ellos se refiere a los LSM (módulos de seguridad de Linux) como AppArmor y SELinux: al ejecutar un contenedor, el "Docker interno" puede intentar aplicar perfiles de seguridad que entrarán en conflicto o confundirán al "Docker externo". Este es el problema más difícil de resolver cuando se intenta fusionar la implementación original del indicador –privilegiado. Mis cambios funcionaron y todas las pruebas pasaron en mi máquina Debian y en las máquinas virtuales de prueba de Ubuntu, pero fallaron y se quemaron en la máquina de Michael Crosby (tenía Fedora, según recuerdo). No recuerdo la causa exacta del problema, pero puede haber sido porque Mike es un tipo inteligente que trabaja con SELINUX=enforce (usé AppArmor) y mis cambios no tomaron en cuenta los perfiles de SELinux.

Docker-in-Docker: "Malvado"

El segundo problema tiene que ver con los controladores de almacenamiento de Docker. Cuando ejecuta Docker-in-Docker, el Docker externo se ejecuta sobre un sistema de archivos normal (EXT4, BTRFS o lo que tenga) y el Docker interno se ejecuta sobre un sistema de copia en escritura (AUFS, BTRFS, Device Mapper). , etc.), dependiendo de lo que esté configurado para usar Docker externo). Esto crea muchas combinaciones que no funcionarán. Por ejemplo, no podrá ejecutar AUFS encima de AUFS.

Si ejecuta BTRFS encima de BTRFS, debería funcionar al principio, pero una vez que haya subvolúmenes anidados, la eliminación del subvolumen principal fallará. El módulo Device Mapper no tiene espacio de nombres, por lo que si varias instancias de Docker lo ejecutan en la misma máquina, todas podrán ver (e influir) las imágenes entre sí y en los dispositivos de respaldo del contenedor. Esto es malo.

Existen soluciones para resolver muchos de estos problemas. Por ejemplo, si desea utilizar AUFS en Docker interno, simplemente convierta la carpeta /var/lib/docker en un volumen y estará bien. Docker ha agregado algunos espacios de nombres base a los nombres de destino de Device Mapper para que, si se ejecutan varias llamadas de Docker en la misma máquina, no se pisen entre sí.

Sin embargo, tal configuración no es nada sencilla, como se puede ver en estos artículos en el repositorio dind en GitHub.

Docker-in-Docker: empeora

¿Qué pasa con el caché de compilación? Esto también puede resultar bastante difícil. La gente suele preguntarme "si estoy ejecutando Docker-in-Docker, ¿cómo puedo usar imágenes alojadas en mi host en lugar de volver a colocar todo en mi Docker interno"?

Algunas personas emprendedoras han intentado vincular /var/lib/docker desde el host a un contenedor Docker-in-Docker. A veces comparten /var/lib/docker con varios contenedores.

Piense detenidamente antes de usar Docker-in-Docker para CI o entorno de prueba
¿Quieres corromper tus datos? ¡Porque esto es exactamente lo que dañará tus datos!

El demonio Docker fue claramente diseñado para tener acceso exclusivo a /var/lib/docker. Nada más debería "tocar, pinchar o pinchar" ningún archivo Docker ubicado en esta carpeta.

¿Por qué esto es tan? Porque este es el resultado de una de las lecciones más difíciles aprendidas durante el desarrollo de dotCloud. El motor de contenedor dotCloud se ejecutaba teniendo múltiples procesos accediendo a /var/lib/dotcloud simultáneamente. Trucos astutos como el reemplazo atómico de archivos (en lugar de la edición in situ), agregar bloqueos obligatorios y de advertencia al código y otros experimentos con sistemas seguros como SQLite y BDB no siempre funcionaron. Cuando estábamos rediseñando nuestro motor de contenedores, que eventualmente se convirtió en Docker, una de las principales decisiones de diseño fue consolidar todas las operaciones de contenedores bajo un único demonio para eliminar todas las tonterías de la concurrencia.

No me malinterpreten: es completamente posible hacer algo bueno, confiable y rápido que involucre múltiples procesos y un control paralelo moderno. Pero creemos que es más sencillo y fácil escribir y mantener código utilizando Docker como único reproductor.

Esto significa que si comparte el directorio /var/lib/docker entre varias instancias de Docker, tendrá problemas. Por supuesto, esto puede funcionar, especialmente en las primeras etapas de la prueba. "¡Escucha, mamá, puedo ejecutar ubuntu como ventana acoplable!" Pero prueba algo más complejo, como extraer la misma imagen de dos instancias diferentes, y verás el mundo arder.

Esto significa que si su sistema de CI realiza compilaciones y reconstrucciones, cada vez que reinicia su contenedor Docker-in-Docker, corre el riesgo de colocar una bomba nuclear en su caché. ¡Esto no está nada bien!

La solución

Demos un paso atrás. ¿Realmente necesita Docker-in-Docker o simplemente desea poder ejecutar Docker y crear y ejecutar contenedores e imágenes desde su sistema CI mientras el sistema CI está en un contenedor?

Apuesto a que la mayoría de la gente quiere la última opción, lo que significa que quieren un sistema de CI como Jenkins para poder ejecutar contenedores. Y la forma más sencilla de hacerlo es simplemente insertar un socket Docker en su contenedor de CI y asociarlo con el indicador -v.

En pocas palabras, cuando ejecute su contenedor de CI (Jenkins u otro), en lugar de piratear algo junto con Docker-in-Docker, comience con la línea:

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

Este contenedor ahora tendrá acceso al socket Docker y, por lo tanto, podrá ejecutar contenedores. Excepto que en lugar de ejecutar contenedores "secundarios", lanzará contenedores "hermanos".

Pruebe esto usando la imagen oficial de Docker (que contiene el binario de Docker):

docker run -v /var/run/docker.sock:/var/run/docker.sock 
           -ti docker

Se ve y funciona como Docker-in-Docker, pero no es Docker-in-Docker: cuando este contenedor cree contenedores adicionales, se crearán en el Docker de nivel superior. No experimentará los efectos secundarios del anidamiento y la caché del ensamblado se compartirá entre varias llamadas.

Nota: Las versiones anteriores de este artículo recomendaban vincular el binario de Docker desde el host al contenedor. Esto ahora ya no es confiable porque el motor Docker ya no cubre bibliotecas estáticas o casi estáticas.

Entonces, si desea utilizar Docker de Jenkins CI, tiene 2 opciones:
instalar Docker CLI usando el sistema de empaquetado de imágenes básico (es decir, si su imagen está basada en Debian, use paquetes .deb), usando la API de Docker.

Algunos anuncios 🙂

Gracias por estar con nosotros. ¿Te gustan nuestros artículos? ¿Quieres ver más contenido interesante? Apóyanos haciendo un pedido o recomendándonos a amigos, VPS en la nube para desarrolladores desde $4.99, un análogo único de servidores de nivel de entrada, que fue inventado por nosotros para usted: Toda la verdad sobre VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps desde $19 o como compartir servidor? (disponible con RAID1 y RAID10, hasta 24 núcleos y hasta 40GB DDR4).

Dell R730xd 2 veces más barato en el centro de datos Equinix Tier IV en Amsterdam? Solo aqui 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV desde $199 ¡en los Paises Bajos! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - ¡desde $99! Leer acerca de Cómo construir infraestructura corp. clase con el uso de servidores Dell R730xd E5-2650 v4 por valor de 9000 euros por un centavo?

Fuente: habr.com

Añadir un comentario