
Docker-in-Docker ist eine virtualisierte Docker-Daemon-Umgebung, die innerhalb des Containers selbst läuft, um Container-Images zu erstellen. Das Hauptziel der Entwicklung von Docker-in-Docker war es, dem Docker-Team bei der schnelleren Entwicklung von Docker zu helfen. Viele verwenden es, um Jenkins CI auszuführen. Zunächst scheint es in Ordnung zu sein, aber anschließend treten Probleme auf, die vermieden werden können, wenn Docker im Jenkins CI-Container installiert wird. In diesem Artikel erfahren Sie, wie Sie das erreichen können. Wenn Sie an der endgültigen Lösung ohne Details interessiert sind, lesen Sie einfach den letzten Abschnitt des Artikels „Lösung des Problems“.

Docker-in-Docker: „Gut“
Vor über zwei Jahren habe ich in Docker eingefügt –privileged und die Ziel war es, dem Hauptteam zu helfen, Docker schneller zu entwickeln. Vor der Einführung von Docker-in-Docker sah der typische Entwicklungszyklus folgendermaßen aus:
- hackity hack;
- build;
- den laufenden Docker-Daemon stoppen;
- einen neuen Docker-Daemon starten;
- testen;
- Wiederholung des Zyklus.
Wenn Sie jedoch einen sauberen, reproduzierbaren Build erstellen wollten (also im Container), wurde es komplizierter:
- hackity hack;
- Stellen Sie sicher, dass eine funktionierende Version von Docker läuft;
- einen neuen Docker mit dem alten Docker bauen;
- den Docker-Daemon stoppen;
- einen neuen Docker-Daemon starten;
- testen;
- den neuen Docker-Daemon stoppen;
- wiederholen.
Mit Docker-in-Docker wurde der Prozess vereinfacht:
- hackity hack;
- Bauen + Starten in einem Schritt;
- Wiederholung des Zyklus.
Ist das nicht viel besser?

Docker-in-Docker: „Schlecht“
Allerdings ist Docker-in-Docker, entgegen weit verbreiteter Meinung, nicht zu 100 % voller Sterne, Ponys und Einhörner. Es gibt einige Probleme, über die der Entwickler informiert sein sollte.
Eine der Herausforderungen betrifft LSM (Linux-Sicherheitsmodule) wie AppArmor und SELinux: Beim Starten eines Containers kann der "interne Docker" versuchen, Sicherheitsprofile anzuwenden, die mit dem "äußeren Docker" in Konflikt stehen oder ihn verwirren. Dies war das komplizierteste Problem, das gelöst werden musste, als wir versuchten, die ursprüngliche Implementierung des Flags –privileged zu kombinieren. Meine Änderungen funktionierten, und alle Tests hätten auf meiner Debian-Maschine sowie auf den Test-VMs von Ubuntu ebenfalls bestanden, aber sie wären auf Michael Crosbys Maschine (soweit ich mich erinnere, hatte er Fedora) abgestürzt und gescheitert. Ich kann mich nicht an den genauen Grund des Problems erinnern, aber es ist möglich, dass es daran lag, dass Mike ein weiser Mann ist, der SELINUX=enforce verwendet (ich habe AppArmor benutzt), und meine Änderungen nicht auf die SELinux-Profile Rücksicht nahmen.
Docker-in-Docker: "Böse"
Das zweite Problem betrifft die Docker-Speicher-Treiber. Wenn Sie Docker-in-Docker ausführen, arbeitet der externe Docker über ein herkömmliches Dateisystem (EXT4, BTRFS oder jedes andere, das Sie haben), während der interne Docker über ein Copy-on-Write-Dateisystem (AUFS, BTRFS, Device Mapper usw., je nachdem, was für den externen Docker konfiguriert ist) arbeitet. Dadurch ergeben sich 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 Untervolumes entstehen, können Sie das übergeordnete Subvolume nicht mehr löschen. Das Device Mapper-Modul hat keinen Namensraum, weswegen, wenn mehrere Docker-Instanzen es auf derselben Maschine verwenden, alle diese Instanzen die Images der anderen sehen (und darauf Einfluss nehmen) sowie auf die Backup-Geräte der Container. Das ist problematisch.
Es gibt Umgehungsmöglichkeiten für viele dieser Probleme. Wenn Sie beispielsweise AUFS in einem internen Docker verwenden möchten, wandeln Sie einfach den Ordner /var/lib/docker um, und alles funktioniert einwandfrei. 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.
Allerdings ist eine solche Konfiguration alles andere als einfach, wie man an diesen im dind-Repository auf GitHub sehen kann.
Docker-in-Docker: Es wird noch komplizierter.
Und was ist mit dem Build-Cache? Das kann ebenfalls ziemlich herausfordernd sein. Oft fragen mich Leute: „Wenn ich Docker-in-Docker ausführe, wie kann ich die Bilder auf meinem Host verwenden, anstatt alles erneut in meinem internen Docker herunterzuladen?“
Einige einfallsreiche Personen haben versucht, /var/lib/docker vom Host im Docker-in-Docker-Container zu binden. Manchmal teilen sie /var/lib/docker zwischen mehreren Containern.

Möchten Sie Daten beschädigen? Denn genau das wird Ihre Daten beschädigen!
Der Docker-Daemon wurde offensichtlich entwickelt, um exklusiven Zugriff auf /var/lib/docker zu haben. Nichts anderes sollte „berühren, anstupsen oder anfassen“ irgendwelche Docker-Dateien in diesem Verzeichnis.
Warum ist das so? Weil es das Ergebnis einer der schwierigsten Lektionen ist, die bei der Entwicklung von dotCloud gelernt wurden. Die Container-Engine von dotCloud arbeitete mit mehreren Prozessen, die gleichzeitig auf /var/lib/dotcloud zugriffen. Klug eingeführte Tricks wie die atomare Ersetzung von Dateien (statt Bearbeitung vor Ort), das ‚Pfeffern‘ von Code mit empfehlenden und verbindlichen Sperren sowie andere Experimente mit sicheren Systemen wie SQLite und BDB funktionierten nicht immer zuverlässig. Als wir unsere Container-Engine überarbeiteten, die schließlich zu Docker wurde, war eine der entscheidenden Designentscheidungen, alle Containeroperationen unter einem einzigen Daemon zu bündeln, um all diesen Unsinn des gleichzeitigen Zugriffs zu beseitigen.
Verstehen Sie mich nicht falsch: Es ist durchaus möglich, etwas Gutes, Zuverlässiges und Schnelles zu erstellen, das mehrere Prozesse und modernes paralleles Management umfasst. Doch wir glauben, es ist einfacher und leichter, Code zu schreiben und zu warten, wenn man Docker als einzigen Akteur verwendet.
Das bedeutet, dass Sie, wenn Sie das Verzeichnis /var/lib/docker zwischen mehreren Docker-Instanzen teilen, auf Probleme stoßen werden. Natürlich kann das funktionieren, besonders in den frühen Testphasen. „Hey, Mama, ich kann mit Docker Ubuntu starten!“ Aber versuchen Sie, etwas Komplexeres zu tun, wie das gleiche Image aus zwei verschiedenen Instanzen abzurufen, und Sie werden sehen, wie 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 atomare Bombe in seinem Cache zurückzusetzen. Das ist überhaupt nicht cool!
Problem lösen
Lassen Sie uns einen Schritt zurückgehen. Brauchen Sie wirklich Docker-in-Docker oder möchten Sie einfach nur die Möglichkeit haben, Docker aus Ihrem CI-System heraus auszuführen, insbesondere Container und Images zu erstellen und zu starten, während sich dieses CI-System selbst in einem Container befindet?
Ich wette, dass die meisten Leute die letzte Variante benötigen, also möchten sie, dass ein CI-System wie Jenkins Container starten kann. Der einfachste Weg, dies zu erreichen, besteht darin, den Docker-Socket in Ihren CI-Container einzufügen und ihn mit dem Flag -v zu verknüpfen.
Einfacher gesagt, wenn Sie Ihren CI-Container (Jenkins oder einen anderen) starten, versuchen Sie nicht, etwas mit Docker-in-Docker zu hacken, sondern starten Sie ihn mit der Zeile:
docker run -v /var/run/docker.sock:/var/run/docker.sock ...Jetzt hat dieser Container Zugriff auf den Docker-Socket und kann daher Container starten. Statt „Kind“-Containern wird er jedoch „verwandte“ Container starten.
Versuchen Sie dies mit dem offiziellen Docker-Image (das die Docker-Binärdatei enthält):
docker run -v /var/run/docker.sock:/var/run/docker.sock
-ti dockerEs sieht aus und funktioniert wie Docker-in-Docker, aber es ist 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ühere Versionen dieses Artikels empfehlen, die Docker-Binärdatei vom Host an den Container zu koppeln. Dies ist mittlerweile nicht mehr zuverlässig, da der Docker-Mechanismus nicht mehr auf statische oder nahezu statische Bibliotheken anwendbar ist.
Wenn Sie Docker aus Jenkins CI verwenden möchten, haben Sie 2 Optionen:
Entweder das Docker CLI mit dem Basis-Paketverwaltungssystem zu installieren (d. h. verwenden Sie .deb-Pakete, wenn Ihr Image auf Debian basiert) oder das Docker API zu nutzen.
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 Ihren Freunden empfehlen. , eine einzigartige Alternative zu Einsteiger-Servern, die wir für Sie entwickelt haben: (Verfügbar sind Optionen mit RAID1 und RAID10, bis zu 24 Kerne und bis zu 40GB DDR4).
Dell R730xd im Equinix Tier IV Rechenzentrum in Amsterdam zum halben Preis? Nur bei uns in den Niederlanden! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — ab 99 $! Lesen Sie darüber
Quelle: habr.com
