Docker und Java: Fortgeschrittene Containerisierung von Anwendungen
Die Containerisierung von Java-Anwendungen mit Docker ist seit langem ein Industriestandard. Allerdings sind grundlegende Dockerfiles und einfache docker run-Befehle nur die Spitze des Eisbergs. Auf fortgeschrittenem Niveau stehen Entwickler vor Aufgaben wie der Optimierung der Image-Größe, der Feinabstimmung der JVM innerhalb des Containers, der Organisation von Multi-Stage-Builds und der Orchestrierung von Microservices. In diesem Artikel behandeln wir die wichtigsten Techniken, die Ihnen helfen, die Containerisierung von Java auf ein professionelles Niveau zu heben.
1. Multi-Stage-Builds: Minimales Image für die Produktion
Eines der Hauptprobleme bei der Containerisierung von Java ist die Größe des resultierenden Images. Das Basis-Image openjdk:17-jdk wiegt etwa 400 MB, was für die Laufzeitumgebung überdimensioniert ist. Die Lösung ist ein Multi-Stage-Build.
Die erste Stufe (Build) verwendet das vollständige JDK und Build-Tools (Maven/Gradle). Die zweite Stufe (Runtime) verwendet ein leichtes JRE-Image und kopiert nur das erstellte JAR/WAR.
# Stufe 1: Anwendung bauenFROM maven:3.8.5-openjdk-17 AS builderWORKDIR /appCOPY pom.xml .COPY src ./srcRUN mvn clean package -DskipTests
# Stufe 2: Minimales Runtime-ImageFROM openjdk:17-jre-slimWORKDIR /app# Nur das erstellte Artefakt kopierenCOPY --from=builder /app/target/myapp-*.jar app.jarEXPOSE 8080CMD ["java", "-jar", "app.jar"]Vorteile:
- Das endgültige Image wiegt ~180 MB statt 400+ MB
- Die Angriffsfläche wird verringert (kein Compiler, keine überflüssigen Hilfsprogramme)
- Die Zeit für Deployment und Image-Download wird verkürzt
2. JVM-Konfiguration für den Betrieb im Container
Lange Zeit passte sich die JVM nur schlecht an die Einschränkungen von Containern (cgroups) an. Ab Java 10 gibt es Flags zur expliziten Angabe von Ressourcen, und ab Java 17+ ist das Verhalten vorhersehbarer. Auf fortgeschrittenem Niveau ist es jedoch wichtig, Speicher und CPU manuell zu kontrollieren.
Wichtige JVM-Parameter für Container:
-XX:MaxRAMPercentage=75.0— weist 75% des Containerspeichers dem Heap zu (anstelle von festem -Xmx)-XX:+UseContainerSupport— aktiviert die Erkennung von Container-Beschränkungen (standardmäßig aktiviert ab Java 10+)-XX:ActiveProcessorCount=2— gibt explizit die Anzahl der verfügbaren Prozessoren an-XX:+ExitOnOutOfMemoryError— beendet den Prozess bei einem OutOfMemoryError (für schnellen Container-Neustart)
# Beispiel Dockerfile mit optimierten JVM-FlagsFROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY target/app.jar app.jar
# JVM für Container konfigurierenENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \ -XX:+UseContainerSupport \ -XX:+ExitOnOutOfMemoryError \ -XX:+PrintGCDetails"
EXPOSE 8080CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]Wichtig: Verwenden Sie eclipse-temurin oder amazon-corretto – dies sind offizielle, gut gewartete Images mit einer transparenten Update-Politik.
3. Docker Compose für Microservice-Architekturen
In realen Projekten existiert eine Java-Anwendung selten isoliert. Üblich ist eine Kombination aus: Spring Boot Backend + PostgreSQL + Redis + Kafka. Docker Compose ermöglicht es, die gesamte Infrastruktur in einer einzigen YAML-Datei zu beschreiben.
version: '3.8'
services: postgres: image: postgres:15-alpine environment: POSTGRES_DB: myapp POSTGRES_USER: user POSTGRES_PASSWORD: secret volumes: - pgdata:/var/lib/postgresql/data networks: - backend
redis: image: redis:7-alpine ports: - "6379:6379" networks: - backend
app: build: context: . dockerfile: Dockerfile ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:postgresql: