Überlegen Sie sorgfältig, bevor Sie Docker-in-Docker für CI oder Testumgebungen verwenden.



Docker-in-Docker ist ein virtualisierter Docker-Daemon, der im Container selbst ausgeführt wird, um Container-Images zu erstellen. Das Hauptziel bei der Erstellung von Docker-in-Docker war die Unterstützung bei der Entwicklung von Docker selbst. Viele Leute benutzen es, um Jenkins CI zu betreiben. Dies scheint zunächst normal zu sein, aber dann gibt es Probleme, die durch die Installation von Docker im Jenkins CI-Container vermieden werden können. Dieser Artikel beschreibt, wie das geht. Wenn Sie an der endgültigen Lösung ohne Details interessiert sind, lesen Sie einfach den letzten Abschnitt des Artikels "Lösen des Problems".



Docker-in-Docker: Gut


Vor mehr als zwei Jahren habe ich die Flagge –privileged in Docker eingefügt und die erste Version von dind geschrieben . Ziel war es, dem Kernteam zu helfen, Docker schneller zu entwickeln. Vor Docker-in-Docker war ein typischer Entwicklungszyklus wie folgt:

  • Hackity Hack;
  • Versammlung
  • Stoppen eines laufenden Docker-Daemons;
  • Starten Sie einen neuen Docker-Daemon.
  • testen;
  • Schleifenwiederholung.

Wenn Sie eine schöne, reproduzierbare Baugruppe (dh in einem Behälter) erstellen möchten, wird sie komplizierter:

  • Hackity Hack;
  • Stellen Sie sicher, dass eine funktionierende Version von Docker ausgeführt wird.
  • Erstellen Sie einen neuen Docker mit einem alten Docker.
  • Stoppen Sie den Docker-Daemon.
  • Starten Sie einen neuen Docker-Daemon.
  • zu testen;
  • Stoppen Sie den neuen Docker-Daemon.
  • wiederholen.

Mit dem Aufkommen von Docker-in-Docker wurde der Prozess vereinfacht:

  • Hackity Hack;
  • Montage + Start in einem Schritt;
  • Schleifenwiederholung.

Ist das nicht viel besser?



Docker-in-Docker: Schlecht


Entgegen der landläufigen Meinung besteht Docker-in-Docker jedoch nicht zu 100% aus Sternen, Ponys und Einhörnern. Ich meine, es gibt mehrere Probleme, über die ein Entwickler Bescheid wissen muss.

Eines davon betrifft LSM (Linux-Sicherheitsmodule) wie AppArmor und SELinux: Wenn der Container gestartet wird, versucht der „interne Docker“ möglicherweise, Sicherheitsprofile anzuwenden, die den „externen Docker“ in Konflikt bringen oder verwirren. Dies ist das schwierigste Problem, das gelöst werden musste, wenn versucht wurde, die ursprüngliche Implementierung des Flag -privileged zu kombinieren. Meine Änderungen funktionierten und alle Tests wurden auf meiner Debian-Maschine und den virtuellen Ubuntu-Testmaschinen weitergegeben, aber sie stürzten ab und brannten auf Michael Crosbys Maschine (soweit ich mich erinnere, hatte er Fedora). Ich kann mich nicht an die genaue Ursache des Problems erinnern, aber es ist möglicherweise darauf zurückzuführen, dass Mike ein weiser Mensch ist, der mit SELINUX = erzwingen (ich habe AppArmor verwendet) arbeitet und meine Änderungen SELinux-Profile nicht berücksichtigt haben.

Docker-in-Docker: Wütend


Das zweite Problem betrifft Docker-Speichertreiber. Wenn Sie Docker-in-Docker starten, wird der externe Docker über dem regulären Dateisystem (EXT4, BTRFS oder was auch immer Sie haben) und der interne Docker über dem Copy-and-Write-System (AUFS, BTRFS, Device Mapper usw.) ausgeführt. abhängig davon, was für die Verwendung eines externen Dockers konfiguriert ist). In diesem Fall gibt es viele Kombinationen, die nicht funktionieren. Beispielsweise können Sie AUFS nicht über AUFS ausführen.

Wenn Sie BTRFS über BTRFS ausführen, sollte dies zuerst funktionieren. Sobald jedoch die Unterschlüssel angezeigt werden, kann das übergeordnete Subvolume nicht gelöscht werden. Das Device Mapper-Modul verfügt nicht über einen Namespace. Wenn mehrere Docker-Instanzen ihn auf demselben Computer verwenden, können sie alle die Images untereinander und auf den Containersicherungsgeräten sehen (und beeinflussen). Das ist schlecht.

Es gibt Problemumgehungen, um viele dieser Probleme zu lösen. Wenn Sie beispielsweise AUFS im internen Docker verwenden möchten, verwandeln Sie einfach den Ordner / var / lib / docker in einen Ordner, und alles ist in Ordnung. Docker fügte den Zielnamen des Device Mapper einige grundlegende Namespaces hinzu, sodass mehrere Docker-Aufrufe auf demselben Computer nicht gegeneinander "treten".

Dieses Setup ist jedoch alles andere als einfach, wie Sie diesen Artikeln im dind-Repository auf GitHub entnehmen können.

Docker-in-Docker: Schlimmer werden


Was ist mit dem Build-Cache? Dies kann auch sehr schwierig sein. Oft werde ich gefragt: "Wenn ich Docker-in-Docker verwende, wie kann ich die Bilder auf meinem Host verwenden, anstatt alles in meinem internen Docker zurückzuziehen."

Einige unternehmungslustige Leute versuchten, / var / lib / docker vom Host an den Docker-in-Docker-Container zu binden. Manchmal teilen sie / var / lib / docker mit mehreren Containern.


Möchten Sie Daten beschädigen? Denn genau das wird Ihre Daten beschädigen!

Der Docker-Daemon wurde eindeutig für den exklusiven Zugriff auf / var / lib / docker konzipiert. Nichts anderes sollte Docker-Dateien in diesem Ordner „berühren, stupsen oder berühren“.

Warum ist das so? Weil es das Ergebnis einer der schwierigsten Lehren aus der Entwicklung von dotCloud ist. Die dotCloud-Container-Engine arbeitete mit mehreren Prozessen zusammen, die gleichzeitig auf / var / lib / dotcloud zugreifen. Tricky Tricks wie das Ersetzen atomarer Dateien (anstatt sie an Ort und Stelle zu bearbeiten), das "Hocken" des Codes mit Hinweis- und obligatorischen Sperren und andere Experimente mit sicheren Systemen wie SQLite und BDB funktionierten nicht immer. Bei der Neugestaltung unserer Container-Engine, die schließlich zu Docker wurde, bestand eine der wichtigsten Entwurfsentscheidungen darin, alle Container-Vorgänge unter einem einzigen Daemon zu sammeln, 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 tun, das mehrere Prozesse und eine moderne parallele Steuerung umfasst. Wir denken jedoch, dass es einfacher und einfacher ist, Code mit Docker als einzigem Player zu schreiben und zu verwalten.

Dies bedeutet, dass Probleme auftreten, wenn Sie das Verzeichnis / var / lib / docker für mehrere Docker-Instanzen freigeben. Dies kann natürlich funktionieren, insbesondere in den frühen Testphasen. "Hören Sie, Ma, ich kann Ubuntu als Docker ausführen!" Versuchen Sie jedoch, etwas Komplexeres zu tun, indem Sie beispielsweise dasselbe Bild aus zwei verschiedenen Instanzen herausziehen, und Sie werden sehen, wie die Welt brennt.

Dies bedeutet, dass bei jedem Neustart und erneuten Zusammenbau Ihres CI-Systems bei jedem Neustart des Docker-in-Docker-Containers die Gefahr besteht, dass eine Atombombe in den Cache geworfen wird. Das ist überhaupt nicht cool!

Lösung


Machen wir einen Schritt zurück. Benötigen Sie wirklich einen Docker-in-Docker oder möchten Sie nur Docker ausführen können, nämlich Container und Images von Ihrem CI-System erstellen und ausführen, während sich dieses CI-System selbst im Container befindet?

Ich wette, die meisten Leute brauchen die letztere Option, das heißt, sie wollen, dass ein CI-System wie Jenkins Container ausführt. Der einfachste Weg, dies zu tun, besteht darin, den Docker-Socket einfach in Ihren CI-Container einzufügen und ihn mit dem Flag -v zu verknüpfen.

Einfach ausgedrückt, wenn Sie Ihren CI-Container (Jenkins oder einen anderen) starten, anstatt etwas mit Docker-in-Docker zu hacken, starten Sie ihn von der Zeile aus:

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. Anstatt "untergeordnete" Container zu starten, werden "verwandte" Container ausgeführt.

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 docker

Es sieht aus und funktioniert wie ein Docker-in-Docker, ist jedoch kein Docker-in-Docker: Wenn dieser Container zusätzliche Container erstellt, werden diese im Docker der obersten Ebene erstellt. Die Nebenwirkungen der Verschachtelung treten nicht auf, und der Build-Cache wird von mehreren Aufrufen gemeinsam genutzt.

Hinweis: In früheren Versionen dieses Artikels wurde empfohlen, die Docker-Binärdatei vom Host an den Container zu binden. Dies ist jetzt unzuverlässig geworden, da sich der Docker-Mechanismus nicht mehr auf statische oder fast statische Bibliotheken erstreckt.

Wenn Sie also Docker von Jenkins CI verwenden möchten, haben Sie zwei Möglichkeiten:
Installieren der Docker-CLI mithilfe des grundlegenden Image-Paketsystems (dh, wenn Ihr Image auf Debian basiert, verwenden Sie die .deb-Pakete) mithilfe der Docker-API.

Ein bisschen Werbung :)


Vielen Dank für Ihren Aufenthalt bei uns. Gefällt dir unser Artikel? Möchten Sie weitere interessante Materialien sehen? Unterstützen Sie uns, indem Sie eine Bestellung aufgeben oder Ihren Freunden Cloud-basiertes VPS für Entwickler ab 4,99 US-Dollar empfehlen , ein einzigartiges Analogon von Einstiegsservern, das von uns für Sie erfunden wurde: Die ganze Wahrheit über VPS (KVM) E5-2697 v3 (6 Kerne) 10 GB DDR4 480 GB SSD 1 Gbit / s ab 19 $ oder wie teilt man den Server? (Optionen sind mit RAID1 und RAID10, bis zu 24 Kernen und bis zu 40 GB DDR4 verfügbar).

Dell R730xd 2-mal günstiger im Equinix Tier IV-Rechenzentrum in Amsterdam? Nur wir haben 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2,6 GHz 14C 64 GB DDR4 4 x 960 GB SSD 1 Gbit / s 100 TV von 199 US-Dollar in den Niederlanden!Dell R420 - 2x E5-2430 2,2 GHz 6C 128 GB DDR3 2x960 GB SSD 1 Gbit / s 100 TB - ab 99 US-Dollar! Lesen Sie, wie Sie eine Infrastruktur aufbauen Klasse C mit Dell R730xd E5-2650 v4-Servern für 9.000 Euro für einen Cent?

All Articles