Das Wissensportal für IT-Professionals. Entdecke die Tiefe und Breite unseres IT-Contents in exklusiven Themenchannels und Magazinmarken.

SIGS DATACOM GmbH

Lindlaustraße 2c, 53842 Troisdorf

Tel: +49 (0)2241/2341-100

kundenservice@sigs-datacom.de

Turnaround-Turbo: Effizientes Shift-Left DevSecOps mit Java und Tilt

In der modernen DevSecOps-Praxis ist die Turnaround-Zeit eine entscheidende Metrik: Sie misst, wie schnell Software Engineers Feedback zu ihren Code-Änderungen erhalten. Um diese Zeit zu verkürzen, verfolgt der Shift-Left-Ansatz das Ziel, Entwicklungsaktivitäten so früh wie möglich im Prozess zu verlagern. Dabei spielen die lokale Ausführbarkeit und Testbarkeit eine zentrale Rolle. Dabei hilft das freie Open-Source-Tool Tilt.

(Artikel aus JavaSPEKTRUM 1/2025)

Banner Java Spektrum

Author Image
Andreas Zitzelsberger

Technischer Geschäftsbereichsleiter


  • 24.01.2025
  • Lesezeit: 23 Minuten
  • 713 Views

Tilt ist ein leistungsstarkes Werkzeug, das das lokale Ausführen und Testen von Anwendungen in Kubernetes-Umgebungen erheblich vereinfacht. In diesem Artikel wird praxisnah erläutert, wie Tilt in Java-Projekten genutzt werden kann, um Anwendungen bereits während der Entwicklung kontinuierlich lokal zu testen und zu überwachen. Dadurch lassen sich Produktivität, Qualität, Sicherheit und die Developer Experience signifikant verbessern.

Motivation

Der Shift-Left-Ansatz verfolgt das Ziel, Entwicklungsaktivitäten so früh wie möglich im Prozess – nach links – zu verlagern. Die Wirkung ist offensichtlich: Jede Sekunde Turnaround-Zeit ist verschwendet, Warten ist frustrierend und Entwickler-Zeit ist teuer.

Die wahrscheinlich wichtigste Quelle für verlorene Zeit ist der Change-Build-Deploy-Workflow. Die lokale Ausführbarkeit ist ein großer Hebel, um hier Beschleunigung zu erreichen. Unittests lassen sich lokal während der Entwicklung ausführen. Doch sobald eine Anwendung im Verbund getestet werden soll, wird es schwierig. Früher konnten diese Klippen nur mit Test-Datenbanken wie H2, aufwendigen Mocks und komplexen und fragilen lokalen Setups umschifft werden. Docker und Kubernetes bieten hier insbesondere in Hinblick auf Isolation und Reproduzierbarkeit bessere Optionen, doch auch diese kommen entweder mit Annahmen und Einschränkungen oder, wie bei händisch gepflegten Kubernetes-Umgebungen, mit aufwendigem Setup und Pflege.

Neben der Produktivität ist die Verfügbarkeit von isolierten und unabhängigen Umgebungen, in denen Entwickler frei schalten und walten können, ein großer Vorteil, um Qualität und Sicherheit von Anwendungen sicherzustellen und im Problemfall handlungsfähig zu sein.

Es braucht also ein Werkzeug, das den Workflow für lokale Deployments effektiv vereinfacht und beschleunigt, und das Werkzeug heißt Tilt.

Was ist Tilt?

Tilt ist ein freies Open-Source-Werkzeug, das Änderungen überwacht und die Build- und Deploy-Teile des Change-Build-Deploy-Workflows für lokale Deployments mit Kubernetes automatisiert. Die wesentlichen Vorteile von Tilt sind:

  • Automatische Aktualisierung: Tilt überwacht das Dateisystem selbstständig auf Änderungen und hält die Ressourcen aktuell.
  • Smart Rebuilds: Ressourcen und Images werden nur wenn notwendig neu gebaut.
  • Live-Updates: Tilt kann Dateien eines Docker Image ersetzen, ohne das Image neu zu bauen.
  • Dashboard: Tilt bringt ein einfaches Dashboard mit, das den Zustand der Anwendungskomponenten und die Logs anzeigt.
  • Geringer Setup- und Pflegeaufwand: Tilt ist einfach aufzusetzen und hat eine flache Lernkurve. Das Setup typischer Anwendungen benötigt nur wenig zusätzliche Zeit, vor allem im Vergleich mit händischen Lösungen.

Tilt eignet sich besonders für Anwendungen, die containerisiert in Kubernetes laufen, wie die im Java-Backend-Bereich verbreiteten Microservice-Architekturen. Tilt eignet sich schlecht für Function-as-a-Service (FaaS) oder Low-Code-Anwendungen. Diese Anwendungen sind mit proprietärer Technologie gegen ihre jeweilige Laufzeitumgebung gebaut und nur schlecht oder gar nicht lokal ausführbar.

Tilt ist für Kubernetes konzipiert, unterstützt neben Kubernetes aber auch Docker Compose und lokales Bare-Metal Deployment. Ebenso ist es möglich, mit Nutzer-definierten Aktionen andere Deployment-Arten umzusetzen.

Die Abläufe, die Tilt für ein Projekt ausführen soll, werden in einem sogenannten Tiltfile definiert. Tilt beobachtet das Tiltfile und definierte Abhängigkeiten wie den Anwendungsquellcode, Build-Definitionen und Kubernetes-Manifeste und löst bei Datei-Änderungen oder Nutzer-Aktionen die notwendigen Updates aus, baut die Anwendung, baut Container-Images, aktualisiert sonstige Ressourcen und rollt die Änderungen automatisch aus. Das Ergebnis ist eine kontinuierlich aktualisierte, laufende Anwendung im Cluster, siehe Abbildung 1.

Abb. 1: Tilt reagiert auf Änderungen und automatisiert Build und Deployment

Tilt bietet dazu ein lokales Web-UI, das eine Übersicht über den Zustand der definierten Ressourcen sowie Detailinformationen zu jeder Ressource bietet, siehe Abbildung 2.

Abb. 2: Das Tilt-Dashboard

Dazu gehören insbesondere die Logs sowie Links auf die per Port-Forward definierten Endpunkte. Das User Interface (UI) ist in der realen Arbeit ein nicht zu unterschätzender Vorteil im Vergleich zu anderen Werkzeugen. Es ist sofort ersichtlich, in welchem Zustand die Ressourcen sind und welche Probleme aufgetreten sind.

Um Tilt nutzen zu können, müssen lokal Docker, Kubernetes und das Kubernetes Command Line Interface (CLI) kubectl installiert sein. Dafür empfehlen sich unter anderem Docker Desktop, Rancher Desktop, MicroK8s oder kind. Die Verwendung von entfernten Clustern in der Cloud ist ebenfalls möglich und dann besonders sinnvoll, wenn Tilt mit einer zentralen containerisierten Entwicklerplattform wie GitPod oder Coder genutzt werden soll.

Tilt funktioniert genauso gut in einem Development-Container wie rein lokal. Bei der Verwendung eines Development-Containers muss lediglich der Docker Client und kubectl im Container passend konfiguriert werden.

Die Besonderheit an Tilt ist, dass sich das Werkzeug auf den Anwendungsfall „Lokale Ausführung“ konzentriert. Dieses Thema ist in der Cloud-Welt wenig besetzt. Viele IDP-Produkte (Internal Developer Plattform) bieten hier nichts, Continuous-Delivery-Werkzeuge wie Argo eignen sich nicht für den skizzierten lokalen Anwendungsfall.

Das ähnlichste Werkzeug am Markt ist Skaffold, mit dem wir ebenfalls gute Erfahrungen gemacht haben. Skaffold ist auch ein freies Open-Source-Werkzeug. Der wesentlichste Unterschied ist, dass Skaffold dazu gedacht ist, in der gesamten CI/CD-Pipeline (Continuous Integration/Deployment) eingesetzt zu werden, während sich Tilt auf die lokale Ausführung konzentriert. Weitere wichtige Unterschiede sind: Tilt ist in der Starlark-Sprache konfiguriert, Skaffold nutzt YAML. Tilt bringt ein Dashboard mit, Skaffold nicht.

DevSpace ist ein rein clientseitiges Open-Source-Entwicklerwerkzeug für Kubernetes, das eine Abstraktion über Kubernetes bietet und wie Tilt und Scaffold Live-Updates direkt im Cluster unterstützt, ohne Neubau und Neustart von Containern. Neben der DevSpace-eigenen Abstraktion können auch kubectl- oder Helm-Manifeste genutzt werden. Im Vergleich zu Tilt oder Skaffold bietet DevSpace einen schlankeren Funktionsumfang und weniger Flexibilität.

Garden ist eine DevOps-Plattform, die die Entwicklung, das Testen und Bereitstellen von Anwendungen, insbesondere in Kubernetes-Umgebungen, vereinfacht. Entwickler beschreiben ihre Infrastruktur mit Garden-YAML-Dateien, die konsistent über Umgebungen hinweg verwendet werden können. Wird Garden als DevOps-Plattform genutzt, dann ist es natürlich auch sinnvoll, Garden für lokale Lauffähigkeit einzusetzen. Garden ist aber zu komplex, um einen Einsatz nur für den lokalen Anwendungsfall zu rechtfertigen.

Okteto verfolgt wie Garden einen Environment-as-Code-Ansatz, fokussiert sich jedoch auf Kubernetes. Der wesentliche Unterschied von Okteto im Vergleich zu DevOps-Plattformen oder auch Werkzeugen wie Tilt und Skaffold ist, das Okteto die Entwicklung in einen Container in einer lokalen oder entfernten Kubernetes-Umgebung verschiebt, der mit der lokalen IDE und lokalen Entwicklerwerkzeugen integriert ist. Der Ansatz klingt interessant, doch fehlen uns direkte Erfahrungen mit Okteto.

Wie wird Tilt benutzt?

Tilt wird als Single-Executable-CLI-Programm ausgeliefert, das über einen Paketmanager wie Homebrew direkt von GitHub oder von Hand installiert werden kann.

Die CLI hat eine eingebaute Hilfe-Funktion, die per tilt help-Befehl genutzt werden kann. Für die typische Verwendung sind die Befehle tilt up zum Starten von Tilt und tilt down zum Löschen der von Tilt erzeugten Ressourcen ausreichend. Weitergehende Möglichkeiten, Diagnosability oder die Snapshot-Funktion finden sich in der Tilt-Dokumentation.

Das Tiltfile wird in Starlark geschrieben. Starlark ist ein Python-Dialekt speziell für dynamische Konfigurationen aus dem Umfeld des Build-Tools Bazel. Der Funktionsumfang ist im Vergleich zu Python reduziert, bietet aber unter anderem Funktionen, Variablen, Schleifen, Arrays und Maps. Eine Übersicht über die Starlark-Sprache findet sich auf der Bazel-Homepage.

Es gibt ein offizielles Code-Plug-in für Visual Studio sowie Syntax-Hervorhebung für IntelliJ und Textmate. Die Syntax-Hervorhebung für Python funktioniert ebenso für Starlark, nur sollte in diesem Fall die Code-Prüfung der IDE ausgeschaltet werden.

Die Konfiguration im Tiltfile erfolgt über vordefinierte Funktionen, im Folgenden werden die wichtigsten vorgestellt:

k8s_yaml: Wendet Kubernetes-Manifeste im Cluster an. Die Eingabe kann dabei ein Satz an Dateien, Verzeichnisse oder auch direkt ein YAML-String sein, der von anderen Funktionen erzeugt wurde. Diese Funktion ist etwa äquivalent zur Ausführung von kubectl apply.

k8s_resource: Erzeugt eine. Kubernetes-Ressource. Eine solche Ressource in Tilt besteht aus einem oder mehreren Kubernetes-Objekten, die bereitgestellt werden sollen. Die beiden wichtigsten Anwendungsfälle für diese Funktion sind als Anker für Abhängigkeiten in Tilt zu dienen und das Anlegen von Port-Forwards.

docker_build: Baut Docker Images, äquivalent zu docker build.

local: Führt Kommandos lokal auf dem Entwicklerrechner aus und gibt die Standardausgabe als String zurück. Diese Funktion kann unter anderem dazu benutzt werden, Kubernetes-Manifeste zu erzeugen.

local_resource: Erzeugt lokale, von Tilt überwachte Objekte. Diese Funktion kann benutzt werden, um Anwendungs-Builds in Abhängigkeit von Dateiänderungen zu steuern.

custom_build: Erzeugt Docker Images, indem ein Nutzer-definierter Befehl verwendet wird. Diese Funktion wird in den folgenden Beispielen verwendet, um die Image-Generierung von Quarkus nutzen zu können.

Tilt bringt zwei Konstrukte mit, um komplexere Projekte zu strukturieren und Erweiterungen zu ermöglichen. Die load-Funktion führt andere Tiltfiles aus und importiert Variablen und Funktionen in den Scope des aufrufenden Tiltfiles. Wird diese Funktion mit einem Pfad aufgerufen, der mit ext:// beginnt, werden Definitionen aus einer Erweiterung (Extension) geladen. Erweiterungen können dabei im offiziellen Extension-Repository von Tilt definiert sein, lokal im Dateisystem liegen oder auch aus anderen Git-Repositories referenziert werden. Die load_dynamic-Funktion funktioniert analog, außer dass sie nicht statisch, sondern zur Laufzeit ausgeführt wird, wodurch zum Beispiel konditionale Imports möglich sind.

Für professionell betriebene Anwendungen gibt es im Kubernetes-Umfeld Werkzeuge zur Strukturierung und zum Templating der Manifeste wie Kustomize oder Jsonnet. Alternativ ist auch Helm als Paket-Manager für Kubernetes weit verbreitet.

Diese Werkzeuge machen es einfacher, eine Klammer um die zusammengehörenden Ressourcen einer Anwendung zu bilden, vereinfachen die Verwaltung der Konfiguration für mehrere Umgebungen und erleichtern die Wiederverwendung Umgebungs-unabhängiger Bestandteile.

Tilt arbeitet gut mit all diesen Werkzeugen zusammen, sodass die lokale Konfiguration einfach als eine weitere Umgebung mitgepflegt werden kann. Für die drei genannten Werkzeuge schaut das wie folgt aus:

Kustomize ist ein First-Level-Citizen in Tilt. Es gibt eine eingebaute kustomize-Funktion, die dem Aufruf der Kustomize-CLI entspricht und als Eingabe für die k8s_yaml-Funktion dienen kann. Bei der Verwendung von Kustomize ist zu beachten, dass sich die von Kubectl verwendete Version von Kustomize und die Version einer eventuell ebenfalls installierten Kustomize-CLI unterscheiden können, was zu unterschiedlichem Verhalten je nach Einsatzkontext führen kann.

Helm ist ebenfalls First-Level-Citizen, analog zu Kustomize gibt es eine eingebaute helm-Funktion zum Deployment von Helm-Charts. Ich empfehle allerdings die Verwendung der Extension helm_resource, die im offiziellen Tilt Extension Repository verfügbar ist. Diese Extension bietet eine helm_repo-Funktion, um ein Helm Repository zu installieren, und eine helm_resource-Funktion, um Helm-Charts zu deployen. Die Funktionen werden analog den Helm-CLI-Befehlen verwendet und sind für Helm-Nutzer intuitiv.

Jsonnet wird nicht direkt unterstützt und dient hier auch als Stellvertreter für andere Templating-Werkzeuge, für die das Vorgehen analog erfolgt. Die local-Funktion von Tilt kann benutzt werden, um lokale Befehle auszuführen, die Standard-Ausgabe zu erfassen und mit der k8s_yaml-Funktion als Kubernetes-Ressourcen zu interpretieren. Auto-Updates bei Änderungen der Quelldateien können mit der watch_file-Funktion umgesetzt werden, die einen Reload des Tiltfile auslöst, wenn sich die referenzierten Dateien ändern.

Beispiel: Tilt für einen Standalone-Service

Das folgende Beispiel zeigt das Zusammenspiel zwischen Tilt und Java am Beispiel eines kleinen Projekts mit Quarkus. Um dem Beispiel folgen zu können, müssen Java 21, Maven, Docker, Kubernetes, kubectl sowie die Quarkus-CLI installiert sein.

Im ersten Schritt wird mit folgendem Befehl ein neues Quarkus-Projekt angelegt:

quarkus create app de.az82.demo:tilt-demo-jib --extensions=jib,rest,rest-jackson,kubernetes,smallrye-health

Das so angelegte Quarkus-Projekt enthält einen kleinen Demo-REST-Service. Jib (Java Container Builder wird zum Bauen von Docker-Images verwendet. Jib erlaubt es, direkt aus dem Java-Build heraus optimierte Docker-Container für Java-Anwendungen zu bauen, ohne ein eigenes Dockerfile zu pflegen und ohne einen Docker-Daemon verwenden zu müssen.

Die Kubernetes-Erweiterung erzeugt Kubernetes-Manifeste mit guten Voreinstellungen direkt aus dem Java-Build heraus. Die smallrye-health-Erweiterung erzeugt fertige Health- und Readiness-Checks für Kubernetes.

Im zweiten Schritt wird das Projekt mit quarkus build erstmalig gebaut, um die versprochenen Kubernetes-Manifeste in target/kubernetes zu erzeugen, denn die Tilt-Funktion k8s_yaml nimmt an, dass die Manifeste bereits zum Start von Tilt vorliegen.

Im dritten Schritt wird im Root des Projekts ein Tiltfile angelegt, siehe Listing 1.

custom_build(
	'az/tilt-demo-jib',
	'quarkus build -Dquarkus.container-image.build=true -Dquarkus.container-image.image=docker.io/$EXPECTED_REF',
	deps=['src', 'pom.xml'])
k8s_yaml(['target/kubernetes/kubernetes.yml'])
k8s_resource('tilt-demo-jib', port_forwards=[port_forward(8080, 
8080, "http")])
Listing 1: Tiltfile für ein einfaches Quarkus-Projekt

Das Tiltfile besteht aus drei Schritten:

custom_build(...): Quarkus wird genutzt, um in einem Rutsch die Anwendung zu bauen, Unittests auszuführen und mit Jib das Docker Image zu bauen. Dieser Schritt wird automatisch ausgeführt, wenn sich etwas an der Anwendung oder am Anwendungs-Build ändert.

k8s_yaml(...): Die von Quarkus generierten Kubernetes-Manifeste werden im Cluster angewendet, um einen Service und ein Deployment für unsere Anwendung zu erzeugen.

k8s_resource(...): Tilt wird informiert, dass es eine Kubernetes-Ressource mit dem Namen tilt-demo-jib gibt, und angewiesen, einen Port-Forward auf den Kubernetes-Service desselben Namens anzulegen.

Im vierten Schritt wird Tilt mit dem Befehl tilt up gestartet. Wenn alles richtig gemacht wurde, sollte die containerisierte Anwendung im Kubernetes-Cluster unter http://localhost:8080/hello erreichbar sein. Wenn nicht, hilft bei Fehlern in Build und Deployment das Tilt-Dashboard, das mit der Leertaste durch die Tilt-CLI geöffnet werden kann.

Wird etwas am Code geändert, so starten die Java- und Docker-Image-Builds automatisch und nach wenigen Sekunden läuft wieder eine aktuelle Anwendung im Cluster. Das Dashboard und der einfache Zugriff auf die Logs helfen ungemein bei der Fehlersuche. Auf fehlgeschlagene Unittests sind sofort ersichtlich.

Dieses Beispiel zeigt nicht nur eindrucksvoll, wie gut die Developer Experience von Quarkus ist, sondern vor allem wie einfach Tilt in ein Projekt eingeführt werden kann.

Zu erwähnen ist allerdings, dass Quarkus eine unschöne Eigenheit mitbringt: Quarkus unterstützt nur JAR-Ressourcen im erzeugten Docker-Image. Das bedeutet, dass die Anwendung erst als JAR paketiert werden muss, anstatt die Class-Dateien direkt verwenden zu können. Das führt zum einen zu unnötig verlängerten Build- und Startzeiten, zum anderen verhindert es, dass einzelne Class-Dateien im Container ausgetauscht werden können. Tilt kann damit bei Live-Updates mit Quarkus immer nur das ganze JAR tauschen. Immerhin werden die Libraries in einen anderen Layer des Containers gepackt, sodass auch die meisten Quarkus-Projekte in Tilt mit guter Performance aktualisiert werden. Andere Frameworks wie Spring Boot haben diese Einschränkung nicht. Die Tilt-Dokumentation enthält ein Beispiel, wie das Live-Update einzelner Klassen umgesetzt werden kann.

Beispiel: Tilt für einen Service mit Datenbank

Der Mehrwert von Tilt wird besonders für nicht triviale Anwendungen deutlich, die aus mehreren Betriebskomponenten bestehen, etwa, wenn Datenbank, Message Broker oder Object Stores dazukommen oder wenn die Anwendung selbst aus mehreren Services besteht.

Das folgende Beispiel definiert eine typische Microservice-Anwendung mit einem Technologie-Stack, wie er im Java-Backend weit verbreitet ist: Eine Quarkus-Anwendung mit einer Postgres-Datenbank. Kustomize wird zum Erzeugen der Kubernetes-Manifeste genutzt.

Der Startpunkt ist wie im ersten Beispiel eine neue Quarkus-App, diesmal mit Postgres-Client sowie Hibernate und Panache für einfaches objekt-relationales Mapping:

quarkus create app de.az82.demo:tilt-demo-db --extensions=jib,rest,rest-jackson,kubernetes,smallrye-health,quarkus-jdbc-postgresql,quarkus-hibernate-orm-panache

Im nächsten Schritt wird die Anwendung um etwas von Postgres abhängige Funktionalität erweitert, siehe Listing 2.

// src/main/java/de/az82/demo/Greeting.java
@Entity
public class Greeting extends PanacheEntity {
	public String text;
}

// src/main/java/de/az82/demo/GreetingResource.java
@Path("/hello")
public class GreetingResource {
	@GET
	@Produces(MediaType.TEXT_PLAIN)
	public String hello() {
    	    List<Greeting> greetings = Greeting.listAll();
    	    return greetings.get(ThreadLocalRandom.current().nextInt(greetings.size())).text;
	}
}

// src/main/resources/application.properties
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT:5432}/${POSTGRES_DB}
quarkus.datasource.username=${POSTGRES_USER}
quarkus.datasource.password=${POSTGRES_PASSWORD}

// src/main/resources/import.sql
INSERT INTO Greeting(name) VALUES('Grias Di!');
INSERT INTO Greeting(name) VALUES('Servus!');
INSERT INTO Greeting(name) VALUES('Guten Tag!');
INSERT INTO Greeting(name) VALUES('Moin Moin!');
Listing 2: Anpassungen an der generierten Quarkus-Anwendung

Die GreetingResource wird so erweitert, dass sie zufällige Grußtexte aus der Datenbank anzeigt. Ein Greeting-Entity stellt die Brücke zur Datenbank dar, Panache- und Hibernate-Magie kümmert sich um den Rest. Der generierte Test-Case muss dann angepasst oder entfernt werden, da nun eine Datenbankverbindung benötigt wird.

Dann wird in src/main/resources/application.properties die Datenbankverbindung konfiguriert, natürlich so, dass die Umgebungs-abhängigen Verbindungsparameter von außen geändert werden können. Die letzte Anpassung an der Anwendung ist, einige Datensätze für Test und Entwicklung anzulegen. Das geht in Quarkus einfach über die Datei src/main/resources/import.sql.

Nun müssen die Kubernetes-Manifeste angelegt werden. Die Verzeichnisstruktur sollte den Konventionen von Kustomize folgen. Das Basis-Verzeichnis base/ enthält wiederverwendbare, umgebungsunabhängige Definitionen. Die Overlay-Verzeichnisse enthalten Anpassungen für die jeweiligen Umgebungen, in diesem Beispiel nur ein Verzeichnis overlays/local/ für eine lokale Umgebung.

In jedem Verzeichnis muss zusätzlich eine kustomization.yaml-Datei enthalten sein, die die Ressourcen und Anpassungen aufzählt. Für Details verweise ich auf die Kustomize-Dokumentation.

Für Postgres-Test- und Entwicklungsinstanzen haben wir gute Erfahrungen mit dem offiziellen Postgres Docker Image gemacht. Um dieses nutzen zu können, braucht es mehrere Kubernetes-Ressourcen: ein Deployment, einen Service sowie ein Persistent Volume (PV) und einen Persistent Volume Claim (PVC). Da die Kubernetes-YAMLs sehr ausführlich sind, verweise ich als Vorlage auf das Begleit-Repository für diesen Artikel auf GitHub [1]. Da die Datenbank-Manifeste wiederverwendbar sein sollen, gehören sie in das Basis-Verzeichnis.

Alternativ könnte auch das Helm-Chart von Bitnami verwendet werden. Damit wären die eigenen Kubernetes-Manifeste überflüssig, dann empfiehlt es sich allerdings, auch die Anwendung mit Helm zu paketieren.

Für die Anwendungsressourcen werden der Einfachheit halber die von Quarkus erzeugten Manifeste genutzt. Dazu werden die Manifeste in k8s/base/kustomization.yaml in der resources-Sektion referenziert. Dieses Vorgehen macht die Aktivierung eines bestimmten Flags für Kustomize notwendig, da Kustomize aus Sicherheitsgründen normalerweise nur die Verwendung von Dateien in Pfaden unterhalb des Elternverzeichnisses einer kustomize.yaml-Datei erlaubt.

Das lokale Overlay wird in der Datei k8s/overlays/local/kustomize.yaml definiert (siehe Listing 3).

# k8s/overlays/local/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base
- namespace.yaml

namespace: tilt-demo-db

secretGenerator:
- literals:
  - POSTGRES_DB=default
  - POSTGRES_USER=default
  - POSTGRES_PASSWORD=default
  name: db-secret

patches:
- path: app-patch.yaml

# k8s/overlays/local/app-patch.yaml.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tilt-demo-db
spec:
  template:
	spec:
  	containers:
    	- name: tilt-demo-db
      	env:
        	- name: POSTGRES_HOST
          	value: db
        	- name: POSTGRES_PORT
          	value: "5432"
      	envFrom:
        	- secretRef:
            	name: db-secret
Listing 3: Das lokale Kustomize-Overlay für eine Anwendung mit Postgres

In dieser Datei sind ein eigener Namespace für die Ressourcen, ein umgebungsspezifisches Secret für Postgres und die Anpassungen an der Anwendung für das lokale Deployment enthalten. Mithilfe eines Kustomize-Patches in k8s/overlays/local/app-patch.yaml wird die Anwendung mit den Umgebungs-abhängigen Zugangsdaten zur Datenbank versorgt.

Ein eigener Namespace isoliert die Ressourcen im Cluster und hilft bei der manuellen Pflege. Die Namespace-Definition kann mit kubectl erzeugt werden:

kubectl create namespace tilt-demo-db --dry-run=client -o yaml > k8s/overlays/local/namespace.yaml

Das Beispiel braucht nun noch ein Tiltfile, das alles verknüpft (siehe Listing 4).

custom_build(
	'az/tilt-demo-db',
	'quarkus build -
Dquarkus.container-image.build=true -Dquarkus.container-image.image=docker.io/$EXPECTED_REF',
	deps=['src', 'pom.xml'])
k8s_yaml(kustomize('./k8s/overlays/local', flags=['--load-restrictor', 'LoadRestrictionsNone']))
k8s_resource('tilt-demo-db', port_forwards=[port_forward(8080, 8080, "http")])
Listing 4: Tiltfile für eine Quarkus-Anwendung mit Postgres

Wie im ersten Beispiel wird die Anwendung mit custom_build gebaut und die Kubernetes-Manifeste werden mit k8s_resource im Cluster angewendet. Der Unterschied ist, dass die kustomize-Funktion zum Templating der Manifeste verwendet wird. Das --load-restrictor='LoadRestrictionsNone'-Flag erlaubt Kustomize, Manifeste aus beliebigen Pfaden zu laden. Wer das nicht möchte, kann genauso gut anstatt der generierten Manifeste selbst definierte verwenden.

Wird die Anwendung mit tilt up gestartet, wird unter http://localhost:8080/hello bei jedem erneuten Laden der Seite ein neuer Gruß aus der Datenbank angezeigt.

Dieses Beispiel zeigt, wie einfach es ist, mit Tilt zusätzliche Komponenten wie eine Datenbank anzubinden, solange sich die Komponenten in einem Container betreiben lassen. Noch einfacher ist es, wenn im Rahmen einer bestehenden Anwendung bereits containerisierte Komponenten zur Verfügung stehen, die dann nur noch für die lokale Umgebung konfiguriert werden müssen.

Testen mit Tilt

Die Beispiele haben gezeigt, wie bei Tilt die normalen Anwendungs-Builds genutzt werden können. Laufen die Unittests wie in den Beispielen im Build mit, ist sichergestellt, dass die Tests erfolgreich waren, bevor die Anwendung zur Ausführung kommt.

Ein alternatives Setup ist, die Unittests unabhängig vom Build als separate lokale Ressource auszuführen und den normale Anwendungs-Build ohne Tests laufen zu lassen. Das hat im Vergleich zum ersten Vorgehen den Vorteil, dass Build und Deployment deutlich schneller gehen, auch bei fehlschlagenden Tests eine laufende Anwendung für Analysen bereitsteht und dennoch schnelles Feedback zum Status der Tests möglich ist. Dieses Vorgehen empfiehlt sich besonders bei umfangreichen Unittests.

Um Tests im Verbund auszuführen, gibt es zwei prinzipielle Möglichkeiten:

  • Ausführung der Tests innerhalb des Clusters,
  • lokale Ausführung der Tests, die per Netzwerk die Anwendung im Cluster testen.

Die Ausführung der Tests im Cluster empfiehlt sich, um Tests zu nutzen, die in einer Entwicklungs- oder Integrationsumgebung laufen sollen. Diese Tests werden wie andere Kubernetes-Ressourcen auch mit k8s_yaml ausgerollt. In diesem Fall muss mitbedacht werden, wie Entwickler komfortabel an die Testergebnisse kommen, etwa indem die Testergebnisse auf einem Dashboard zugänglich gemacht werden.

Die lokale Ausführung der Tests entspricht dem Fall, dass Tests in einem CI- oder CD-Werkzeug gegen entfernte Umgebungen ausgeführt werden.

Bei Tilt sind Tests keine First-Level-Citizens, es gibt keine speziellen Testkonstrukte. Lokal laufende Tests können einfach über eine local_resource-Definition ausgeführt werden.

Quarkus erlaubt einen interessanten Misch-Modus. Tests können mit @QuarkusIntegrationTest annotiert werden. Für solche Tests führt Quarkus die Anwendung selbst lokal auf dem Entwicklerrechner aus. Damit das mit Tilt funktioniert, müssen benötigte Komponenten im Cluster wie eine Datenbank mit k8s_resource-Definitionen und Port Forwards exponiert werden. Die Anwendung wird dann, wie im Beispiel, über Umgebungsvariablen mit den Zugriffsdaten für die benötigten Komponenten versorgt.

Tilt bietet mit dem tilt ci-Befehl eine Möglichkeit, Tilt in einer CI-Pipeline auszuführen. Das kann etwa dazu genutzt werden, Verbundtests zu orchestrieren. Das ist besonders effektiv, wenn die CI-Pipeline in der Lage ist, kurzlebige Cluster genau für die Ausführung der Tests zu instanziieren. Das Kubernetes-Projekt selbst verwendet kind, um lokale Cluster zu erzeugen. Diese Funktionalität kann auch dazu genutzt werden, das Tiltfile für lokale Entwicklung im CI-Build abzusichern.

Die Verwendung von Tilt zum kontinuierlichen lokalen Testen ist keine Raketenwissenschaft. Tilt hat den großen Vorteil, dass man wenige Momente nach einer Änderung entweder eine getestete und laufende Anwendung hat – oder eine gute Indikation, was schiefgegangen ist.

Fazit

Tilt unterstützt Shift-Left, indem es Entwicklern ermöglicht, schneller und einfacher auf Kubernetes basierte Anwendungen lokal zu testen und zu entwickeln. Durch die Automatisierung der Change-Build-Deploy-Schleife und die intuitive Web-Oberfläche steigert Tilt die Produktivität, minimiert Fehlerquellen und verkürzt die Zeit bis zum Feedback. Besonders für Microservice-Architekturen im Java-Backend-Bereich ist Tilt ein effektives und flexibles Werkzeug, das sich sowohl für einfache als auch komplexe Setups eignet.

Dabei gilt unabhängig von den konkreten Werkzeugen: Lokale Ausführbarkeit und lokale Testbarkeit lohnen sich.

Online-Ressource

[1] Beispiel-Repository zu diesem Artikel, siehe: github.com/az82/tilt-demo

. . .

Author Image

Andreas Zitzelsberger

Technischer Geschäftsbereichsleiter
Zu Inhalten

Andreas Zitzelsberger ist Entwickler und Architekt aus Leidenschaft und arbeitet als Technischer Geschäftsbereichsleiter bei QAware. Seine Schwerpunkte sind Cloud, Cloud-native und Modernisierung.


Artikel teilen