Devfile.io ist eine Open-Source-Initiative, die einen offenen Standard für containerisierte Entwicklungsumgebungen mithilfe von YAML-Dateien definiert. Das Tool wurde 2019 in Go geschrieben und das Hauptziel ist die Einrichtung und Verwaltung von Entwicklungsumgebungen zu vereinfachen und zu automatisieren. Dies ist besonders nützlich in der Cloud-nativen Entwicklung, bei der die Umgebungen über verschiedene Entwicklungs-, Test- und Bereitstellungsphasen hinweg konsistent sein müssen. Devfile ist ausserdem ein CNCF Sandbox Projekt. Hier ein Auszug aus der CNCF Landscape
Entstehung und Zweck
Devfile.io wurde entwickelt, um die Herausforderungen der Aufrechterhaltung konsistenter Entwicklungsumgebungen zu bewältigen. Dies ist gerade in Zeiten von BYOD (bring your own device) wichtig, da die lokale Entwicklungsumgebung meist stark von dem jeweiligen Betriebssystem beeinflusst wird.
Die Idee ist daher, eine standardisierte Methode zur Definition der Konfiguration dieser Umgebungen bereitzustellen, wodurch sie portabel und reproduzierbar werden.
Ein Devfile spezifiziert dabei die Werkzeuge, Abhängigkeiten und Einstellungen, die für eine Entwicklungsumgebung erforderlich sind, sodass Entwickler sofort das machen können, was ihnen auch wirklich Spass macht, nämlich direkt mit dem Coding beginnen zu können, ohne ihre Umgebung jedes Mal aufwendig und nervenaufreibend manuell einrichten zu müssen.
Hauptmerkmale und Vorteile
Standardisierung: Devfiles verwenden YAML und definieren eine klare API mittels einer Schema Version. Reproduzierbarkeit: Durch die Definition der Umgebung in einem Devfile können Entwickler sicherstellen, dass die Umgebung auf verschiedenen Maschinen und über die gesamte Entwicklungslaufzeit eines Projekts konsistent ist. Automatisierung: Devfiles automatisieren die Einrichtung von Entwicklungsumgebungen und reduzieren den Zeit- und Arbeitsaufwand für die manuelle Konfiguration dieser Umgebungen. Integration: Devfiles integrieren sich in verschiedene Entwicklungswerkzeuge und -plattformen wie Eclipse Che, odo und JetBrains Space 1️⃣, Red Hat Developer Sandbox, um nahtlose Entwicklungserfahrungen zu bieten.
1️⃣ Leider sieht es allerdings so aus, als ob Jetbrains Space nicht mehr weiter angeboten wird und durch Github Codespaces ersetzt wurde bzw. gerade in der Transition ist.
Innerloop vs Outerloop
In einer Devfile-Spezifikation gibt es zwei Bereiche für die Bereitstellung: Innerloop und Outerloop. Diese Bereiche sind für eine umfassende Entwicklungserfahrung sowie für die ordnungsgemäße Integration des gesamten Spektrums an Entwicklungstools für Kubernetes- und OpenShift-Projekte unerlässlich.
Innerloop
Innerloop sind alle Aktionen, die ein Entwickler in seiner Entwicklungsumgebung durchführt, z. B. das Ausführen von Tests, Debugging und lokale Implementierungen, bevor er seinen Code in das VCS (Version Control System) eincheckt.
Outerloop
Outerloop deckt somit logischerweise dann alles ab, was nach der Entwicklungsphase kommt. Sobald der Quellcode in das VCS eingecheckt wurde, werden beispielsweise Integrationstests, vollständige Builds oder Deployments durchgeführt.
Aufbau eines Devfile’s
Wollen wir uns erstmal anschauen, wie so ein devfile aussieht. Wir sehen hier ein valides Devfile, welches die Minimalanforderungen erfüllt.
|
|
In Zeile 1 definieren wir die Schema Version. Diese definiert einfach, welche Elemente in unserem Yaml File erlaubt sind und welche nicht, bzw. definiert die API Version, die wir nutzen.
In Zeile 3 definieren wir dann die Komponenten. Komponenten sind nichts anderes als Development Tools, Runtimes oder auch Services. Hier geben wir konkret das Container Image an, welches für die Entwicklung verwendet werden soll.
Es gibt noch viele weitere Dinge, die wir definieren können. Eine vollständige Liste findet man in der Beschreibung der jeweiligen Schema Version.
Aber wollen wir uns doch mal ein kleines Real World Beispiel ansehen. Da ich in meinem nächsten Techup odo anschauen möchte, werde ich euch hier ein Beispiel anhand der Red Hat OpenShift Dev Spaces zeigen. Ich erstelle mir erstmal ein kleines Go Programm mit dem ich dann später “spielen” kann,
|
|
Sollte man noch keinen Red Hat Account haben, muss man sich diesen zuerst erstellen. Ich werde diese Schritte hier nicht einzeln zeigen, da diese recht intuitiv sind. Da ich meistens in IntelliJ entwickle, installiere ich mir als nächstes das OpenShift Toolkit by Red Hat. Nachdem diese beiden Schritte gemacht sind, sollte im IntelliJ ein neues Icon auf der linken Seite erscheinen.
Bei euch steht dort jetzt wahrscheinlich noch eine lokale Url zum Cluster. Mit einem Rechtsklick auf den Server könnt ihr euch dann bei eurem Remote Cluster anmelden. Auch hier sind wieder ein paar Schritte in einem internen IntelliJ Browser erforderlich, auf die ich hier nicht weiter eingehe. Wenn wir das erledigt haben, kümmern wir uns um unser eigentliches Devfile. Lokal habe ich jetzt ein ganz einfaches Go Projekt, mit dem oben gezeigten Code.
Wenn ich wieder zurück in die OpenShift Ansicht gehe, sehe ich mein lokales Projekt und kann dort “Start dev on Cluster” auswählen.
Es öffnet sich ein interaktives Terminal in dem ich nun noch ein paar Angaben machen muss. Ich wähle hier jeweils die Defaults. Wenn alles fertig ist, sollte in der Ausgabe irgendwann die folgenden Zeilen stehen.
[!TIP]
Ich hatte anfangs ein paar Probleme, da die Verzeichnisse nicht korrekt angegeben wurden oder die Berechtigungen auf die Ordner falsch waren. Mit ein paar Anpassungen an den Pfaden hat es aber dann doch geklappt.
1 2
${PROJECT_SOURCE}/.go --> /opt/app-root/src/.go ${PROJECT_SOURCE}/.cache --> /tmp/.cache
Nun wir können jetzt sehen, dass Port Forwardings erzeugt wurden, welche wir jetzt lokal aufrufen können. Wenn ich im Browser http://127.0.0.1:20001/b-nova erscheint die folgende Seite
Wir sehen nun also durch das Port Forwarding im lokalen Browser die Ausgabe der Go Applikation, welche auf dem OpenShift Cluster läuft. Sehr cool!
Gehen wir wieder in unseren lokalen Ordner. Wir sehen, dass das devfile.yaml
für uns angelegt wurde.
Wollen wir uns die Datei doch mal genauer anschauen.
|
|
Das devfile sieht schon etwas komplizierter aus, als unser Minimalbeispiel. Wollen wir mal Zeile für Zeile durchgehen und es aufbröseln. schemaVersion
haben wir uns weiter oben bereits angeschaut. Springen wir also sofort zu den Metadaten.
Metadata
Wie der Name bereits sagt, können wir für unser devfile Metadaten definieren, welche dem Entwickler zusätzliche Informationen liefern. Alle Metadaten sind optional, weswegen ich diese hier nicht weiter verfolge.
Kommen wir zum “Herzstück” unseres devfile’s. Die Component
Component
Die Component
definiert unsere Laufzeitumgebung, bzw. Umgebungen. Es gibt 5 verschiedene Typen von Components: kubernetes
, container
, openshift
, image
, volume
.
Wir schauen uns container
, image
und volume
mal genauer an.
container
Mit container
können wir benutzerdefinierte Tools in den Arbeitsbereich einbinden. Diese werden mittels eines Container Image image
definiert. Wir können dem Container dabei args
, also Argumente übergeben, oder auch per env
Umgebungsvariablen zur Verfügung stellen. Mit endpoints
geben wir an, auf welchen Ports der Container angesprochen werden kann. memoryLimit
definiert noch den maximalen Speicher, der dem Container zur Verfügung steht und mountSources
erlaubt dem Container den Zugriff auf die Projektsourcen (/projects Pfad) .
image
Im Gegensatz zu container
können wir mit image
direkt ein Image basierend auf einem Dockerfile bauen. Hier ein Beispiel:
|
|
Ich denke der Aufbau ist selbsterklärend.
volume
Als letztes schauen wir uns noch volume
an. Wir können diese dazu nutzen, Daten zwischen den Containern auszutauschauen oder auch um Daten während der Entwicklung mit anderen Teams auszutauschen. Schauen wir uns das an einem kleinen Beispiel an:
|
|
Hier sehen wir, dass es ein Volume cache
gibt, welches dann per Volume Mount dem Container hinzugefügt wird.
Zeile 37-78 definiert uns 3 Commands. Schauen wir uns an, was Commands sind und wofür wir diese brauchen.
Commands
Commands in einem Devfile sind spezifische Anweisungen oder Aktionen, die definiert werden, um verschiedene Entwicklungsaufgaben innerhalb einer Entwicklungsumgebung zu automatisieren und zu erleichtern. Diese Commands sind wesentliche Bestandteile eines Devfiles und dienen verschiedenen Zwecken, darunter das Bauen, Testen, Ausführen und Debuggen von Anwendungen.
Wofür werden Commands gebraucht?
- Automatisierung von Aufgaben:
- Build-Commands: Automatisieren den Bauprozess der Anwendung, indem sie die erforderlichen Werkzeuge und Schritte zum Kompilieren des Codes und Erstellen von Artefakten ausführen.
- Run-Commands: Starten die Anwendung in einer bestimmten Umgebung, sei es lokal oder in einer Cloud-Umgebung.
- Test-Commands: Führen Testsuiten aus, um die Anwendung zu überprüfen und sicherzustellen, dass sie wie erwartet funktioniert.
- Standardisierung und Konsistenz:
- Durch die Definition von Commands im Devfile können alle Entwickler eines Teams dieselben Befehle verwenden, was zu einer konsistenteren und vorhersehbareren Entwicklungsumgebung führt.
- Erleichterung der Entwicklung:
- Debug-Commands: Erleichtern das Debuggen der Anwendung durch Vorkonfiguration von Debugging-Tools und -Einstellungen.
- Init-Commands: Führen Initialisierungsaufgaben durch, wie z.B. das Einrichten von Datenbanken oder das Konfigurieren von Umgebungsvariablen.
- Wiederholbarkeit und Skalierbarkeit:
- Commands ermöglichen es, wiederholbare und skalierbare Entwicklungsprozesse zu schaffen, die leicht von einem Entwickler auf den anderen übertragen werden können.
Struktur eines Commands
Ein Command in einem Devfile ist typischerweise als YAML- oder JSON-Eintrag definiert und besteht aus mehreren Komponenten, darunter:
- ID: Die ID des Commands.
- attributes: Map in der man Implementierungsabhängige yaml Attribute definieren kann.
- Type: Der Typ des Commands (z.B.
exec
für das Ausführen eines Shell-Befehls,apply
für das Anwenden einer K8s-Ressource,composite
für die Ausführung mehrerer Sub-Commands ).
|
|
Wir haben in unserem Beispiel nur exec
als Type und wollen uns diesen nun genauer anschauen. Mit dem exec
Type können wir CLI Commands in unserem Container ausführen.
Das Attribut commandLine
definiert dabei den Befehl.
Mit component
können wir angeben, auf welche Komponente sich das Command bezieht. Da wir nur eine Komponente runtime
haben, wird auch nur diese angegeben.
Wir können mit env
jedem Command Umgebungsvariablen zur Verfügung stellen.
Ein weiteres Feld ist group
. Mögliche Werte sind hier build
, run
, test
, debug
oder deploy
. So können wir also für die verschiedenen Phasen in unserer Applikation, ein entsprechendes Command ausführen. isDefault
definiert dann das Standard Command innerhalb einer Gruppe. Es darf nur ein Default Command geben.
Damit können wir also für den entsprechenden Lifecycle genau definieren, was ausgeführt werden soll.
Schauen wir uns das ganze doch mal in der Praxis, am Beispiel unseres Go Programms an. Was passiert jetzt genau, wenn wir lokal entwickeln und sich was am Code ändert.
Wir ändern in unserem Programm die Begrüssung von “Hello” zu “Hello and welcome” ab und beobachten, was genau in der Konsole passiert.
|
|
|
|
Und wenn wir nun im Browser wieder unsere Applikation aufrufen?
Das ist ziemlich cool. Wir können also lokal auf unserem Rechner entwickeln und der ganze Build und Deployment-Prozess wird anhand des devfile’s für uns erledigt. Im Hintergrund wird ein Deployment auf RedHat OpenShift erstellt und die Dateien bei einer Änderung synchronisiert und neu gebaut.
Hier sehen wir das OpenShift Deployment in der Konsole
Schauen wir uns das ganze noch einmal im Pod selbst an. Ich verbinde mich mit dem Terminal zum Pod und gehe in das Source-Verzeichnis:
Wir sehen die letzte Änderung der Binary Datei (also main) war um 10:17. Ich ändere jetzt lokal wieder den Text in der main.go Datei und wie wir sehen können ändern sich die Timestamps der Quelldatei und auch der des Binary.
Das war eine kurze Einführung in devfiles.io was das Setup eines Entwicklungsprojekts wirklich sehr einfach und unkompliziert macht. Dem aufmerksamen Leser ist wahrscheinlich schon aufgefallen, dass im Hintergrund odo.dev verwendet wird, welches ich oben bereits erwähnt habe. Ich werde euch dies im nächsten Techup genauer zeigen.
Devfile Registry
Schauen wir uns als letztes noch die Devfile Registry an. Diese dient dazu, Devfile-Stacks für Kubernetes-Entwicklerwerkzeuge wie odo, Eclipse Che und die OpenShift Developer Console zu speichern und bereitzustellen. Damit können wir also über die Devfile-Registry direkt auf Devfiles zugreifen und diese nutzen.
Dabei entspricht jeder Devfile-Stack einer bestimmten Laufzeit oder einem bestimmten Framework, z. B. Node.js, Quarkus oder Go. Ein Devfile-Stack enthält ausserdem auch noch die devfile.yaml-Datei, ein Logo und auch Outer-Loop-Ressourcen. Diese sorgen dafür das Codeüberprüfungen und Integrationstests ausgeführt werden, die in der Regel durch CI/CD-Pipelines (Continuous Integration/Continuous Delivery) automatisiert werden. Kurz gesagt bieten die Devfile-Stacks Entwicklern eine Vorlagen für den Einstieg in die Entwicklung cloud-nativer Anwendungen.
Fazit
Devfiles ist meiner Meinung nach ein Schritt in die richtige Richtung. Da ich dies bis jetzt aber nur in Testprojekten verwendet habe und auch nicht wirklich im Team damit gearbeitet habe, kann ich es abschliessend noch nicht bewerten. Für meine Tests ist es allerdings ein sehr nützliches Tool, welches die Setup Zeit einer Entwicklungsumgebung wesentlich optimieren kann.