
Docker-in-Docker ist eine virtualisierte Docker-Umgebung, die in einem Container läuft, um Container-Images zu erstellen. Das Hauptziel von Docker-in-Docker war es, dem Docker-Team zu helfen, Docker schneller zu entwickeln. Viele verwenden es, um Jenkins CI auszuführen. Zunächst scheint das normal zu sein, doch dann treten Probleme auf, die sich vermeiden lassen, indem man Docker im Jenkins CI-Container installiert. In diesem Artikel erfahren Sie, wie das geht. Wenn Sie an der endgültigen Lösung ohne Details interessiert sind, lesen Sie einfach den letzten Abschnitt des Artikels "Problemlösung".

Docker-in-Docker: „Gut“
Vor über zwei Jahren habe ich in Docker –privileged eingefügt und . Das Ziel war es, dem Hauptteam zu helfen, Docker schneller zu entwickeln. Vor der Einführung von Docker-in-Docker war der typische Entwicklungszyklus folgendermaßen:
- Hackity hack;
- Build;
- Stoppen des laufenden Docker-Daemons;
- Starten eines neuen Docker-Daemons;
- Testen;
- Zyklen wiederholen.
Wenn Sie jedoch einen schönen, reproduzierbaren Build (d.h. im Container) erstellen wollten, wurde es komplizierter:
- Hackity hack;
- sicherstellen, dass eine funktionierende Version von Docker läuft;
- neuen Docker mit altem Docker bauen;
- Docker-Daemon anhalten;
- neuen Docker-Daemon starten;
- testen;
- neuen Docker-Daemon anhalten;
- wiederholen.
Mit Docker-in-Docker wurde der Prozess einfacher:
- Hackity hack;
- Bauen + Starten in einem Schritt;
- Zyklen wiederholen.
Ist das nicht viel besser?

Docker-in-Docker: „Schlecht“
Aber entgegen der weit verbreiteten Meinung besteht Docker-in-Docker nicht zu 100 % aus Sternen, Ponys und Einhörnern. Ich meine, dass es einige Probleme gibt, über die der Entwickler informiert sein sollte.
Eines davon betrifft LSM (Linux-Sicherheitsmodule) wie AppArmor und SELinux: Wenn Sie einen Container starten, kann der "interne Docker" versuchen, Sicherheitsprofile anzuwenden, die mit dem "externen Docker" in Konflikt stehen oder ihn verwirren. Dies war das komplizierteste Problem, das gelöst werden musste, als ich versuchte, die ursprüngliche Implementierung des Flags –privileged zu integrieren. Meine Änderungen funktionierten, und alle Tests wären auch auf meiner Debian-Maschine und den Test-VMs von Ubuntu bestanden, aber sie wären auf Michael Crosbys Maschine (soweit ich mich erinnere, hatte er Fedora) abgestürzt und verbrannt. Ich kann mich nicht an den genauen Grund für das Problem erinnern, aber möglicherweise trat es auf, weil Mike ein weiser Mann ist, der mit SELINUX=enforce arbeitet (ich benutzte AppArmor), und meine Änderungen die SELinux-Profile nicht berücksichtigten.
Docker-in-Docker: „Böse“
Das zweite Problem hängt mit den Docker-Speicher-Treibern zusammen. Wenn Sie Docker-in-Docker ausführen, arbeitet der externe Docker über einem gewöhnlichen Dateisystem (EXT4, BTRFS oder einem anderen, das Sie haben), während der interne Docker über einem Copy-on-Write-Dateisystem (AUFS, BTRFS, Device Mapper usw., je nach dem, was der externe Docker zu verwenden konfiguriert ist) arbeitet. Dabei entstehen viele Kombinationen, die nicht funktionieren werden. Zum Beispiel können Sie AUFS nicht über AUFS ausführen.
Wenn Sie BTRFS über BTRFS ausführen, sollte es zunächst funktionieren, aber sobald untergeordnete Volumes entstehen, wird es nicht möglich sein, das übergeordnete Subvolume zu löschen. Das Device Mapper-Modul hat keine Namensräume, weshalb, wenn mehrere Docker-Instanzen auf einer Maschine laufen, alle diese auf die Bilder und die Reservegeräte der Container zugreifen (und Einfluss nehmen) können. Das ist schlecht.
Es gibt Umgehungen für viele dieser Probleme. Wenn Sie beispielsweise AUFS im internen Docker verwenden möchten, machen Sie einfach das Verzeichnis /var/lib/docker zu einem Volumen, und alles wird in Ordnung sein. Docker hat einige grundlegende Namensräume zu den Zielnamen des Device Mappers hinzugefügt, sodass, wenn mehrere Docker-Aufrufe auf einer Maschine ausgeführt werden, sie sich nicht „überlappen“ werden.
Dennoch ist eine solche Konfiguration alles andere als einfach, wie man in diesen im dind-Repository auf GitHub sehen kann.
Docker-in-Docker: Es wird noch schlimmer
Wie sieht es mit dem Build-Cache aus? Das kann ebenfalls ziemlich kompliziert sein. Leute fragen mich oft: „Wenn ich Docker-in-Docker starte, wie kann ich Bilder verwenden, die auf meinem Host gespeichert sind, anstatt alles in meinem internen Docker erneut herunterzuladen?“
Einige findige Leute haben versucht, /var/lib/docker vom Host in den Docker-in-Docker-Container zu binden. Manchmal teilen sie /var/lib/docker mit mehreren Containern.

Willst du Daten beschädigen? Denn genau das wird deine Daten beschädigen!
Der Docker-Daemon wurde offensichtlich so entwickelt, dass er exklusiven Zugriff auf /var/lib/docker hat. Nichts anderes sollte Dateien im Docker-Ordner berühren, anstupsen oder antesten.
Warum ist das so? Weil dies das Ergebnis einer der schwierigsten Lektionen ist, die wir bei der Entwicklung von dotCloud gelernt haben. Die Container-Engine von dotCloud hatte mehrere Prozesse, die gleichzeitig auf /var/lib/dotcloud zugriffen. Tricks wie atomare Dateiersetzungen (anstatt In-Place-Bearbeitungen), das 'Würzen' von Code mit fakultativen und obligatorischen Sperren und andere Experimente mit sicheren Systemen wie SQLite und BDB haben nicht immer funktioniert. Als wir unsere Container-Engine umgestaltet haben, die letztendlich zu Docker wurde, war eine der wichtigsten Designentscheidungen, alle Containeroperationen unter einem einzigen Daemon zusammenzufassen, um all diesen Unsinn des gleichzeitigen Zugriffs zu beenden.
Verstehe mich nicht falsch: Es ist durchaus möglich, etwas Gutes, Zuverlässiges und Schnelles zu machen, das mehrere Prozesse und modernes paralleles Management umfasst. Aber wir sind der Meinung, dass es einfacher und leichter ist, Code zu schreiben und zu warten, wenn man Docker als den einzigen Akteur verwendet.
Das bedeutet, dass du Probleme haben wirst, wenn du das Verzeichnis /var/lib/docker zwischen mehreren Docker-Instanzen teilst. Natürlich kann das funktionieren, besonders in den frühen Phasen des Testens. 'Hör zu, Mama, ich kann Ubuntu mit 'Docker' starten!' Aber versuche, etwas Komplexeres zu machen, wie das Abrufen desselben Images aus zwei verschiedenen Instanzen, und du wirst sehen, dass die Welt brennt.
Das bedeutet, dass wenn Ihr CI-System Builds und Rebuilds durchführt, Sie jedes Mal, wenn Sie den Docker-in-Docker-Container neu starten, das Risiko eingehen, eine Atomrakete in seinen Cache zurückzusetzen. Das ist ganz und gar nicht cool!
Lösung des Problems
Lassen Sie uns einen Schritt zurückgehen. Brauchen Sie wirklich Docker-in-Docker oder möchten Sie einfach die Möglichkeit haben, Docker aus Ihrem CI-System herauszuführen, um Container und Images zu erstellen und zu starten, während sich dieses CI-System in einem Container befindet?
Ich wette, die meisten Menschen benötigen die letzte Option, das heißt, sie möchten, dass ein CI-System wie Jenkins Container starten kann. Und der einfachste Weg, das zu tun, ist, einfach den Docker-Socket in Ihren CI-Container einzufügen, indem Sie ihn mit dem -v-Flag verknüpfen.
Einfacher gesagt, wenn Sie Ihren CI-Container (Jenkins oder einen anderen) starten, anstatt etwas mit Docker-in-Docker zu hacken, starten Sie ihn einfach mit dieser Zeile:
docker run -v /var/run/docker.sock:/var/run/docker.sock ...Jetzt hat dieser Container Zugriff auf den Docker-Socket und kann somit Container starten. Allerdings wird er anstelle von „Kind“-Containern „Geschwister“-Container starten.
Versuchen Sie das mit dem offiziellen Docker-Image (das die Docker-Binärdatei enthält):
docker run -v /var/run/docker.sock:/var/run/docker.sock
-ti dockerDas sieht aus und funktioniert wie Docker-in-Docker, ist jedoch kein Docker-in-Docker: Wenn dieser Container zusätzliche Container erstellt, werden diese im übergeordneten Docker erstellt. Sie werden keine Nebenwirkungen der Verschachtelung erleben, und der Build-Cache wird für mehrere Aufrufe gemeinsam genutzt.
Hinweis: Früher empfahl dieser Artikel, die Docker-Binärdatei vom Host an den Container zu binden. Das ist mittlerweile unzuverlässig geworden, da das Docker-Framework nicht mehr auf statische oder nahezu statische Bibliotheken ausgeweitet wird.
Wenn Sie also Docker aus Jenkins CI verwenden möchten, haben Sie 2 Optionen:
Installation der Docker-CLI mithilfe des ursprünglichen Paket-Management-Systems (d. h. wenn Ihr Image auf Debian basiert, verwenden Sie .deb-Pakete) oder die Nutzung der Docker-API.
Ein wenig Werbung 🙂
Danke, dass Sie bei uns bleiben. Gefallen Ihnen unsere Artikel? Möchten Sie mehr interessante Inhalte sehen? Unterstützen Sie uns, indem Sie eine Bestellung aufgeben oder uns Freunden empfehlen, , ein einzigartiges Äquivalent zu Einsteigerservern, das wir für Sie entwickelt haben: (es sind Optionen mit RAID1 und RAID10 bis zu 24 Kernen und bis zu 40GB DDR4 verfügbar).
Dell R730xd ist im Equinix Tier IV Rechenzentrum in Amsterdam doppelt so günstig? Nur bei uns in den Niederlanden! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — ab $99! Lesen Sie, wie
Quelle: habr.com
