Docker und Kotlin: Containerisierung von Anwendungen auf fortgeschrittenem Niveau

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Docker und Kotlin: Containerisierung von Anwendungen auf fortgeschrittenem Niveau



Containerisierung ist zu einem integralen Bestandteil der modernen Softwareentwicklung geworden. Wenn Sie bereits die grundlegenden Prinzipien von Docker beherrschen und einige einfache Images mit Kotlin erstellt haben, ist es an der Zeit, auf die nächste Stufe zu gehen. In diesem Artikel behandeln wir fortgeschrittene Techniken: Multi-Stage-Builds, Optimierung der endgültigen Image-Größe, Arbeit mit gängigen Frameworks (Ktor, Spring Boot) und effizientes Koroutinen-Management in einer containerisierten Umgebung.



1. Multi-Stage-Builds



Eines der Hauptprobleme bei der Containerisierung von JVM-Anwendungen ist die Image-Größe. Ein Basis-JDK-Image kann Hunderte von Megabyte wiegen. Multi-Stage-Builds ermöglichen es, die Kompilierungs- und Ausführungsphasen zu trennen, sodass im endgültigen Image nur die JRE und die erstellte JAR-Datei verbleiben.



Betrachten wir ein Beispiel für eine Kotlin-Anwendung, die mit Gradle erstellt wird:



# Build-StageFROM gradle:7.6-jdk17 AS builderWORKDIR /appCOPY build.gradle.kts settings.gradle.kts ./COPY src ./srcRUN gradle build --no-daemon -x test

# Runtime-StageFROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY --from=builder /app/build/libs/*.jar app.jarEXPOSE 8080CMD ["java", "-jar", "app.jar"]


Warum ist das effizient?

  • In der Build-Stage werden das vollständige JDK und Gradle verwendet, gelangen aber nicht in das endgültige Image.
  • Das endgültige Image enthält nur die JRE (Java Runtime Environment), was die Größe um das 3- bis 5-fache reduziert.
  • Alpine Linux reduziert das Volumen zusätzlich auf etwa 150-200 MB statt 600+ MB.


2. Optimierung von Layern und Caching



Docker cached Layer, aber nur, wenn sie sich nicht geändert haben. Um wiederholte Builds zu beschleunigen, trennen Sie Abhängigkeiten und Quellcode:



FROM gradle:7.6-jdk17 AS builderWORKDIR /app

# Zuerst nur die Abhängigkeitsdateien kopierenCOPY build.gradle.kts settings.gradle.kts ./RUN gradle dependencies --no-daemon

# Dann den Quellcode (ändert sich häufiger)COPY src ./srcRUN gradle build --no-daemon -x test


Dieser Ansatz stellt sicher, dass der Layer mit den Abhängigkeiten nur neu erstellt wird, wenn sich build.gradle.kts ändert. Andernfalls verwendet Docker den Cache und spart bei jeder Iteration Minuten.



Tipp: Für Kotlin-Projekte mit vielen Abhängigkeiten (z. B. Spring Boot) ist dieser Trick besonders relevant – das Laden des Gradle-Caches kann bis zu 2-3 Minuten dauern.



3. Containerisierung von Ktor-Anwendungen



Ktor ist ein leichtes asynchrones Framework für Kotlin, das sich ideal für Microservices eignet. Die Containerisierung einer Ktor-Anwendung erfordert minimale Konfiguration, es gibt jedoch Nuancen in Bezug auf Koroutinen.



Beispiel Dockerfile für Ktor:



FROM gradle:7.6-jdk17 AS builderWORKDIR /appCOPY build.gradle.kts settings.gradle.kts ./RUN gradle dependencies --no-daemonCOPY src ./srcRUN gradle build --no-daemon -x test

FROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY --from=builder /app/build/libs/ktor-app-*.jar app.jarEXPOSE 8080

# Wichtig: JVM-Konfiguration für KoroutinenCMD ["java", "-XX:+UseZGC", "-Xms256m", "-Xmx512m", "-jar", "app.jar"]


Wichtige Punkte:

  • Verwenden Sie ZGC (Z Garbage Collector) – er minimiert Pausen, was für asynchrone Anwendungen mit vielen Koroutinen entscheidend ist.
  • Begrenzen Sie den Speicher über -Xms und -Xmx. In Containern hilft dies, OOM-Fehler zu vermeiden.
  • Stellen Sie sicher, dass der Ktor-Server den Hauptthread nicht blockiert – Koroutinen sollten über Dispatchers.IO oder einen benutzerdefinierten Pool konfiguriert werden.


Beispiel für die Konfiguration von Ktor für den Betrieb in einem Container:



fun main() {    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {        routing {            get("/") {                

Blogs

Buchempfehlungen