Devfile.yml und devcontainer.json sind beides Standards zur Beschreibung von Entwicklungscontainern. Diese Standards werden von unterschiedlichen Anwendungen verwendet und deployen einen Container auf Grundlage dieser Dateien.
Als ich das erste Mal von Devfile hörte, war mein erster Gedanke: Was ist der Unterschied zu Dockerfile? Ein Dockerfile beschreibt auch einen Container, egal ob es sich um einen Production-, Development- oder einen anderen Container handelt. Außerdem habe ich mich gefragt: Warum brauche ich einen speziellen Standard für Entwicklungsumgebungen? Ist ein Container nicht ein Container? Und was darin ist, sollte für den Standard, der ihn beschreibt, doch keine Rolle spielen…
Die devcontainers-Dokumentation versucht, diese Frage zu beantworten. Entwicklungscontainer enthalten in der Regel zusätzliche Pakete und Werkzeuge, die nicht Teil einer Produktionsumgebung sind. Allerdings war mir immer noch nicht klar, warum dies einen separaten Standard erfordert. Könnte ich nicht einfach Dockerfiles verwenden, um unterschiedliche Container für Entwicklung und Produktion zu beschreiben?
Die Antwort ist: Ja, kann man. Aber Devcontainer und Devfile stellen zusätzliche Funktionen zur Verfügung, die über das was ein Dockerfile alleine tun kann, hinausgehen.
Bevor ich zum Vergleich komme, beschreibe ich kurz, was Dockerfile, Docker-Compose, Devfile und Devcontainer eigentlich tun.
Dockerfile
Dockerfiles enthalten Anweisungen, um ein Docker-Container-Image zu erstellen. Ein Dockerfile beschreibt einen einzelnen Container. Docker kann ein Docker-Container-Image aus einer Docker-Datei erstellen. Beispielsweise spezifiziert ein Dockerfile:
- Welches Betriebssystem im Container verwendet werden soll.
- Es kann Dateien und Pakete zu den Container hinzufügen.
- Es kann Shell-Befehle angeben, die im Container ausgeführt werden sollen etc.
Das Dockerfile beinhaltet alle benötigten Informationen, um einen Container von Grund auf zu erstellen: Es gibt an, was der Container enthalten soll.
Docker-Compose
Docker-Compose hingegen erlaubt es, Mulit-Container-Applikationen zu beschreiben. In einer Docker-Compose.yaml können Docker-Container referenziert werden. Es kann außerdem festgelegt werden, wie diese miteinander interagieren sollen. Beispiel: In einer Docker-Compose-Datei kann ein Frontend-, ein Backend- und ein Datenbank-Container referenziert werden. Es kann ein Netzwerk für die Container definiert werden, um die Kommunikation zwischen den Containern festzulegen. Ports können gemappt werden, Secrets injiziert werden und dem Container Speichervolumen zugeordnet werden. Die Docker-Compose-Datei kann auf Container-Images in einer Container-Registry verweisen oder Docker-Files referenzieren, um die Container von Grund auf neu zu erstellen.
Devfile & Devcontainer
Devfile und Devcontainer stellen einen weiteren Layer zur Verfügung. Das erlaubt es, eine vollständige Entwicklungsumgebung zu definieren. Das beinhaltet die Referenzierung von Container-Images (von einer Registry oder Docker-Files oder Docker-Compose-Files) und anderen Komponenten, die eine Entwicklungsumgebung ausmachen. Beispielsweise IDE-Plugins, Source-Code-Repos und Befehle, die nach der Erstellung der Entwicklungsumgebung ausgeführt werden sollen.
Devfile- und Devcontainer können mit verschiedenen Tools verwendet werden, die diese interpretieren und ausführen. Der Großteil dieser Tools sind IDEs. Eine Liste von kompatiblen tools sind in der Dokumentation zu finden:
Devcontainer: https://containers.dev/supporting
Devfile: https://devfile.io/docs/2.2.2/innerloop-vs-outerloop#support-list-of-developer-tools
Devfile und Devcontainer beinhalten:
- “Interpreter-Spezifische”-Konfiguration: Beispielsweise VS Code-Erweiterungen, die installiert werden, wenn ein devcontainer.json interpretiert (ausgeführt) wird oder das Directory, das den Source Code enthält und welches geöffnet und von der IDE zugeordnet werden soll.
- Anpassung des Containers: Port-Zuordnungen (sofern diese nicht bereits in einer referenzierten Docker-Compose-Datei angegeben sind) und Befehle, die innerhalb des Containers ausgeführt werden, z. B. zum Starten von Diensten,
- Skript(e), das/die nach dem Deployment der Container ausgeführt wird/werden, z. B. ein CLI, das Sie durch die Konfiguration der Anwendung führt (z. B. zum Anlegen von Benutzer_innen) oder ein Skript, das ein SSL-Zertifikat erstellt und lädt.
Die Verwendung einer devfile- oder devcontainer-Datei könnte zum Beispiel so aussehen:
- Sie klonen ein Quellcode-Repository, das ein devfile oder eine devcontainer.json enthält.
- Sie öffnen die devfile oder devcontainer.json in einer IDE, die diese Standards unterstützt.
- Die IDE fragt Sie, ob Sie die Konfiguration ausführen möchten. Wenn Sie sich dafür entscheiden, wird sie das tun, was in der Konfiguration angegeben ist. Zum Beispiel könnte sie:
- Images aus einer Registry ziehen oder Images aus dockerfiles /docker-compose im Repository erstellen,
- die Befehle aus der devfile oder devcontainer.json im Container ausführen,
- Ein Post-Installations-Skript ausführen,
- IDE-Plugins installieren,
- Das Quellcode-Repository in der IDE öffnen.
Damit dies funktioniert, müssen bestimmte Voraussetzungen erfüllt sein. Beispielsweise muss Docker auf dem System installiert sein und die IDE muss korrekt konfiguriert sein, damit sie Docker-Befehle ausführen kann.
Die Container, die bei diesem Prozess deployed werden, enthalten in der Regel die Software, an der Sie arbeiten möchten – mit allen dafür erforderlichen Abhängigkeiten. Dies wird von Docker durchgeführt und erfordert korrekt konfigurierte Docker-Container.
Zusammenfassend lässt sich sagen, dass die Standards devfile und devcontainer.json Folgendes bieten:
- Bequemlichkeit für die Docker-Funktionalität: In der Konfigurationsdatei werden die Docker-Befehle angegeben und von der IDE ausgeführt,
- Bequemlichkeit für die Ausführung anderer Konfigurationsschritte wie das Ausführen von Installationsskripten,
- Die IDE-Konfiguration.
Unterschiede
Im Gegensatz zu Dockerfiles erstellen Devfile und Devcontainer einen Container nicht von Grund auf, sondern fügen einen neuen Layer zu den bestehenden Docker-Container-Images hinzu. Im Gegensatz zu Docker-Compose spezifizieren Devfile und Devcontainer nicht, wie die Container zusammenspielen.
Ähnlichkeiten
Einige ähnliche Funktionen sind aber in einigen oder allen von diesen Standards vorhanden. Alle erlauben die Angabe von Befehlen, die innerhalb des Containers in verschiedenen Phasen seiner Erstellung oder Deployments ausgeführt werden sollen. Dies ist sinnvoll und sogar notwendig, um eine angemessene Trennung der Bereiche und die Wiederverwendbarkeit der Konfiguration zu ermöglichen.
Ein Dockerfile, das beschreibt, wie ein einzelner Container erstellt wird, kann für viele verschiedene Zwecke wiederverwendet werden. Beispielsweise, um eine Anwendung in Entwicklungs-, Test- und Produktionsumgebungen auszuführen. Die Befehle, die im Dockerfile angegeben werden, sollten generisch sein und nur dem Zweck dienen, den Inhalt des Containers zu definieren.
Die Docker-Compose-Datei beschreibt, wie verschiedene Container zusammenarbeiten sollen, um eine Multikomponenten-Anwendung zu bilden. Daher hat die Docker-Compose-Datei Kenntnis von anderen Containern, die der einzelne Container nicht hat. In der Docker-Compose-Datei angegebene Befehle können Befehle ausführen, die nur möglich sind, wenn andere Container existieren, oder die nur bei Vorhandensein anderer Container Sinn machen – zum Beispiel das Verbinden der Container mit demselben Netzwerk.
In einer Entwicklungsumgebung können andere Befehle sinnvoll sein. Eben solche, die für die Entwicklung notwendig sind, z. B. das Vorladen einer Anwendung mit Testdaten oder das Einfügen von Zugriffsschlüsseln in einem Development-Cluster.
Port-Zuordnungen können sowohl in Docker-Compose als auch in Devfile- und Devcontainer-Dateien angegeben werden. Wenn eine Docker-Compose-Datei in einer Devfile- oder Devcontainer-Datei referenziert wird, können die Port-Zuordnungen in der Docker-Compose-Datei enthalten sein. Devfile und Devcontainer erlauben aber Verweise auf Container-Images sowie auf Dockerdateien, die keine Port-Zuordnungen enthalten.
Was fehlt
Eine Sache, die bei all diesen Standards fehlt, ist die Spezifikation der zugrunde liegenden Infrastruktur, auf der die Container oder Entwicklungsumgebungen ausgeführt werden sollen. (Obwohl sowohl Devcontainer als auch Devfile es erlauben, einige Aspekte davon zu spezifizieren, z. B. minimale Host-Anforderungen (Devcontainer) oder wie ein Deployment nach Kubernetes aussehen sollte (Devfile).)
Dies führt zurück zum Thema der Trennung von einzelnen Bereichen: Es gibt separate Tools, die auf die Automatisierung der Infrastruktur spezialisiert sind und die in Kombination mit den beschriebenen Standards verwendet werden können, um die Erstellung vollständiger Entwicklungsumgebungen zu automatisieren. In der Regel ist die zugrunde liegende Infrastruktur aber der Entwickler_innen.
Fazit
Betrachtet man die von Devfile, Devcontainer, Dockerfile und Docker-Compose bereitgestellten Funktionen, so hat jede von ihnen einen berechtigten Platz im Konfigurations- und Automatisierungs-Toolstack für das lokale Deployment von Software als Teil einer Entwicklungsumgebung.
Der besorgniserregende Eindruck, den ich aber habe, ist, dass all dies unglaublich kompliziert ist. Es gibt mehrere Komplexitätsebenen, von denen jede ihren eigenen Standard rechtfertigt. Abgesehen von dem Aufwand, der mit dem Verständnis und der Anwendung all dieser Konfigurationsschichten und deren Zusammenspiel verbunden ist, schafft die Verteilung der Funktionalität auf viele verschiedene, sich teilweise überschneidende Konfigurationsstandards eine große Anzahl potenzieller Fehlerquellen. Die Tatsache, dass die Konfiguration über mehrere einzelne Konfigurationsdateien verteilt ist, macht es außerdem schwierig, Probleme zu erkennen und zu beheben, wenn sie auftreten.
Verschärft wird dies durch das Bestreben von Devfile und Devcontainer, als umfassende Standards zu dienen. Das erfordert, dass sie eine große Anzahl möglicher Implementierungen unterstützen, was zu einer zunehmenden Komplexität in ihren Schemata führt.
Devcontainer unterstützt bereits jetzt zusätzliche Sub-Files für sogenannte Features, die die Funktionalität weiter ausbauen. Auf der einen Seite sieht dies nach einem Feature-Creep aus, wie das bei populären Tools üblich ist, die sich aufblähen, wenn sie erweitert werden, um mehr und mehr Edge-Cases zu unterstützen. Andererseits frage ich mich angesichts der unzähligen Konfigurationsstandards und von denen einige eine sehr enge Auswahl an Anwendungsfällen abdecken (wie devfile und devcontainer), ob es nicht eine Verbesserung wäre, wenn einige von ihnen ihren Anwendungsbereich erweitern würden, so dass man nicht noch einen weiteren Konfigurationsstandard zu einem bereits komplexen Stack hinzufügen muss.
Wie lässt sich das Problem lösen?
Um diese zunehmende Komplexität zu bewältigen, wird eine Abstraktionsebene benötigt, die es Entwicklerinnen und Entwicklern ermöglicht, Deployments als Service, ohne sich um die erforderliche Konfiguration kümmern zu müssen. Das ist, was wir mit Cloudomation DevStack, unserer Plattform für Remote-Entwicklungsumgebungen, anbieten. DevStack ermöglicht es einem spezialisierten Team von DevOps- oder Platform-Engineers, Entwicklungsumgebungen als Service für Entwickler_innen bereitzustellen. Entwickler_innen müssen sich nicht mehr selbst um die immer komplexer werdenden, voneinander abhängigen Konfigurationen kümmern, nur um ihre lokalen Entwicklungsumgebungen zum Laufen zu bringen.