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



Docker-in-Docker es un demonio virtualizado de Docker que se ejecuta en el contenedor para construir imágenes del contenedor. El objetivo principal de crear Docker-in-Docker era ayudar a desarrollar Docker. Mucha gente lo usa para ejecutar Jenkins CI. Al principio esto parece normal, pero luego hay problemas que se pueden evitar instalando Docker en el contenedor Jenkins CI. Este artículo describe cómo hacer esto. Si está interesado en la solución final sin detalles, simplemente lea la última sección del artículo "Solución del problema".



Docker-in-Docker: bueno


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

  • hackity hack;
  • montaje
  • detener un demonio de Docker en ejecución;
  • lanzar un nuevo docker daemon;
  • pruebas;
  • repetición de bucle

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

  • hackity hack;
  • asegúrese de que se esté ejecutando una versión funcional de Docker;
  • construir una nueva ventana acoplable con una ventana antigua;
  • detener el demonio de la ventana acoplable;
  • lanzar un nuevo docker daemon;
  • Probar;
  • detener el nuevo demonio Docker;
  • repetir.

Con la llegada de Docker-in-Docker, el proceso se ha simplificado:

  • hackity hack;
  • montaje + lanzamiento en un solo paso;
  • repetición de bucle

¿No es eso mucho mejor?



Docker-in-Docker: Malo


Sin embargo, contrario a la creencia popular, Docker-in-Docker no está compuesto al 100% por estrellas, ponis y unicornios. Quiero decir, hay varios problemas que un desarrollador necesita saber.

Uno de ellos se refiere a los LSM (módulos de seguridad de Linux) como AppArmor y SELinux: cuando se inicia el contenedor, el "Docker interno" puede intentar aplicar perfiles de seguridad que entren en conflicto o confundan al "Docker externo". Este es el problema más difícil que debía resolverse al intentar combinar la implementación original de la bandera privilegiada. Mis cambios funcionaron, y todas las pruebas también pasarían en mi máquina Debian y en las máquinas virtuales de prueba de Ubuntu, pero se bloquearían y quemarían en la máquina Michael Crosby (hasta donde recuerdo, tenía Fedora). No recuerdo la causa exacta del problema, pero probablemente sucedió porque Mike es una persona sabia que trabaja con SELINUX = enforce (usé AppArmor), y mis cambios no tuvieron en cuenta los perfiles de SELinux.

Docker-in-Docker: enojado


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

Si ejecuta BTRFS encima de BTRFS, esto debería funcionar primero, pero tan pronto como aparezcan las subclaves, el subvolumen principal no se puede eliminar. El módulo Device Mapper no tiene un espacio de nombres, por lo que si varias instancias de Docker lo usan en la misma máquina, todos pueden ver (e influir) las imágenes entre sí y en los dispositivos de copia de seguridad del contenedor. Esto es malo.

Existen soluciones alternativas para resolver muchos de estos problemas. Por ejemplo, si desea usar AUFS en el Docker interno, simplemente convierta la carpeta / var / lib / docker en una y todo estará bien. Docker agregó algunos espacios de nombres básicos a los nombres de destino del Device Mapper, de modo que si se realizan múltiples llamadas de Docker en la misma máquina, no se "pisarán" entre sí.

Sin embargo, esta configuración está lejos de ser simple, como puede ver en estos artículos en el repositorio de dind en GitHub.

Docker-in-Docker: empeorando


¿Qué pasa con el caché de compilación? Esto también puede ser bastante difícil. La gente a menudo me pregunta "si estoy ejecutando Docker-in-Docker, ¿cómo puedo usar las imágenes ubicadas en mi host, en lugar de recuperar todo en mi Docker interno"?

Algunas personas emprendedoras intentaron vincular / var / lib / docker desde el host al contenedor Docker-in-Docker. A veces comparten / var / lib / docker con múltiples contenedores.


¿Quieres corromper los datos? ¡Porque esto es exactamente lo que dañará sus datos!

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

¿Por qué esto es tan? Porque es el resultado de una de las lecciones más difíciles aprendidas del desarrollo de dotCloud. El motor de contenedor dotCloud trabajó con varios procesos accediendo / var / lib / dotcloud al mismo tiempo. Los trucos difíciles, como el reemplazo de archivos atómicos (en lugar de editar en el lugar), "encaramando" el código con bloqueos de aviso y obligatorios, y otros experimentos con sistemas seguros como SQLite y BDB, no siempre funcionaron. Cuando rediseñamos nuestro motor de contenedores, que finalmente se convirtió en Docker, una de las principales decisiones de diseño fue recopilar todas las operaciones de contenedores en un solo demonio para eliminar todas estas tonterías de acceso simultáneo.

No me malinterpreten: es bastante posible hacer algo bueno, confiable y rápido, que incluirá varios procesos y un control paralelo moderno. Pero creemos que es más simple y fácil escribir y mantener código usando Docker como el ú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, Ma, ¡puedo ejecutar ubuntu como un acoplador!" Pero intente hacer algo más complejo, por ejemplo, extraiga la misma imagen de dos instancias diferentes, y verá cómo arde el mundo.

Esto significa que si su sistema de CI realiza ensamblajes y reensamblajes, cada vez que reinicia el contenedor Docker-in-Docker, corre el riesgo de dejar caer una bomba nuclear en su caché. ¡Esto no es genial en absoluto!

Solución al problema


Retrocedamos un paso. ¿Realmente necesita un Docker-in-Docker o simplemente desea poder ejecutar Docker, es decir, construir y ejecutar contenedores e imágenes desde su sistema CI, mientras este sistema CI está en el contenedor?

Apuesto a que la mayoría de las personas necesitan la última opción, es decir, quieren un sistema de CI como Jenkins para ejecutar contenedores. Y la forma más fácil de hacer esto es simplemente insertar el zócalo Docker en su contenedor CI, asociándolo con el indicador -v.

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

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

Ahora este contenedor tendrá acceso al zócalo Docker y, por lo tanto, podrá lanzar contenedores. Excepto que en lugar de lanzar contenedores "secundarios", ejecutará contenedores "relacionados".

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 un Docker-in-Docker, pero no es un Docker-in-Docker: cuando este contenedor crea contenedores adicionales, se crearán en Docker de nivel superior. No experimentará los efectos secundarios de la anidación, y la memoria caché de compilación se compartirá entre varias llamadas.

Nota: las versiones anteriores de este artículo aconsejaban vincular el binario de Docker desde el host al contenedor. Esto ahora no es confiable, ya que el mecanismo Docker ya no se extiende a bibliotecas estáticas o casi estáticas.

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

Un poco de publicidad :)


Gracias por estar con nosotros. ¿Te gustan nuestros artículos? ¿Quieres ver más materiales interesantes? Apóyenos haciendo un pedido o recomendando a sus amigos, VPS en la nube para desarrolladores desde $ 4.99 , un análogo único de servidores de nivel básico que inventamos para usted: toda la verdad sobre VPS (KVM) E5-2697 v3 (6 núcleos) 10GB DDR4 480GB SSD 1Gbps desde $ 19 o cómo dividir el servidor? (las opciones están disponibles 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 tenemos 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV desde $ 199 en los Países Bajos!Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - ¡desde $ 99! Lea sobre Cómo construir un edificio de infraestructura. clase c con servidores Dell R730xd E5-2650 v4 que cuestan 9,000 euros por un centavo?

All Articles