DockerIDEJava

Debugging von Java Apps in Docker Containern

Wie kann ich meine dockerisierte Java-Anwendung mit IntelliJ IDEA oder Eclipse debuggen? Und wie bekomme ich IntelliJ IDEA dazu, dass Änderungen am Code während des Debuggens automatisch neu compiliert und deployt werden, ohne dass der Debug-Prozess neu gestartet werden muss?

Man in front of monitor with code
Nubelson Fernandes

Manchmal ist es hilfreich oder gar notwendig, das Remote-Debugging von Java Apps in Docker Containern (oder auch in einem Kubernetes-Cluster etc.) zu ermöglichen.

Damit das schnell und ohne großen Aufwand geht, sollte man das Remote-Debugging gleich von Anfang an unterstützen. Eine komfortable Möglichkeit dazu wird in diesem Beitrag gezeigt.

Dockerfile

Ein wesentlicher Unterschied zu vielen üblichen Dockerfiles für Java-Anwendungen ist die Tatsache, dass hier nicht das JAR direkt ausgeführt wird, sondern als Entrypoint ein Shell-Script angegeben wird:

FROM eclipse-temurin:17-jre-alpine
ARG JAR_FILE=target/*.jar

COPY ${JAR_FILE} app.jar
COPY entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh

USER 65534

ENTRYPOINT [ "./entrypoint.sh" ]

entrypoint.sh

Das Shell-Script ist für das Starten der Java-Anwendung zuständig und steuert abhängig von der Umgebungsvariable JAVA_DEBUG_PORT, ob das Remote-Debugging auf dem angegebenen Port aktiviert oder deaktiviert ist:

#!/bin/sh
if [ x"${JAVA_DEBUG_PORT}" != x ]; then
	echo "Debugger activated on internal port ${JAVA_DEBUG_PORT}"
	JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=${JAVA_DEBUG_SUSPEND:=n},address=*:${JAVA_DEBUG_PORT}"
fi

cmd="java $JAVA_OPTS -jar app.jar"
echo "running $cmd"
sh -c "$cmd"

Wird die Umgebungsvariable JAVA_DEBUG_SUSPEND auf den Wert „y“ gesetzt, kann erzwungen werden, dass die Anwendung beim Start wartet, bis ein Debugger verbunden ist.

Über die Umgebungsvariable JAVA_OPTS können zusätzliche Java-Parameter übergeben werden.

Zusätzliche Parameter für die Java-Anwendung können wie gewohnt in Zeile 7 ergänzt werden (z. B. -Dspring.profiles.active=prod) oder eine weitere Umgebungsvariable kann hierfür eingeführt werden.

Container starten

Um den Container debuggen zu können, muss die Umgebungsvariable JAVA_DEBUG_PORT (und wenn gewünscht auch JAVA_DEBUG_SUSPEND) beim Start gesetzt werden:

 $ docker run -d -e JAVA_DEBUG_PORT=7777 -p 7777:7777 YOUR-IMAGE

Was natürlich entsprechend auch bei der Verwendung von Kubernetes funktioniert:

apiVersion: apps/v1
kind: Deployment
spec:
  strategy:
    ...
  selector:
    ...
  template:
    metadata:
      ...
    spec:
      ...
      containers:
        - name: YOUR-NAME
          image: YOUR-IMAGE
          securityContext:
            runAsNonRoot: true
            runAsUser: 65534
            runAsGroup: 65534
            allowPrivilegeEscalation: false
          resources:
            ...
          livenessProbe:
            ...
          readinessProbe:
            ...
          env:
            - name: JAVA_DEBUG_PORT
              value: "5005"
            - name: JAVA_DEBUG_SUSPEND
              value: "y"

Debuggen der Anwendung im Container

Danach kann man die Anwendung mit dem integrierten Debugger einer IDE wie Eclipse oder IntelliJ IDEA debuggen.

Dazu muss erst eine „Debug Configuration“ vom Typ „Remote Java Application“ (Eclipse) beziehungsweise „Remote JVM Debug“ (IntelliJ) angelegt werden.

Der Host muss in unserem Beispiel localhost sein, der Port 7777.

Mit Eclipse

Eclipse Screenshot für das Debugging von Java Apps in Docker
Debug Configuration für das remote Debugging in Eclipse

Mit IntelliJ IDEA

IntelliJ Screenshot für das Debugging von Java Apps in Docker
Debug Configuration für das remote Debugging in IntelliJ IDEA

Verbinden des Debuggers mit dem Container

Um den Debugger mit der Anwendung im Container verbinden zu können muss der Port von Docker weitergeleitet werden. Das ist mit -p 7777:7777 im docker run Befehl schnell passiert.

Wenn der Container aber remote läuft, zum Beispiel in einem Kubernetes Cluster, muss der Port erst weitergeleitet werden:

$ kubectl port-forward YOUR-CONTAINER 7777:7777

Fazit

Erweitert man also das Dockerfile bzw das Run-Script nur um wenige Zeilen, ist es später lokal als auch remote über kubectl ganz einfach das Debugging von Java Apps in Docker zu aktivieren.