Mit der Early-Access-Version von Docker Scout beginnt Docker Hub endlich mit der Visualisierung von Image-Interna. Das ist toll! Warum hat Docker Hub das nicht schon vor Jahren gemacht? Aufgrund seines Geschäftsmodells.
Bevor wir fortfahren, werfen wir einen kurzen Blick darauf, woraus Docker-Images bestehen.
Wenn Sie ein Docker-Image herunterladen, indem Sie docker pull
in der Befehlszeile ausführen, zeigt die Docker-CLI den Download-Fortschritt jeder Ebene an, während das Image abgerufen wird. Wenn Sie jemals ein Docker-Image heruntergeladen haben, haben Sie es wahrscheinlich gesehen:
Wenn Sie bereits mit Docker gearbeitet haben, haben Sie wahrscheinlich einige dieser Ebenen für Ihre eigenen Bilder definiert. Entwickler definieren diese Ebenen implizit, wenn sie Docker-Dateien schreiben. Beispielsweise erzeugt jede Zeile in dieser Docker-Datei eine Ebene:
FROM ubuntu:22.04 # install apt dependencies RUN apt-get update RUN apt-get install -y iputils-ping python3 python3-pip # install python dependencies RUN pip3 install numpy # cleanup apt lists RUN rm -rf /var/lib/apt/lists/* CMD ["/bin/bash"]
Ebenen sind archivierte Linux-Verzeichnisse – es handelt sich um Dateisystem-Tarballs. Docker lädt alle Ihre Bildebenen herunter und entpackt sie jeweils in ein separates Verzeichnis. Wenn Sie mit dem Befehl docker run
einen Container aus einem Docker-Image starten, kombiniert der Docker-Daemon die Image-Ebenen zu einem Container.
Ein Großteil des Mehrwerts von Docker als Produkt besteht darin, dass es diese Details abstrahiert, sodass Benutzer die Vorteile geschichteter Container nutzen können, ohne darüber nachdenken zu müssen, wie sie funktionieren. Aber alle Abstraktionen lecken, und Docker ist da keine Ausnahme – manchmal muss man den Vorhang aufziehen.
Docker-Image-Layer enthalten die Ursprungsgeschichte für jede im Dateisystem des Containers vorhandene Binärdatei. Die erste Zeile in einem Docker-Image ist „die FROM-Zeile“. Es definiert das Docker-Image (und damit die Image-Ebenen), auf dem die Docker-Datei aufbaut.
Durch die Untersuchung der Ebenen der aktuellen Docker-Datei und der Ebenen der übergeordneten Image-Kette können Entwickler feststellen, woher jede Datei im Root-Dateisystem eines Containers stammt. Das sind sehr wertvolle Informationen. Es hilft Entwicklern:
Stellen Sie sich vor, Sie klicken durch die Ebenen in einer Visualisierung, um Dateiänderungen über Docker-Image-Versionen hinweg zu verfolgen. Wenn ein automatisierter Sicherheitsscan eine Schwachstelle in einem Ihrer Bilder identifiziert, stellen Sie sich vor, Sie verwenden ein Layer-Inspektion-Tool, um herauszufinden, wie die Schwachstelle entstanden ist.
Zu große Bildgrößen können Unternehmen viel Geld kosten. Viele CI/CD-Pipelines rufen Docker-Images für jede Pull-Anfrage ab, sodass langwierige Docker-Image-Downloads die Pipelines verlangsamen können, wodurch Entwickler weniger effizient werden und CPU-Zeit verschwendet wird. Da Unternehmen die Infrastrukturkosten in der Regel auf Stundenbasis bezahlen, ist jede verschwendete Stunde eine unnötige Ausgabe.
Über die Verschwendung von Rechenressourcen hinaus können aufgeblähte Docker-Images zu übermäßigen Netzwerkübertragungskosten führen. Wenn eine Organisation Docker-Images über AWS-Regionen hinweg oder von AWS ins offene Internet herunterlädt, führt das Aufblähen von Docker-Images direkt zu verschwenderischen Infrastrukturausgaben. Das summiert sich schnell.
Es kann leicht passieren, dass Ihre Docker-Image-Ebenen versehentlich aufgebläht werden. Die zuvor dargestellte Docker-Datei enthält ein klassisches Beispiel: Die Ebene aus Zeile 4 speichert 40 MB Dateien auf der Festplatte, die später von der Ebene aus Zeile 11 gelöscht werden. Aufgrund der Funktionsweise von Docker-Images sind diese Daten immer noch Teil des Images 40 MB unnötige Bildgröße.
Dies ist ein einfaches Beispiel – tatsächlich stammt es direkt aus der Docker- Dokumentation . In komplexeren Docker-Dateien kann dieser Fehler viel schwieriger zu erkennen sein.
Es kann schwierig sein, mit Docker-Image-Ebenen über die Befehlszeile zu interagieren, aber bevor Docker Scout kürzlich veröffentlicht wurde, war die Befehlszeile der Ort, an dem Sie den neuesten Stand der Technik finden konnten. Hier sind die beiden grundlegenden Ansätze.
Dies ist die schnörkellose Do-it-yourself-Unix-Methode. Sie benötigen lediglich einen Host, auf dem ein Docker-Daemon ausgeführt wird. Es ist ein einfacher Ansatz:
docker create
, um einen Container aus dem Image zu starten.docker inspect
um die Ebenenverzeichnisse des neuen Containers zu finden.cd
in diese Verzeichnisse in der Befehlszeile und denken Sie über die Ebenen in Ihrem Kopf nach.
Das ist mühsam. Nehmen wir an, wir versuchen, ein aufgeblähtes Bild in einem Docker-Image aufzuspüren, das wir seit einigen Monaten verwenden, dessen Größe jedoch in letzter Zeit erheblich zugenommen hat.
Zuerst erstellen wir einen Container aus dem Bild:
where-the@roadmap-ends ~ $ docker create --name example where-the-roadmap-ends 339b8905b681a1d4f7c56f564f6b1f5e63bb6602b62ba5a15d368ed785f44f55
Dann teilt uns docker inspect
mit, wo die Ebenenverzeichnisse des heruntergeladenen Bildes in unserem Dateisystem gelandet sind:
where-the@roadmap-ends ~ $ docker inspect example | grep GraphDriver -A7 "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47-init/diff:/var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff:/var/lib/docker/overlay2/j0dekt7y8xgix11n0lturmf8t/diff:/var/lib/docker/overlay2/zd57mz6l4zrsjk9snc2crphfq/diff:/var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff:/var/lib/docker/overlay2/8c639b22627e0ad91944a70822b442e5bff848968263a37715a293a15483c170/diff", "MergedDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47/merged", "UpperDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47/diff", "WorkDir": "/var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47/work" }, "Name": "overlay2"
Die Ebenen, die wir für diese Untersuchung untersuchen möchten, sind die Liste der „LowerDir“-Verzeichnisse. Die anderen Verzeichnisse sind nicht Teil des Docker-Images selbst – wir können sie ignorieren.
Also analysieren wir die Liste der „LowerDir“-Verzeichnisse in der Befehlszeile:
where-the@roadmap-ends ~ $ docker inspect example | grep GraphDriver -A7 | grep LowerDir | awk '{print $2}' | sed 's|"||g' | sed 's|,||g' | sed 's|:|\n|g' /var/lib/docker/overlay2/1c18bd289d9c3f9f0850e301bf86955395c312de3a64a70e0d0e6a5bed337d47-init/diff /var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff /var/lib/docker/overlay2/j0dekt7y8xgix11n0lturmf8t/diff /var/lib/docker/overlay2/zd57mz6l4zrsjk9snc2crphfq/diff /var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff /var/lib/docker/overlay2/8c639b22627e0ad91944a70822b442e5bff848968263a37715a293a15483c170/diff
Dies ist die Liste der Ebenen im Bild, der Reihe nach, wobei die unterste Ebene zuerst steht. Jetzt müssen wir diese Ebenenverzeichnisse manuell mit den Dockerfile-Zeilen korrelieren, die sie erstellt haben.
Leider bietet uns Docker keine Möglichkeit, diese Zeilen direkt aus einem Docker-Image zu extrahieren – dies ist das Beste, was wir mithilfe docker history
erreichen können:
where-the@roadmap-ends ~ $ docker history where-the-roadmap-ends IMAGE CREATED CREATED BY SIZE COMMENT 6bbac081b2a7 2 hours ago CMD ["/bin/bash"] 0B buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c rm -rf /var/lib/apt/lists/* #… 0B buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c pip3 install numpy # buildkit 70MB buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c apt-get install -y iputils-pi… 343MB buildkit.dockerfile.v0 <missing> 2 hours ago RUN /bin/sh -c apt-get update # buildkit 40.1MB buildkit.dockerfile.v0 <missing> 8 months ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 8 months ago /bin/sh -c #(nop) ADD file:550e7da19f5f7cef5… 69.2MB
Anhand dieser Ausgabe können wir identifizieren, welche Layer über Verzeichnisse verfügen und welcher Dockerfile-Befehl den Layer erstellt hat (angezeigt in der Spalte CREATED BY).
Der docker history
Verlaufsbefehl gibt die Ebenen in Ihrem Container in derselben Reihenfolge aus, in der docker inspect
die Ebenenverzeichnisse auflistet. Mit diesem Wissen können wir die beiden Ausgaben manuell zusammenführen, um zu sehen, welche Layer größer als andere sind, welcher Dockerfile-Befehl sie erstellt hat und welches Verzeichnis die einzelnen Layer enthält.
Hier ist der Inhalt von Schicht A, die ein apt-get update
durchführt:
where-the@roadmap-ends ~ $ du -hs /var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff/var/lib/apt/lists 38.2M /var/lib/docker/overlay2/83za1pmv9xri44tddzyju0usm/diff/var/lib/apt/lists
Im Vergleich zum Inhalt der Ebene B werden dadurch die von Ebene A verbleibenden Dateien gelöscht:
where-the@roadmap-ends ~ $ du -hs /var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff/var/lib/apt/lists 4.0K /var/lib/docker/overlay2/wbugwbg23oefsf678r7anbn4f/diff/var/lib/apt/lists
Das Verzeichnis /var/lib/apt/lists
existiert in beiden Schichten, aber in Schicht B belegt das Verzeichnis fast keinen Platz.
Das liegt daran, dass das Verzeichnis der Schicht B „Whiteout-Dateien“ enthält, die Docker verwendet, um Dateien für den Ausschluss aus dem endgültigen Container-Dateisystem zu markieren.
Aus diesem Grund sind die Dateien, obwohl sie in Schicht B „gelöscht“ wurden, immer noch in Schicht A vorhanden und tragen somit zur Gesamtgröße Ihres Bildes bei – 38,2 MB unnötiger Aufblähung.
War das nicht einfach? 😉
Die manuelle Methode ist so komplex und unhandlich, dass die Open-Source-Community speziell für diese Aufgabe ein Tool namens dive
entwickelt hat. Dive ist ein CLI-Tool, das ein Bild als Eingabe verwendet, seine Dateisysteme analysiert und eine textbasierte interaktive Benutzeroberfläche in Ihrem Terminal darstellt. Es korreliert die Bildebenen für Sie, sodass Sie die Ebenenverzeichnisse einfacher überprüfen können.
Wenn es mit dem Docker-Image aus der Docker-Datei oben ausgeführt wird, sieht es so aus:
┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Aggregated Layer Contents ├─────────────────────────────────────────────────────────────────────────────── Cmp Size Command └── var 69 MB FROM acee8cf20a197c9 └── lib 40 MB RUN /bin/sh -c apt-get update # buildkit └── apt 343 MB RUN /bin/sh -c apt-get install -y iputils-ping python3 python3-pip # buildkit └── lists 70 MB RUN /bin/sh -c pip3 install numpy # buildkit ├── auxfiles 0 B RUN /bin/sh -c rm -rf /var/lib/apt/lists/* # buildkit ├── lock ├── partial │ Layer Details ├─────────────────────────────────────────────────────────────────────────────────────────── ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-backports_InRelease ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-backports_main_binary-arm64_Packages.lz4 Tags: (unavailable) ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-backports_universe_binary-arm64_Packages.lz4 Id: 2bc27a99fd5750414948211814da00079804292360f8e2d7843589b9e7eb5eee ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_InRelease Digest: sha256:6e6fb36e04f3abf90c7c87d52629fe154db4ea9aceab539a794d29bbc0919100 ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_main_binary-arm64_Packages.lz4 Command: ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_multiverse_binary-arm64_Packages.lz4 RUN /bin/sh -c apt-get update # buildkit ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_restricted_binary-arm64_Packages.lz4 ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-security_universe_binary-arm64_Packages.lz4 │ Image Details ├─────────────────────────────────────────────────────────────────────────────────────────── ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_InRelease ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_main_binary-arm64_Packages.lz4 Image name: where-the-roadmap-ends ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_multiverse_binary-arm64_Packages.lz4 Total Image size: 522 MB ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_restricted_binary-arm64_Packages.lz4 Potential wasted space: 62 MB ├── ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_universe_binary-arm64_Packages.lz4 Image efficiency score: 90 % ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_InRelease ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_main_binary-arm64_Packages.lz4 Count Total Space Path ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_multiverse_binary-arm64_Packages.lz4 2 28 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy_universe_binary-arm64_Pack ├── ports.ubuntu.com_ubuntu-ports_dists_jammy_restricted_binary-arm64_Packages.lz4 2 7.3 MB /usr/bin/perl └── ports.ubuntu.com_ubuntu-ports_dists_jammy_universe_binary-arm64_Packages.lz4 2 4.4 MB /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.30 2 2.9 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy_main_binary-arm64_Packages 2 2.0 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_main_binary-arm64_ 2 1.7 MB /var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_jammy-updates_universe_binary-ar
Dive ist ein wunderbares Werkzeug und ich bin froh, dass es es gibt – aber manchmal reicht es nicht aus. Textbasierte Schnittstellen sind nicht besonders einfach zu verwenden – manchmal ist Grafana besser als top
.
Außerdem können große Bilder die Fähigkeiten von Dive überfordern. Bei der Untersuchung großer Bilder verbraucht Dive viel Speicher – manchmal bricht der Kernel den Dive-Prozess ab, bevor er Daten ausgibt.
Aus Entwicklersicht war es immer sinnvoll zu erwarten, dass Docker-Image-Layer-Visualisierungen im Docker Hub vorhanden sind. Schließlich befinden sich unsere Docker-Bilddaten bereits im Backend von Docker Hub.
Ich habe mir oft vorgestellt, wie ich die Interna von Docker-Images in meinem Browser untersuche und dabei eine Benutzeroberfläche verwende, die etwa so aussieht:
Mit Docker Scout scheint sich Docker als Unternehmen in diese Richtung zu bewegen. Wenn ich heute zum neuesten Postgres-Image in Docker Hub navigiere, werde ich mit Folgendem begrüßt:
Als Entwickler ist das spannend. Mit dieser neuen Benutzeroberfläche kann ich die Details der Bildebene visuell durchsuchen und Schwachstellen und Bildgrößenprobleme hervorheben, genau wie ich es möchte.
Als Docker Hub zum ersten Mal gestartet wurde, gab es zwei Arten von Benutzern:
Und Docker musste diese beiden Arten von Benutzern unterschiedlich angehen.
Nach dem Debüt von Docker im Jahr 2013 wollten Entwickler ihr Produkt sofort nutzen. Docker-Container standardisieren die Verpackung und Bereitstellung der Software aller. Container wurden in der Softwareentwickler-Community schnell populär.
Die Softwarehersteller waren jedoch zögerlicher. Warum sollten sie ihre Software auf eine Plattform hochladen, deren Hauptnutzen darin besteht, die Differenzierung zwischen Softwareprodukten aufzuheben?
Docker musste sie für sich gewinnen, um Docker Hub erfolgreich zu machen. Anstatt sich also darauf zu konzentrieren, Entwickler anzulocken, richteten sich Design und Funktionsumfang von Docker Hub an Software-Herausgeber.
Für Verlage war Docker Hub eine Marketingseite. Es gab ihnen einen Ort, an dem sie für ihre Produkte werben konnten. Entwickler konnten die internen Details des Docker-Images nicht sehen, genauso wenig wie Autohäuser es ihnen ermöglichten, ihre Motorblöcke zu zerlegen. Interne technische Details wurden nicht zur Schau gestellt, sondern versteckt, sodass sich die Käufer auf die Produkte selbst konzentrierten und nicht darauf, wie sie hergestellt wurden.
Im Docker Hub fehlten entwicklerorientierte Funktionen zur Optimierung der Bildgröße und zur Selbstprüfung der Lieferkette, da Docker bereits Entwickler ohne diese Funktionen überzeugte. Tatsächlich neigen diese Funktionen dazu, Software-Herausgeber in ein schlechtes Licht zu rücken – und sie waren die Benutzer, die Docker Hub noch für seinen Erfolg gewinnen musste.
Diese Dynamik, sowohl Software-Herausgeber als auch Entwickler zu bedienen, machte Docker Hub zu einer zweiseitigen Plattform.
Bei zweiseitigen Plattformen handelt es sich um Unternehmen, die zwei unterschiedliche Kategorien von Benutzern haben und beide an der Plattform teilnehmen müssen, damit die Dinge funktionieren. Normalerweise ist es die Motivation für die Nutzung der Plattform, die Benutzer in verschiedene Gruppen aufteilt.
Während die beiden Benutzergruppen möglicherweise nicht direkt miteinander Geschäfte abwickeln, ähneln zweiseitige Plattformen Märkten: Sie schaffen keinen Wert, es sei denn, Anbieter kommen zum Verkauf und Käufer zum Kauf.
In der Technologiebranche sind zweiseitige Plattformen allgegenwärtig. Wenn diese Geschäftsmodelle erfolgreich sind, erzeugen sie tendenziell Netzwerkeffekte, die Unternehmen zu nachhaltigem Wachstum und Rentabilität verhelfen. Ab einem bestimmten Punkt ziehen die Größe der Plattform und ihre etablierte Position im Raum neue Benutzer auf die Plattform.
Sobald Sie wissen, wonach Sie suchen, sind zweiseitige Plattformen leicht zu erkennen. Hier sind drei Beispiele.
Auf LinkedIn gibt es zwei Arten von Benutzern: Mitarbeiter und Personalmanager. Beide Gruppen beteiligen sich aus unterschiedlichen Gründen – Mitarbeiter wollen Jobs und Personalmanager wollen Leute einstellen.
Keine der Gruppen konnte von der Website das bekommen, was sie wollte, bis die andere Gruppe begann, sich zu beteiligen. Sobald sich eine ausreichende Anzahl jeder Gruppe anmeldete, wurde sie zum Standardanlaufpunkt für neue Mitglieder beider Gruppen, und das Wachstum der Website setzte sich fort, bis hin zur Übernahme durch Microsoft für 26 Milliarden US-Dollar.
Auf YouTube gibt es Content-Ersteller und Zuschauer. Zuschauer kommen auf die Website, um sich Videos anzusehen – Inhaltsersteller veröffentlichen auf der Website auf der Suche nach guter Stimmung, Ruhm und Reichtum.
Nachdem YouTube sich den Ruf erworben hatte, ein Ort zu sein, an dem Content-Ersteller erfolgreich sein können, tauchten immer mehr Content-Ersteller auf – und je mehr Inhalte sie generierten, desto mehr Zuschauer kamen zu Besuch. Sobald die Plattform eine bestimmte Größe überschritt, blieb den Erstellern und Zuschauern von Inhalten nichts anderes übrig, als sie weiterhin zu nutzen – jede Gruppe brauchte die andere und sie konnten sich nur über YouTube finden.
Damit Docker Hub relevant ist, müssen Software-Herausgeber Bilder hochladen und Entwickler diese herunterladen. Sobald genügend Herausgeber Bilder an Docker Hub übermittelten, wurde dieser zum Standardspeicherort für Entwickler. Während die Plattform weiter wuchs, festigte Docker Hub seine Dominanz als einziges Register, das sie alle beherrschte.
Zumindest war das der Plan. In Wirklichkeit lehnte Docker (das Unternehmen) ab und Docker Hub wurde nie „der Richtige“. Beim Aufbau einer zweiseitigen Plattform haben Startups ein Produkt, müssen jedoch zweimal die Produktmarktanpassung finden. Für Docker war diese Belastung zu groß – am Ende spaltete sie Docker in zwei Hälften .
Nach dem Verkauf seines Unternehmensgeschäfts hat sich Docker nun neu erfunden und konzentriert seinen Abonnementdienst ausschließlich auf Entwickler und deren Arbeitgeber. Docker Hub ist keine zweiseitige Plattform mehr, sondern ein einfaches SaaS – Kunden zahlen eine monatliche Gebühr als Gegenleistung für das Pushen und Abrufen ihrer Images.
Dadurch ändert sich die Rechnung. Es gibt keine „Publisher“-Benutzerpersönlichkeit mehr, die Docker zufrieden stellen kann – sie sind ganz auf Entwickler ausgerichtet. Für Docker Hub ebnet dies den Weg für die Image-Layer-Inspektionsfunktion von Docker Scout. Für diejenigen von uns, die aus der Ferne zuschauen, zeigt es die subtile, grundlegende Verbindung zwischen dem Geschäftsmodell eines Startups und seinem Produktangebot.
Auch hier veröffentlicht.