Réfléchissez bien avant d'utiliser Docker-in-Docker pour CI ou environnement de test.



Docker-in-Docker est un démon Docker virtualisé exécuté dans le conteneur lui-même pour créer des images de conteneur. L'objectif principal de la création de Docker-in-Docker était d'aider au développement de Docker lui-même. Beaucoup de gens l'utilisent pour exécuter Jenkins CI. Au début, cela semble normal, mais il y a ensuite des problèmes qui peuvent être évités en installant Docker dans le conteneur CI Jenkins. Cet article explique comment procéder. Si vous êtes intéressé par la solution finale sans détails, lisez simplement la dernière section de l'article «Résoudre le problème».



Docker-in-Docker: Bon


Il y a plus de deux ans, j'ai inséré l' indicateur –privileged dans Docker et écrit la première version de dind . L'objectif était d'aider l'équipe principale à développer Docker plus rapidement. Avant Docker-in-Docker, un cycle de développement typique était comme ceci:

  • hack hack;
  • AssemblĂ©e
  • arrĂŞter un dĂ©mon docker en cours d'exĂ©cution;
  • lancer un nouveau dĂ©mon docker;
  • essai;
  • rĂ©pĂ©tition de boucle.

Si vous vouliez faire un bel assemblage reproductible (c'est-Ă -dire dans un conteneur), alors c'est devenu plus complexe:

  • hack hack;
  • assurez-vous qu'une version fonctionnelle de Docker est en cours d'exĂ©cution;
  • construire un nouveau docker avec un ancien docker;
  • arrĂŞtez le dĂ©mon docker;
  • lancer un nouveau dĂ©mon docker;
  • tester;
  • arrĂŞtez le nouveau dĂ©mon Docker;
  • rĂ©pĂ©ter.

Avec l'avènement de Docker-in-Docker, le processus a été simplifié:

  • hack hack;
  • assemblage + lancement en une seule Ă©tape;
  • rĂ©pĂ©tition de boucle.

N'est-ce pas beaucoup mieux?



Docker-in-Docker: mauvais


Cependant, contrairement à la croyance populaire, Docker-in-Docker n'est pas composé à 100% d'étoiles, de poneys et de licornes. Je veux dire, il y a plusieurs problèmes qu'un développeur doit connaître.

L'un d'eux concerne les LSM (modules de sécurité Linux) tels que AppArmor et SELinux: lorsque le conteneur démarre, le «Docker interne» peut essayer d'appliquer des profils de sécurité qui entreront en conflit ou confondront le «Docker externe». Il s'agit du problème le plus difficile à résoudre lors de la tentative de combinaison de l'implémentation d'origine de l'indicateur privilégié. Mes changements ont fonctionné et tous les tests passeraient sur ma machine Debian et les machines virtuelles de test Ubuntu aussi, mais ils plantaient et brûlaient sur la machine de Michael Crosby (pour autant que je m'en souvienne, il avait Fedora). Je ne me souviens pas de la cause exacte du problème, mais cela est probablement arrivé parce que Mike est un sage qui travaille avec SELINUX = enforce (j'ai utilisé AppArmor), et mes modifications n'ont pas pris en compte les profils SELinux.

Docker-in-Docker: En colère


Le deuxième problème est lié aux pilotes de stockage Docker. Lorsque vous lancez Docker-in-Docker, le Docker externe s'exécute au-dessus du système de fichiers standard (EXT4, BTRFS, ou tout ce que vous avez), et le Docker interne s'exécute au-dessus du système de copie et d'écriture (AUFS, BTRFS, Device Mapper, etc.) , en fonction de ce qui est configuré pour utiliser un Docker externe). Dans ce cas, il existe de nombreuses combinaisons qui ne fonctionneront pas. Par exemple, vous ne pouvez pas exécuter AUFS sur AUFS.

Si vous exécutez BTRFS au-dessus de BTRFS, cela devrait fonctionner en premier, mais dès que les sous-clés apparaissent, le sous-volume parent ne peut pas être supprimé. Le module Device Mapper n'a pas d'espace de noms, donc si plusieurs instances Docker l'utilisent sur la même machine, elles peuvent toutes voir (et influencer) les images les unes sur les autres et sur les périphériques de sauvegarde de conteneur. C'est mauvais.

Il existe des solutions pour résoudre bon nombre de ces problèmes. Par exemple, si vous souhaitez utiliser AUFS dans le Docker interne, il suffit de transformer le dossier / var / lib / docker en un et tout ira bien. Docker a ajouté des espaces de noms de base aux noms cibles du Device Mapper, de sorte que si plusieurs appels Docker sont effectués sur la même machine, ils ne se «heurteront» pas les uns aux autres.

Cependant, cette configuration est loin d'être simple, comme vous pouvez le voir dans ces articles dans le référentiel dind sur GitHub.

Docker-in-Docker: empirer


Qu'en est-il du cache de génération? Cela peut également être assez difficile. Les gens me demandent souvent «si j'utilise Docker-in-Docker, comment puis-je utiliser les images situées sur mon hôte, au lieu de tout récupérer dans mon Docker interne»?

Certaines personnes entreprenantes ont tenté de lier / var / lib / docker de l'hôte au conteneur Docker-in-Docker. Parfois, ils partagent / var / lib / docker avec plusieurs conteneurs.


Vous voulez corrompre des données? Parce que c'est exactement ce qui va endommager vos données!

Le démon docker a été clairement conçu pour avoir un accès exclusif à / var / lib / docker. Rien d'autre ne devrait «toucher, piquer ou toucher» les fichiers Docker de ce dossier.

Pourquoi cela est-il ainsi? Parce que c'est le résultat de l'une des leçons les plus difficiles tirées du développement de dotCloud. Le moteur de conteneur dotCloud fonctionnait avec plusieurs processus accédant à / var / lib / dotcloud en même temps. Les astuces délicates, telles que le remplacement de fichiers atomiques (au lieu de les modifier sur place), "percher" le code avec des verrous consultatifs et obligatoires, et d'autres expériences avec des systèmes sécurisés tels que SQLite et BDB, ne fonctionnaient pas toujours. Lorsque nous avons repensé notre moteur de conteneur, qui est finalement devenu Docker, l'une des principales décisions de conception a été de rassembler toutes les opérations de conteneur sous un seul démon pour éliminer toutes ces absurdités d'accès simultané.

Ne vous méprenez pas: il est tout à fait possible de faire quelque chose de bien, de fiable et de rapide, qui comprendra plusieurs processus et un contrôle parallèle moderne. Mais nous pensons qu'il est plus simple et plus facile d'écrire et de maintenir du code en utilisant Docker comme seul lecteur.

Cela signifie que si vous partagez le répertoire / var / lib / docker entre plusieurs instances Docker, vous aurez des problèmes. Bien sûr, cela peut fonctionner, en particulier au début des tests. "Écoute, maman, je peux exécuter Ubuntu en tant que docker!" Mais essayez de faire quelque chose de plus complexe, par exemple, retirez la même image de deux instances différentes, et vous verrez comment le monde brûle.

Cela signifie que si votre système CI effectue des assemblages et des réassemblages, chaque fois que vous redémarrez le conteneur Docker-in-Docker, vous courez le risque de larguer une bombe nucléaire dans son cache. Ce n'est pas cool du tout!

Solution au problème


Prenons un peu de recul. Avez-vous vraiment besoin d'un Docker-in-Docker ou voulez-vous simplement pouvoir exécuter Docker, à savoir créer et exécuter des conteneurs et des images à partir de votre système CI, alors que ce système CI lui-même est dans le conteneur?

Je parie que la plupart des gens ont besoin de cette dernière option, c'est-à-dire qu'ils veulent un système CI comme Jenkins pour exécuter des conteneurs. Et la façon la plus simple de le faire est d'insérer simplement le socket Docker dans votre conteneur CI, en l'associant à l'indicateur -v.

Autrement dit, lorsque vous lancez votre conteneur CI (Jenkins ou autre), au lieu de pirater quelque chose avec Docker-in-Docker, démarrez-le à partir de la ligne:

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

Désormais, ce conteneur aura accès au socket Docker et pourra donc lancer des conteneurs. Sauf qu'au lieu de lancer des conteneurs «enfants», il exécutera des conteneurs «associés».

Essayez ceci en utilisant l'image Docker officielle (qui contient le binaire Docker):

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

Il ressemble et fonctionne comme un Docker-in-Docker, mais ce n'est pas un Docker-in-Docker: lorsque ce conteneur crée des conteneurs supplémentaires, ils seront créés dans le Docker de plus haut niveau. Vous ne rencontrerez pas les effets secondaires de l'imbrication et le cache de génération sera partagé entre plusieurs appels.

Remarque: les versions précédentes de cet article recommandaient de lier le binaire Docker de l'hôte au conteneur. Cela est désormais devenu peu fiable, car le mécanisme Docker ne s'étend plus aux bibliothèques statiques ou presque statiques.

Ainsi, si vous souhaitez utiliser le Docker de Jenkins CI, vous avez 2 options:
installer la Docker CLI en utilisant le système de base de création d'image (c'est-à-dire, si votre image est basée sur Debian, utilisez les packages .deb), en utilisant l'API Docker.

Un peu de publicité :)


Merci de rester avec nous. Aimez-vous nos articles? Vous voulez voir des matériaux plus intéressants? Soutenez-nous en passant une commande ou en recommandant à vos amis, le cloud VPS pour les développeurs à partir de 4,99 $ , un analogue unique de serveurs d'entrée de gamme que nous avons inventé pour vous: Toute la vérité sur VPS (KVM) E5-2697 v3 (6 cœurs) 10 Go DDR4 480 Go SSD 1 Gbit / s à partir de 19 $ ou comment diviser le serveur? (les options sont disponibles avec RAID1 et RAID10, jusqu'à 24 cœurs et jusqu'à 40 Go de DDR4).

Dell R730xd 2 fois moins cher au centre de données Equinix Tier IV à Amsterdam? Nous avons seulement 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV à partir de 199 $ aux Pays-Bas!Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - à partir de 99 $! En savoir plus sur la création d'un bâtiment d'infrastructure. classe c utilisant des serveurs Dell R730xd E5-2650 v4 coûtant 9 000 euros pour un sou?

All Articles