Freigeben über


Einführung in Container für Java-Anwendungen

Container bieten eine konsistente, portable Umgebung für Ihre Java-Anwendungen in allen Entwicklungs-, Test- und Produktionsphasen. In diesem Artikel werden Containerisierungskonzepte für Java-Anwendungen vorgestellt und Sie werden durch das Erstellen, Debuggen, Optimieren und Bereitstellen von containerisierten Java-Anwendungen in Azure Container Apps geführt.

In diesem Artikel lernen Sie grundlegende Containerisierungskonzepte für Java-Entwickler und die folgenden Fähigkeiten kennen:

  • Einrichten Ihrer Entwicklungsumgebung für containerisierte Java-Anwendungen.
  • Erstellen von Dockerfiles, die für Java-Workloads optimiert sind.
  • Konfigurieren lokaler Entwicklungsworkflows mit Containern.
  • Debuggen von containerisierten Java-Anwendungen.
  • Optimierung von Java-Containern für die Produktion.
  • Bereitstellen Ihrer containerisierten Java-Anwendungen in Azure Container Apps.

Durch die Containerisierung Ihrer Java-Anwendungen erhalten Sie konsistente Umgebungen, eine vereinfachte Bereitstellung, eine effiziente Ressourcennutzung und eine verbesserte Skalierbarkeit.

Container für Java-Anwendungen

Container verpacken Anwendungen mit ihren Abhängigkeiten, um die Konsistenz in allen Umgebungen zu gewährleisten. Für Java-Entwickler bedeutet dies, die Anwendung, ihre Abhängigkeiten, das Java Runtime Environment/Java Development Kit (JRE/JDK) und die Konfigurationsdateien in einer einzigen, portablen Einheit zu bündeln.

Die Containerisierung hat entscheidende Vorteile gegenüber der Virtualisierung und eignet sich daher ideal für die Cloud-Entwicklung. Im Gegensatz zu einer virtuellen Maschine wird ein Container auf dem Host-Betriebssystem-Kernel eines Servers ausgeführt. Dies ist vorteilhaft für Java-Anwendungen, die bereits in der Java Virtual Machine (JVM) laufen. Die Containerisierung von Java-Anwendungen führt zu einem minimalen Mehraufwand und bietet erhebliche Vorteile bei der Bereitstellung.

Das Container-Ökosystem umfasst die folgenden Schlüsselkomponenten:

  • Bilder - die Blaupausen.
  • Container – ausgeführte Instanzen.
  • Registrierungen - wo Bilder gespeichert werden.
  • Orchestratoren – Systeme, die Container in großem Umfang verwalten.

Docker ist die beliebteste Containerisierungsplattform und wird im Azure-Ökosystem durch Azure Container Apps gut unterstützt.

Einrichten Ihrer Entwicklungsumgebung

In diesem Abschnitt erfahren Sie, wie Sie die erforderlichen Tools installieren und Ihre Entwicklungsumgebung konfigurieren, um containerisierte Java-Anwendungen zu erstellen, auszuführen und zu debuggen.

Installieren erforderlicher Tools

Zum Containerisieren von Java-Anwendungen müssen die folgenden Tools auf Ihrem Entwicklungscomputer installiert sein:

Überprüfen Sie Ihre Installation mit den folgenden Befehlen:

docker --version
docker compose version

Konfigurieren von Visual Studio Code für die Containerentwicklung

Konfigurieren Sie für die Java-Entwicklung in Containern Visual Studio Code, indem Sie das Java Extension Pack installieren und Ihr JDK einrichten. Mit der Dev Containers-Erweiterung können Sie einen beliebigen Ordner in einem Container öffnen und den vollständigen Funktionsumfang von Visual Studio Code in diesem Container verwenden.

Damit Visual Studio Code automatisch einen Entwicklungscontainer erstellt und eine Verbindung mit ihm herstellt, erstellen Sie in Ihrem Projekt eine devcontainer.json-Datei .devcontainer/ .

Die folgende Beispielkonfiguration definiert beispielsweise einen Java-Build:

{
    "name": "Java Development",
    "image": "mcr.microsoft.com/devcontainers/java:21",
    "customizations": {
        "vscode": {
            "extensions": [
                "vscjava.vscode-java-pack",
                "ms-azuretools.vscode-docker"
            ]
        }
    },
    "forwardPorts": [8080, 5005],
    "remoteUser": "vscode"
}

Diese Konfiguration verwendet das Java-Entwicklungscontainerimage von Microsoft, fügt wichtige Erweiterungen hinzu und leitet sowohl den Anwendungsport 8080als auch den Debugport weiter. 5005

Dockerfile erstellen

Eine Dockerfile enthält Anweisungen zum Erstellen eines Docker-Images. Für Java-Anwendungen enthält das Dockerfile in der Regel die folgenden Komponenten:

  • Ein Basisimage mit dem JDK oder JRE.
  • Anweisungen zum Kopieren von Anwendungsdateien.
  • Befehle zum Festlegen von Umgebungsvariablen.
  • Konfigurationen von Einstiegspunkten.

Wählen Sie ein Basisimage aus

Die Wahl des richtigen Basisbildes ist entscheidend. Ziehen Sie diese Optionen in Betracht:

BESCHREIBUNG Name Bemerkungen
Microsoft Java Entwicklung Bild mcr.microsoft.com/java/jdk:21-zulu-ubuntu Vollständiges JDK und optimiert für Azure
Microsoft Java Produktionsbild mcr.microsoft.com/java/jre:21-zulu-ubuntu Nur Runtime und optimiert für Azure
Offizielles OpenJDK-Entwicklungsimage openjdk:21-jdk Vollständiges JDK
Offizielles OpenJDK-Produktionsimage openjdk:21-jre Nur Laufzeit

Verwenden Sie für Entwicklungsumgebungen ein vollständiges JDK-Image. Verwenden Sie für die Produktion ein JRE- oder distributionsloses Image, um die Größe und Angriffsfläche Ihrer Anwendung zu minimieren.

Die Microsoft Java-Images verfügen über Azure-spezifische Optimierungen und werden regelmäßig mit Sicherheitspatches aktualisiert, sodass sie sich ideal für Anwendungen eignen, die auf Azure Container Apps abzielen.

Grundlegende Dockerfile-Beispiele

Das folgende Beispiel zeigt eine einfache Dockerfile für eine Java-Anwendung:

FROM mcr.microsoft.com/java/jdk:21-zulu-ubuntu
WORKDIR /app
COPY target/myapp.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Für Spring Boot-Anwendungen können Sie Ihre Dockerfile auf der folgenden Basis einrichten:

FROM mcr.microsoft.com/java/jdk:21-zulu-ubuntu
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Dspring.profiles.active=docker", "-jar", "app.jar"]

Verwenden Sie für Produktionsbereitstellungen das im folgenden Beispiel gezeigte JRE-Image, um die Größe Ihrer Anwendung zu reduzieren und die Angriffsfläche zu minimieren:

FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENV JAVA_OPTS="-Dserver.port=8080"
ENTRYPOINT ["java", ${JAVA_OPTS}, "-jar", "app.jar"]

Lokale Entwicklung mit Containern

Container sollen in verschiedenen Kontexten ausgeführt werden. In diesem Abschnitt lernen Sie einen lokalen Entwicklungsablauf für die Verwendung mit Containern kennen.

Verwenden von Docker Compose für Anwendungen mit mehreren Containern

Die meisten Java-Anwendungen interagieren mit Datenbanken, Caches oder anderen Diensten. Docker Compose hilft Ihnen, Anwendungen mit mehreren Containern mithilfe einer einfachen YAML-Konfigurationsdatei zu definieren und zu orchestrieren.

Was ist Docker Compose?

Docker Compose ist ein Tool, mit dem Sie die folgenden Aufgaben ausführen können:

  • Definieren Sie Anwendungen mit mehreren Containern in einer einzigen Datei.
  • Verwalten Sie den Anwendungslebenszyklus, einschließlich Start, Stopp und Neuerstellung.
  • Pflegen Sie isolierte Umgebungen.
  • Erstellen Sie Netzwerke für die Servicekommunikation.
  • Beibehalten von Daten mithilfe von Volumes.

Beispiel: Java-Anwendung mit Datenbank

Die folgende compose.yml Datei konfiguriert eine Java-Anwendung mit einer PostgreSQL-Datenbank:

version: '3.8'
services:
  app:
    build: .                              # Build from Dockerfile in current directory
    ports:
      - "8080:8080"                       # Map HTTP port
      - "5005:5005"                       # Map debug port
    environment:
      - SPRING_PROFILES_ACTIVE=dev
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/myapp
    volumes:
      - ./target:/app/target              # Mount target directory for hot reloads
    depends_on:
      - db                                # Ensure database starts first
  
  db:
    image: postgres:13
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=myapp
    ports:
      - "5432:5432"                       # Expose PostgreSQL port
    volumes:
      - postgres-data:/var/lib/postgresql/data  # Persist database data

volumes:
  postgres-data:                          # Named volume for database persistence

Diese Datei weist die folgenden Merkmale auf:

  • Services können sich gegenseitig über ihren Namen referenzieren, db z. B. in der JDBC-URL.
  • Docker Compose erstellt automatisch ein Netzwerk für die Dienste.
  • Die Java-Anwendung wartet auf den Start der Datenbank, da depends_on.
  • Die Datenbankdaten bleiben über Neustarts hinweg mit einem benannten Volume erhalten.

Allgemeine Docker Compose-Befehle

Nachdem Sie die compose.yml Datei erstellt haben, verwalten Sie die Anwendung mit den folgenden Befehlen:

# Build images without starting containers
docker compose build

# Start all services defined in compose.yml
docker compose up

# Start in detached mode (run in background)
docker compose up -d

# View running containers managed by compose
docker compose ps

# View logs from all containers
docker compose logs

# View logs from a specific service
docker compose logs app

# Stop all services
docker compose down

# Stop and remove volumes (useful for database resets)
docker compose down -v

Entwicklungsworkflow

Ein typischer Java-Entwicklungsworkflow mit Docker Compose umfasst die folgenden Schritte:

  1. Erstellen Sie die compose.yml Datei und die Dockerfile.
  2. Ausführen docker compose up , um alle Dienste zu starten.
  3. Nehmen Sie Änderungen an Ihrem Java-Code vor.
  4. Erstellen Sie Ihre Anwendung neu. Abhängig von der Konfiguration müssen Sie Ihre Container möglicherweise neu starten.
  5. Testen Sie die Änderungen in der containerisierten Umgebung.
  6. Wenn Sie fertig sind, führen Sie docker compose down.

Ausführen einzelner Container mit Docker

In einfacheren Szenarien, in denen Sie nicht mehrere miteinander verbundene Dienste benötigen, können Sie den docker run Befehl verwenden, um einzelne Container zu starten.

Typisch für Java-Anwendungen sind folgende Docker-Befehle:

# Run a Java application JAR directly
docker run -p 8080:8080 myapp:latest

# Run with environment variables
docker run -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=prod" myapp:latest

# Run in detached mode (background)
docker run -d -p 8080:8080 myapp:latest

# Run with a name for easy reference
docker run -d -p 8080:8080 --name my-java-app myapp:latest

# Run with volume mount for persistent data
docker run -p 8080:8080 -v ./data:/app/data myapp:latest

Debuggen von containerisierten Anwendungen

Das Debuggen von containerisierten Java-Anwendungen ist manchmal eine Herausforderung, da Ihr Code in einer isolierten Umgebung innerhalb des Containers ausgeführt wird.

Standardmäßige Debugansätze gelten nicht immer direkt, aber mit der richtigen Konfiguration können Sie eine Remotedebugverbindung mit Ihrer Anwendung herstellen. In diesem Abschnitt erfahren Sie, wie Sie Ihre Container für das Debuggen konfigurieren, Ihre Entwicklungstools mit ausgeführten Containern verbinden und häufige Probleme im Zusammenhang mit Containern beheben.

Einrichten des Remotedebuggens

Zum Debuggen containerisierter Java-Anwendungen müssen Sie einen Debug-Port verfügbar machen und Ihre IDE so konfigurieren, dass eine Verbindung zu diesem Port hergestellt wird. Sie können diese Aufgaben ausführen, indem Sie die folgenden Schritte ausführen:

  1. Um das Debuggen zu aktivieren, ändern Sie Ihre Dockerfile so, dass sie den folgenden Inhalt enthält:

    Hinweis

    Sie können stattdessen Ihren Containerstartbefehl ändern.

    FROM mcr.microsoft.com/java/jdk:21-zulu-ubuntu
    WORKDIR /app
    COPY target/*.jar app.jar
    EXPOSE 8080 5005
    ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]
    
  2. Konfigurieren Sie die launch.json Datei von Visual Studio Code so, dass eine Verbindung mit dem Debugport hergestellt wird, wie im folgenden Beispiel gezeigt:

    {
      "version": "0.2.0",
      "configurations": [
        {
          "type": "java",
          "name": "Debug in Container",
          "request": "attach",
          "hostName": "localhost",
          "port": 5005
        }
      ]
    }
    
  3. Starten Sie Ihren Container mit einem Port 5005 , der Ihrem Host zugeordnet ist, und starten Sie dann den Debugger in Visual Studio Code.

Behandeln von Containerproblemen

Wenn sich Container nicht wie erwartet verhalten, können Sie die Protokolle Ihrer App überprüfen, um das Problem zu untersuchen.

Verwenden Sie die folgenden Befehle, um eine Problembehandlung für Ihre Anwendung durchzuführen. Bevor Sie diese Befehle ausführen, stellen Sie sicher, dass Sie die Platzhalter (<...>) durch Ihre eigenen Werte ersetzen.

# View logs
docker logs <CONTAINER_ID>

# Follow logs in real-time
docker logs -f <CONTAINER_ID>

# Inspect container details
docker inspect <CONTAINER_ID>

# Get a shell in the container
docker exec -it <CONTAINER_ID> bash

Aktivieren Sie bei Java-spezifischen Problemen die JVM-Flags für eine bessere Diagnose, wie im folgenden Beispiel gezeigt:

ENTRYPOINT ["java", "-XX:+PrintFlagsFinal", "-XX:+PrintGCDetails", "-jar", "app.jar"]

In der folgenden Tabelle sind häufige Probleme und die entsprechenden Lösungen aufgeführt:

Fehler Mögliche Lösung
Nicht genügend Arbeitsspeicher. Erhöhen Sie die Speichergrenzwerte für Container
Verbindungstimeouts Überprüfen Sie die Netzwerkkonfiguration auf Fehler. Überprüfen Sie Ports und Routingregeln.
Probleme mit Berechtigungen Überprüfen Sie die Dateisystemberechtigungen.
Probleme mit dem Klassenpfad Überprüfen Sie die JAR-Struktur und die Abhängigkeiten.

Java-Container optimieren

Java-Anwendungen in Containern erfordern eine besondere Berücksichtigung für eine optimale Performance. Die JVM wurde entwickelt, bevor Container üblich waren. Die Verwendung von Containern kann zu Problemen bei der Ressourcenzuordnung führen, wenn sie nicht ordnungsgemäß konfiguriert sind.

Sie können die Leistung und Effizienz Ihrer containerisierten Java-Anwendungen erheblich verbessern, indem Sie die Speichereinstellungen optimieren, die Image-Größe optimieren und die Garbage Collection konfigurieren. In diesem Abschnitt werden grundlegende Optimierungen für Java-Container behandelt, wobei der Schwerpunkt auf der Speicherverwaltung, der Startzeit und der Ressourcenauslastung liegt.

JVM-Speicherkonfiguration in Containern

Die JVM erkennt die Speichergrenzen für Container in Java 8 nicht automatisch. Für Java 9+ ist die Containererkennung standardmäßig aktiviert.

Konfigurieren Sie Ihre JVM so, dass Container-Grenzwerte eingehalten werden, wie im folgenden Beispiel gezeigt:

FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

Die folgenden JVM-Flags sind für containerisierte Anwendungen wichtig:

  • -XX:MaxRAMPercentage=75.0. Legt den maximalen Heap als Prozentsatz des verfügbaren Arbeitsspeichers fest.
  • -XX:InitialRAMPercentage=50.0. Legt die anfängliche Heap-Größe fest.
  • -Xmx und -Xms. Diese Flags sind ebenfalls verfügbar, erfordern jedoch feste Werte.

Vorbereiten der Produktionsbereitstellung

Die Verlagerung containerisierter Java-Anwendungen in die Produktion erfordert Überlegungen, die über die grundlegende Funktionalität hinausgehen.

Produktionsumgebungen erfordern robuste Sicherheit, zuverlässige Überwachung, ordnungsgemäße Ressourcenzuweisung und Konfigurationsflexibilität.

In diesem Abschnitt werden die grundlegenden Vorgehensweisen und Konfigurationen behandelt, die zum Vorbereiten Ihrer Java-Container für den Einsatz in der Produktion erforderlich sind. Der Schwerpunkt des Abschnitts liegt auf Sicherheit, Zustandsprüfungen und Konfigurationsmanagement, um sicherzustellen, dass Ihre Anwendungen in der Produktion zuverlässig ausgeführt werden.

Bewährte Sicherheitsmethoden

Schützen Sie Ihre containerisierten Java-Anwendungen mit den folgenden Vorgehensweisen:

  • Standardmäßiger Sicherheitskontext. Führen Sie Ihre Anwendungen als Nicht-Root-Benutzer aus, wie im folgenden Beispiel gezeigt:

    FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu
    WORKDIR /app
    COPY target/*.jar app.jar
    RUN addgroup --system javauser && adduser --system --ingroup javauser javauser
    USER javauser
    ENTRYPOINT ["java", "-jar", "app.jar"]
    
  • Suchen Sie proaktiv nach Problemen. Überprüfen Sie Containerimages regelmäßig auf Sicherheitsrisiken, indem Sie den folgenden Befehl verwenden:

    docker scan myapp:latest
    
  • Aktualität des Basisbildes. Halten Sie Ihre Basisimages auf dem neuesten Stand.

  • Geheime Verwaltung. Implementieren Sie eine ordnungsgemäße Verwaltung von Geheimnissen. Codieren Sie beispielsweise vertrauliche Daten nicht hartcodiert in Ihre Anwendung, und verwenden Sie nach Möglichkeit einen Schlüsseltresor.

  • Eingeschränkte Sicherheitskontexte. Wenden Sie das Prinzip der geringsten Rechte auf alle Sicherheitskontexte an.

  • Zugriff auf das Dateisystem. Verwenden Sie nach Möglichkeit schreibgeschützte Dateisysteme.

Gesundheitschecks und -überwachung

Überprüfen Sie die Anwendungsintegrität mit Tests , um sicherzustellen, dass Ihre Anwendung ordnungsgemäß ausgeführt wird.

Schließen Sie für Spring Boot-Anwendungen die Actuator-Abhängigkeit für umfassende Integritätsendpunkte ein, wie im folgenden Beispiel gezeigt:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Konfigurieren Sie Ihre Anwendung so, dass Protokolle in einem für Containerumgebungen geeigneten Format ausgegeben werden, z. B. JSON.

Bereitstellen in Azure Container Apps

In diesem Abschnitt erfahren Sie, wie Sie Ihre Java-Container für die Bereitstellung von Azure Container Apps vorbereiten, und es werden wichtige Konfigurationsüberlegungen erläutert.

Vorbereiten Ihres Containers für Azure

  • Port-Konfiguration. Stellen Sie sicher, dass Ihr Container den von Azure bereitgestellten Port überwacht, wie im folgenden Beispiel gezeigt:

    FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu
    WORKDIR /app
    COPY target/*.jar app.jar
    ENV PORT=8080
    EXPOSE ${PORT}
    CMD java -jar app.jar --server.port=${PORT}
    
  • Sonde für Gesundheit. Implementieren Sie Integritätstests für die Live- und Bereitschaftsprüfungen von Azure.

  • Protokoll-Konfiguration. Konfigurieren Sie die Protokollierung für die Ausgabe in stdout/stderr.

  • Planen Sie für das Unerwartete. Richten Sie die ordnungsgemäße Behandlung des ordnungsgemäßen Herunterfahrens mit Timeoutkonfiguration ein. Weitere Informationen finden Sie unter Verwaltung des Anwendungslebenszyklus in Azure Container Apps.