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?
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
Mit 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.