Introduction | odo

25.09.2024Stefan Welsch
DevOps CLI Developer Experience Kubernetes RedHat OpenShift DevOps Cloud Computing

Banner

Letztes Mal habe ich euch Devfile.io vorgestellt. Hier eine kurze Zusammenfassung des Techups: Devfile.io ist eine Open-Source-Initiative, die einen offenen Standard für containerisierte Entwicklungsumgebungen mittels YAML-Dateien definiert. Seit 2019 erleichtert dieser Standard die Einrichtung und Verwaltung von Entwicklungsumgebungen, insbesondere in der Cloud-nativen Entwicklung. Als CNCF-Sandbox-Projekt ermöglicht Devfile die Standardisierung von Konfigurationen, was Portabilität und Reproduzierbarkeit gewährleistet. Devfiles definieren Tools, Abhängigkeiten und Einstellungen und sorgen somit für Automatisierung und Konsistenz. Diese Devfiles sind mit Tools wie Eclipse Che und odo.dev integriert und unterscheiden zwischen Innerloop- und Outerloop-Aktionen, die den gesamten Entwicklungs- und Deployment-Prozess abdecken. Zudem bietet die Devfile-Registry vorgefertigte Stacks für verschiedene Laufzeiten und Frameworks an, die den Einstieg in die Cloud-native Entwicklung erleichtern.

Nachdem wir die Vorteile von Devfile.io rekapituliert haben, widmen wir uns nun einem weiteren wichtigen Tool in der Cloud-nativen Entwicklungslandschaft, das den Devfile-Standard implementiert: odo. Odo ist ein Open-Source-Tool, das speziell für die Entwicklung von Anwendungen auf Kubernetes und OpenShift entwickelt wurde. Es nutzt Devfiles zur Definition und Verwaltung von Entwicklungsumgebungen und vereinfacht damit den gesamten Entwicklungsprozess erheblich. Im Folgenden betrachten wir die Funktionen und Vorteile von odo.dev und zeigen, wie es Entwicklern hilft, effizient und konsistent in Kubernetes-Umgebungen zu arbeiten.

Zielplattform vorbereiten

Damit wir odo nun nutzen können, brauchen wir eine Plattform, auf welcher unsere Container laufen können. Wir haben hier mehrere Möglichkeiten. Wir könnten uns beispielsweise ein lokales Kubernetes aufsetzen. Hier würde uns beispielsweise minikube weiterhelfen. Wer natürlich einen existierenden Kubernetes Cluster hat, kann den auch einfach nutzen.

Eine einfachere Variante wäre es allerdings, wenn wir lokal einfach Podman nutzen. Du kannst dir die Podman CLI installieren und ganz einfach eine Instanz starten. Eine Anleitung zu den verschiedenen Systemen findet ihr direkt auf der Podman Seite. Für Mac Benutzer hier ein Shortcut:

1
2
3
brew install podman
podman machine init
podman machine start

Da wir es aber nicht ganz einfach wollen, nutzen wir trotzdem die Kubernetes Variante, da wir uns damit noch ein zusätzliches odo Kommando genauer anschauen können ;-) Damit wir also loslegen können, müssen wir odo erstmal mit unserem Kubernetes Cluster “verbinden”. Dazu erstellen wir einen eigenen Namespace mit der odo CLI.

1
odo create namespace odo-sample

Nun fragt ihr euch vielleicht, was macht odo create namespace denn mehr als die standard Erstellung eines Namespaces über kubectl. Jetzt die Antwort ist relativ einfach, nämlich eigentlich nichts :-) Das Command ist lediglich Provider agnostisch, sprich, wenn ich odo create namespace in einer Openshift Umgebung ausführe, so wird anstatt eines Namespaces ein Projekt erstellt. Hierzu gibt es von odo auch einen Alias odo create project. Das Command ist also schlau genug die unterstützten Ressourcen des aktuellen Clusters zu handhaben. Du kannst also odo create project in einem Kubernetes Cluster ausführen und es wird einen Namespace erstellen oder du kannst odo create namespace in einem OpenShift Cluster verwenden und ein Projekt würde erstellt werden.

Initialisierung

Es gibt zwei Möglichkeiten, ein Projekt mit odo zu starten: Entweder wird ein bestehendes Projekt “odo-isiert”, oder es wird ein neues Projekt mit odo erstellt. Schauen wir uns beide Optionen an.

Vorhandenes Projekt

Ich werde dazu erstmal ein einfaches Golang Projekt erstellen.

1
mkdir odo-sample && cd odo-sample

Dann erstellen wir eine main.go mit folgendem Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", HelloServer)
	err := http.ListenAndServe("0.0.0.0:8080", nil)
	if err != nil {
		return
	}
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
	_, err := fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
	if err != nil {
		return
	}
}

Als Letztes noch das Module initialisieren und kurz testen, ob alles funktioniert und schon sind wir ready um das Projekt mit odo zu verwalten.

1
2
go mod init com.bnova.bnova-techup.odo-sample
go run main.go

Als Nächstes benötigen wir ein devfile, welches verschiedene Konfigurationen enthält, die wir brauchen, um unsere Applikation auf unserem Cluster laufen zu lassen. Wer zu devfile weitere Informationen benötigt, dem darf ich an dieser Stelel mein Techup über devfile.io empfehlen.

Die odo CLI bietet uns nun eine einfache Möglichkeit das Devfile, basierend auf unserem vorhandenen Code zu erstellen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
odo init
  __
 /  \__     Initializing a new component
 \__/  \    Files: Source code detected, a Devfile will be determined based upon source code autodetection
 /  \__/    odo version: v3.16.1 (v3.16.1-Homebrew)
 \__/

Interactive mode enabled, please answer the following questions:
 ✓  Determining a Devfile for the current directory [645ms]
Based on the files in the current directory odo detected
Supported architectures: all
Language: Go
Project type: Go
Application ports: 8080
The devfile "go:1.2.1" from the registry "DefaultDevfileRegistry" will be downloaded.
? Is this correct? (Y/n) 

Wie wir sehen können, werden basierend unseres Projekts bestimmte Dinge wie Language, Project Type und auch Application Ports schon befüllt. Sobald wir die Parameter bestätigen wird das angegebene Image heruntergeladen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
✓  Downloading devfile "go:1.2.1" from registry "DefaultDevfileRegistry" [1s]

↪ Container Configuration "runtime":
  OPEN PORTS:
    - 8080
    - 5858
  ENVIRONMENT VARIABLES:
    - DEBUG_PORT = 5858

? Select container for which you want to change configuration?  [Use arrows to move, type to filter]
  runtime
> NONE - configuration is correct

Nun werden wir noch gefragt, ob die Container Konfiguration mit den Ports und Umgebungsvariablen richtig ist. An dieser Stelle könnten wir diese anpassen. Was wir auch jetzt schon sehen können, ist, dass wir die Möglichkeit haben mehrere Container Konfigurationen anzulegen. Dazu aber später mehr. Wir haben hier nichts zu bemängeln und wählen NONE aus.

1
2
3
4
5
6
7
? Enter component name: (odo-sample)
You can automate this command by executing:
   odo init --name odo-sample --devfile go --devfile-registry DefaultDevfileRegistry --devfile-version 1.2.1

Your new component 'odo-sample' is ready in the current directory.
To start editing your component, use 'odo dev' and open this folder in your favorite IDE.
Changes will be directly reflected on the cluster.

Als Nächstes geben wir noch einen Komponentennamen ein. Hier können wir für unser Beispiel einfach mal den Standard lassen und drücken wieder Enter. Sobald wir das erledigt haben, ist das initiale Setup erstmal erledigt. Im Ordner selbst können wir nun sehen, dass ein devfile.yaml angelegt wurde und ein Ordner namens odo.

img.png

Neues Projekt

Um ein neues Projekt mit odo zu initialisieren, erstellen wir uns einen neuen leeren Ordner

1
mkdir odo-quarkus && cd odo-quarkus

Anschliessend führen wir in diesem Ordner das odo init Kommando aus. Dieses Kommando erstellt uns wieder das devfile.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
odo init
  __
 /  \__     Initializing a new component
 \__/  \    Files: No source code detected, a starter project will be created in the current directory
 /  \__/    odo version: v3.16.1 (v3.16.1-Homebrew)
 \__/

Interactive mode enabled, please answer the following questions:
? Select architectures to filter by:  [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]
> [x]  amd64
  [ ]  arm64
  [ ]  ppc64le
  [ ]  s390x

Nun folgt ein interaktiver Modus um zu bestimmen welche Architektur, welche Programmiersprache und welches Framework genutzt werden soll.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Interactive mode enabled, please answer the following questions:
? Select architectures to filter by: arm64
? Select language: Java
? Select project type: Quarkus Java
? Select version:  1.5.0 (default)
✓  Downloading devfile "java-quarkus:1.5.0" from registry "DefaultDevfileRegistry" [1s]

↪ Container Configuration "tools":
OPEN PORTS:
- 8080
- 5858
ENVIRONMENT VARIABLES:
- DEBUG_PORT = 5858

? Select container for which you want to change configuration?  [Use arrows to move, type to filter]
tools
> NONE - configuration is correct

Für Enterprise Kunden ist hier auch interessant, dass man als Starter Projekt sofort die RedHat Version von Quarkus nutzen kann. Für unseren Test reicht uns aber erst mal die Community Version. Danach müssen wir noch wie bereits oben gesehen den Komponentennamen angeben und unser Projekt wird erstellt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
? Which starter project do you want to use?  [Use arrows to move, type to filter]
? Which starter project do you want to use? community
? Enter component name: odo-quarkus
 ✓  Downloading starter project "community" [646ms]

You can automate this command by executing:
   odo init --name odo-quarkus --devfile java-quarkus --devfile-registry DefaultDevfileRegistry --devfile-version 1.5.0 --starter community

Your new component 'odo-quarkus' is ready in the current directory.
To start editing your component, use 'odo dev' and open this folder in your favorite IDE.
Changes will be directly reflected on the cluster.

Auch hier wird wieder ein komplettes Projekt inklusive odo Integration und Devfile für uns erstellt. Wer sich das devfile.yaml mal genauer anschauen und verstehen will, dem lege ich auch wieder mein Techup über devfile.io ans Herz. Dort erkläre ich es Zeile für Zeile,

Das Projekt können wir dann in unserer IDE öffnen und direkt loslegen. Apropos IDE. Es gibt das Plugin OpenShift Toolkit von Red Hat, welches sowohl für Jetbrains Produkte als auch für Visual Studio Code verfügbar ist. Diese ermöglichen es direkt mit OpenShift oder auch Kubernetes-Clustern direkt zu interagieren. Dabei werden die odo und oc Binarys genutzt.

Da wir nun gesehen haben, wie man mit odo ein Projekt erstellen, bzw. initialisieren kann, schauen wir uns nun an, wie man odo zur schnellen Entwicklung nutzen kann.

Development

Starten wir also mit der lokalen Entwicklung. Dies können wir mit einem der zwei wichtigsten Kommandos in odo tun odo dev. odo dev ist sehr nützlich in der initialen Phase der Entwicklung, wenn man also oft Änderungen am Code durchführt und sich diese direkt anschauen möchte. Der dev Modus ermöglicht auch Debugging und man kann Tests ausführen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
odo dev                                                                                                                                                                                                                                       11m 10s
  __
 /  \__     Developing using the "odo-quarkus" Devfile
 \__/  \    Namespace: odo-quarkus
 /  \__/    odo version: v3.16.1 (v3.16.1-Homebrew)
 \__/

↪ Running on the cluster in Dev mode
 ✓  Web console accessible at http://localhost:20000/ 
 ✓  API Server started at http://localhost:20000/api/v1 
 ✓  API documentation accessible at http://localhost:20000/swagger-ui/ 
 •  Waiting for Kubernetes resources  ...
 ✓  Added storage m2 to component
===================
⚠  Pod is Pending
===================
 

Wie wir sehen können wird die Applikation auf dem Cluster gestartet. Schauen wir uns das einmal mit kubectl an

1
2
3
k get pods                                                                                                                                                                       ⎈ docker-desktop/odo-quarkus
NAME                               READY   STATUS    RESTARTS   AGE
odo-quarkus-app-75cd8696d9-ws6nb   1/1     Running   0          2m6s

Wer sich jetzt fragt, wieso wir keinen Namespace angeben müssen um den Pod zu sehen, der hat sehr gut aufgepasst. ;-) Die Erklärung dafür ist aber recht einfach. Weiter oben haben wir odo create namespace odo-sample ausgeführt. Dadurch wird nicht nur der Namespace erstellt, sondern auch als Default gesetzt. Wenn wir den Namespace auf einen bereits bestehenden wechseln wollen, können wir dies mit odo set namespace tun.

Der erste Start kann etwas länger dauern, da noch die Images heruntergeladen werden müssen. Wenn das aber alles durch ist, sollten wir die folgende Ausgabe im Terminal sehen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
✓  Pod is Running
 ✓  Syncing files into the container [87ms]
 ✓  Executing post-start command in container (command: init-compile) [16s]
=============================================================================================
⚠  WARNING: Building images on Apple Silicon / M1 is not (yet) supported natively on Podman
=============================================================================================
====================================================================================================
⚠  There is however a temporary workaround: https://github.com/containers/podman/discussions/12899
====================================================================================================
 •  Executing the application (command: dev-run)  ...
 ✓  Waiting for the application to be ready [1s]
 -  Forwarding from 127.0.0.1:20001 -> 8080


↪ Dev mode
 Status:
 Watching for changes in the current directory /Users/swelsch/Development/b-nova/github.com/b-nova-techhub/odo-quarkus

Wir sollten nun also mit http://localhost:20001 auf unsere Applikation zugreifen können. Funktioniert

1
2
curl localhost:20001/hello
Hello b-nova!%                                                                                                             

Nun wollen wir eine Anpassung am Code machen und schauen was passiert. Wir fügen im Output noch was hinzu

1
2
3
4
5
6

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
    return "Hello and welcome, b-nova";
}

Sobald wir die Datei speichern, können wir im Terminal folgendes beobachten

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
File /Users/swelsch/Development/b-nova/github.com/b-nova-techhub/odo-quarkus/src/main/java/org/acme/GreetingResource.java changed
 •  Waiting for Kubernetes resources  ...
 ✓  Syncing files into the container [22ms]
=============================================================================================
⚠  WARNING: Building images on Apple Silicon / M1 is not (yet) supported natively on Podman
=============================================================================================
====================================================================================================
⚠  There is however a temporary workaround: https://github.com/containers/podman/discussions/12899
====================================================================================================
 ✓  Waiting for the application to be ready [1s]

↪ Dev mode
 Status:
 Watching for changes in the current directory /Users/swelsch/Development/b-nova/github.com/b-nova-techhub/odo-quarkus

Die Änderungen, die wir am Code machen, werden also ad-hoc auf unserem Cluster appliziert. Wenn wir entsprechend einen Curl ausführen, sehen wir auch die erwartete neue Begrüssung.

1
2
curl localhost:20001/b-nova
Hello and welcome, b-nova!%                                                                            

Logausgabe

Was uns noch fehlt, sind Logausgaben. Im Terminal sehen wir lediglich die Ausgaben, welche odo generiert. Hierzu bietet uns die odo CLI aber natürlich auch ein Kommando an odo logs Hier sehen wir einen Teilauszug aus den Logs. Natürlich kann ich auch direkt ein --follow im Kommando angeben, um der Logausgabe zu folgen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
odo logs

tools: Listening for transport dt_socket at address: 5005
tools: Press [e] to edit command line args (currently ''), [h] for more options>
tools: Tests paused
tools: Press [e] to edit command line args (currently ''), [r] to resume testing, [h] for more options>
tools: Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [h] for more options>
tools: 2024-08-14 06:21:34,577 INFO  [io.qua.kub.dep.PropertyUtil] (build-5) Kubernetes manifests are generated with 'The container port http' having default value '8080'. The app and manifests will get out of sync if the property 'quarkus.http.port' is changed at runtime.
tools: __  ____  __  _____   ___  __ ____  ______ 
tools:  --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
tools:  -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
tools: --\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
tools: 2024-08-14 06:21:35,273 INFO  [io.quarkus] (Quarkus Main Thread) code-with-quarkus 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.13.2) started in 1.292s. Listening on: http://0.0.0.0:8080
tools: 2024-08-14 06:21:35,274 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
tools: 2024-08-14 06:21:35,274 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, kubernetes, micrometer, resteasy, smallrye-context-propagation, smallrye-health, vertx]

Ausführung im Container

Wenn man nun einen Befehl im Container ausführen möchte, bietet uns odo ein run Kommando odo run an. Mit diesem Kommando kann man “Commands” ausführen, welche wir im Devfile definieren. Für alle die jetzt verwirrt sind, folgt hier ein Beispiel ;-) Ich definiere also im Devfile das folgende “Command”:

1
2
3
4
5
6
commands:
    ...
      - exec:
      component: tools
      commandLine: bash
      id: connect

Nun kann ich mittels odo über das Kommando run dieses Command ausführen und eine Shell sollte in meinem Container gestartet werden, also:

1
2
3
4
5
6
7
8
odo run connect
=============================================================================================
⚠  WARNING: Building images on Apple Silicon / M1 is not (yet) supported natively on Podman
=============================================================================================
====================================================================================================
⚠  There is however a temporary workaround: https://github.com/containers/podman/discussions/12899
====================================================================================================
[jboss@odo-quarkus-app-75cd8696d9-zxgxj ~]$ 

Dies kann sehr praktisch sein, um vordefinierte Commands zur Verfügung zu stellen, beispielsweise Löschen von bestimmten Daten oder Konfiguration bestimmter Applikationen.

Web Console

In der Konsolenausgabe können wir sehen, dass es auch eine Web Console gibt, welche wir unter der angegebenen Url aufrufen können.

1
Web console accessible at http://localhost:20000/

img_6.png

Das ist natürlich sehr nice, denn so können wir die gesamte Konfiguration auch bequem in der Weboberfläche machen. Zum Testen passen wir in den Metadaten ein Feld an und schauen, ob dieser Wert auch wirklich im devfile reflektiert wird. In der yaml Datei sehen wir die folgenden Metadaten.

img_7.png

Wir wollen über die Weboberfläche nun die Beschreibung ändern. Hier muss man beachten, dass man nach dem Update des Werts “Apply” noch zusätzlich “Save” in der oberen rechten Ecke klicken muss.

img_8.png

Sobald dies erledigt ist schauen wir uns wieder das devfile.yaml an und können sehen, dass die Datei auch aktualisiert wurde.

img_9.png

Deployment

odo bietet uns theoretisch auch eine Möglichkeit an, unsere Applikation direkt auf einem Cluster zu deployen. Ich bin mir allerdings nicht sicher, ob das wirklich einen realen Use Case darstellt, zumindest nicht in der professionellen Softwareentwicklung. Hier würde ich immer den Weg über eine CICD Pipeline gehen. Daher werde ich odo deploy in diesem Techup nicht weiter betrachten.

Fazit

Odo ist ein vielseitiges Tool, das die Entwicklung von Anwendungen auf Kubernetes und OpenShift erheblich vereinfacht. Es ermöglicht eine nahtlose Integration mit Devfiles und bietet Entwicklern eine benutzerfreundliche Umgebung, um sich auf das Wesentliche zu konzentrieren: das Schreiben von Code. Die Integration von Plugins für gängige IDEs und die Verfügbarkeit einer Web-Konsole machen Odo zu einem mächtigen Werkzeug in der Cloud-nativen Entwicklung. Während die direkte Deployment-Funktion von Odo in professionellen Umgebungen möglicherweise weniger relevant ist, stellt es dennoch eine wertvolle Ressource dar, um den Entwicklungsprozess zu optimieren und die Effizienz zu steigern.

Stefan Welsch

Stefan Welsch – Manitu, Pionier, Stuntman, Mentor. Als Gründer von b-nova ist Stefan immer auf der Suche nach neuen und vielversprechenden Entwicklungsfeldern. Er ist durch und durch Pragmatiker und schreibt daher auch am liebsten Beiträge die sich möglichst nahe an 'real-world' Szenarien anlehnen.