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

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

Docker-in-Docker es un entorno virtualizado del demonio Docker ejecutado dentro del mismo contenedor para construir imágenes de contenedor. El objetivo principal de crear Docker-in-Docker era ayudar en el desarrollo del propio Docker. Muchas personas lo utilizan para ejecutar Jenkins CI. Al principio parece normal, pero luego surgen problemas que se pueden evitar instalando Docker en un contenedor Jenkins CI. Este artículo explica cómo hacerlo. Si le interesa la solución final sin detalles, simplemente lea la última sección del artículo 'Solución al problema'.

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

Docker-in-Docker: 'Bueno'

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

  • hackity hack;
  • construcción (build);
  • detener el demonio Docker en ejecución;
  • iniciar un nuevo demonio Docker;
  • probar;
  • repetir el ciclo.

Sin embargo, si deseaba hacer una construcción bonita y reproducible (es decir, en un contenedor), se volvía más compleja:

  • hackity hack;
  • asegurarse de que haya una versión funcional de Docker en ejecución;
  • construir un nuevo Docker con el antiguo Docker;
  • detener el demonio Docker;
  • iniciar un nuevo demonio Docker;
  • probar;
  • detener el nuevo demonio Docker;
  • repetir.

Con la llegada de Docker-in-Docker, el proceso se simplificó:

  • hackity hack;
  • construcción + ejecución en un solo paso;
  • repetir el ciclo.

¿No es cierto que es mucho mejor así?

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

Docker-in-Docker: 'Malo'

Sin embargo, a pesar de la creencia popular, Docker-in-Docker no está hecho 100% de estrellas, ponis y unicornios. Quiero decir que hay varios problemas de los que el desarrollador debe estar consciente.

Uno de ellos se refiere a LSM (módulos de seguridad de Linux), como AppArmor y SELinux: al iniciar el contenedor, 'Docker interno' puede intentar aplicar perfiles de seguridad que entrarán en conflicto o confundirán a 'Docker externo'. Este es el problema más complicado que se tuvo que resolver al intentar combinar la implementación original de la bandera –privileged. Mis cambios funcionaron y todas las pruebas también pasarían en mi máquina Debian y en máquinas virtuales de pruebas en Ubuntu, pero se caerían y arderían en la máquina de Michael Crosby (hasta donde recuerdo, él tenía Fedora). No puedo recordar la razón exacta del problema, pero puede que surgía porque Mike es una persona sabia que trabaja con SELINUX=enforce (yo usé AppArmor), y mis cambios no tomaron en cuenta los perfiles de SELinux.

Docker-in-Docker: 'El malo'

El segundo problema está relacionado con los controladores de almacenamiento de Docker. Cuando ejecutas Docker-in-Docker, el Docker externo funciona sobre un sistema de archivos normal (EXT4, BTRFS o cualquier otro que tengas), mientras que el Docker interno funciona sobre un sistema de copia en escritura (AUFS, BTRFS, Device Mapper, etc., dependiendo de lo que esté configurado para usar el Docker externo). Esto da lugar a muchas combinaciones que no funcionarán. Por ejemplo, no podrás ejecutar AUFS sobre AUFS.

Si ejecutas BTRFS sobre BTRFS, debería funcionar al principio, pero tan pronto como aparezcan subvolúmenes anidados, no podrás eliminar el subvolumen padre. El módulo Device Mapper no tiene espacios de nombres, por lo que si varias instancias de Docker lo usan en una sola máquina, todas podrán ver (y afectar) las imágenes unas de otras y los dispositivos de respaldo de contenedores. Eso es malo.

Hay soluciones alternativas para muchos de estos problemas. Por ejemplo, si deseas usar AUFS en el Docker interno, simplemente convierte la carpeta /var/lib/docker en ese sistema, y todo funcionará bien. Docker ha añadido algunos espacios de nombres básicos a los nombres de destino de Device Mapper, de modo que si varias llamadas a Docker se realizan en una sola máquina, no se 'pisarán' unas a otras.

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

Docker-in-Docker: se pone aún peor

¿Y qué pasa con el caché de la construcción? Esto también puede resultar bastante complicado. 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 volver a descargarlas todas en mi Docker interno?”

Algunas personas ingeniosas han intentado enlazar /var/lib/docker desde el host en el contenedor Docker-in-Docker. A veces comparten /var/lib/docker entre varios contenedores.

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

El demonio de Docker claramente fue diseñado para tener acceso exclusivo a /var/lib/docker. Nadie más debería "tocar, hurgar o manipular" los archivos de Docker en esta carpeta.

¿Por qué es así? Porque es el resultado de una de las lecciones más duras aprendidas en el desarrollo de dotCloud. El motor de contenedores de dotCloud funcionaba teniendo varios procesos accediendo simultáneamente a /var/lib/dotcloud. Trucos astutos, como el reemplazo atómico de archivos (en lugar de editar en el lugar), bloquear el código con bloqueos recomendados y obligatorios y otros experimentos con sistemas seguros como SQLite y BDB, no siempre funcionaban. Cuando reescribimos nuestro motor de contenedores, que eventualmente se convirtió en Docker, una de las principales decisiones de diseño fue condensar todas las operaciones de contenedores bajo un solo demonio para acabar con toda esta tontería de accesos concurrentes.

No me malinterpretes: es completamente posible hacer algo bueno, confiable y rápido que incluya varios procesos y una gestión paralela moderna. Pero creemos que es más simple y fácil escribir y mantener código utilizando Docker como el único jugador.

Esto significa que si compartes el directorio /var/lib/docker entre varias instancias de Docker, tendrás problemas. Claro, puede funcionar, especialmente en las primeras etapas de prueba. "¡Mira, mamá, puedo correr ubuntu con 'docker'!" Pero intenta hacer algo más complejo, como sacar la misma imagen de dos instancias diferentes, y verás cómo el mundo arde.

Esto significa que si su sistema CI realiza compilaciones y recompilaciones, cada vez que reinicie el contenedor Docker-in-Docker, corre el riesgo de hacer estallar una bomba de tiempo en su caché. ¡No es nada genial!

Solucionar el problema

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

Apuesto a que la mayoría de la gente quiere la última opción, es decir, quieren que un sistema CI como Jenkins pueda ejecutar contenedores. Y la forma más fácil de hacerlo es simplemente conectar el socket de Docker a su contenedor CI, usando la bandera -v.

En otras palabras, cuando inicie su contenedor CI (Jenkins u otro), en lugar de hackear algo con Docker-in-Docker, inícielo con la línea:

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

Ahora este contenedor tendrá acceso al socket de Docker y, por lo tanto, podrá ejecutar contenedores. Excepto que, en lugar de lanzar contenedores 'hijos', 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

Esto se ve y funciona como Docker-in-Docker, pero no es Docker-in-Docker: cuando este contenedor cree contenedores adicionales, se crearán en Docker de nivel superior. No experimentará efectos secundarios de anidamiento, y la caché de compilación será compartida entre múltiples invocaciones.

Nota: versiones anteriores de este artículo recomendaban vincular el binario de Docker desde el host al contenedor. Ahora esto se ha vuelto poco fiable, ya que el mecanismo de Docker ya no se extiende a bibliotecas estáticas o casi estáticas.

Por lo tanto, si desea utilizar Docker desde Jenkins CI, tiene 2 opciones:
instalar Docker CLI utilizando el sistema de empaquetado de imágenes base (es decir, si su imagen se basa en Debian, utilice paquetes .deb), utilizar Docker API.

Un poco de publicidad 🙂

Gracias por permanecer con nosotros. ¿Te gustan nuestros artículos? ¿Quieres ver más contenido interesante? Apóyanos haciendo un pedido o recomendando a tus conocidos, VPS en la nube para desarrolladores desde $4.99, un análogo único de servidores entry-level que hemos diseñado para Ti: Toda la verdad sobre VPS (KVM) E5-2697 v3 (6 núcleos) 10GB DDR4 480GB SSD 1Gbps desde $19, o cómo dividir correctamente un servidor? (disponibles opciones con RAID1 y RAID10, hasta 24 núcleos y hasta 40GB DDR4).

¿Dell R730xd a mitad de precio en el centro de datos Equinix Tier IV en Ámsterdam? Solo aquí 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TB desde $199 ¡en los Países Bajos! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — ¡desde $99! Lee sobre cómo Construir infraestructura de clase empresarial usando servidores Dell R730xd E5-2650 v4 que cuestan 9000 euros a un precio asequible?

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster