-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile25Jlink
More file actions
169 lines (152 loc) · 7.85 KB
/
Dockerfile25Jlink
File metadata and controls
169 lines (152 loc) · 7.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# ============================
# 1. Build Stage (Java 25)
# ============================
FROM registry.access.redhat.com/ubi9/openjdk-25:latest AS build
WORKDIR /app
# Maven Caching & Build (wie gehabt)
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests dependency:go-offline
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests compile spring-boot:process-aot package
# JAR extrahieren für Layering (Layer gemäß src/main/resources/layers.xml):
# - dependencies, observability-dependencies, spring-boot-loader, snapshot-dependencies, application
# Versionsneutrale Selektion: schließt das Original-JAR (nicht-umgepacktes) aus
RUN JAR=$(ls target/*.jar | grep -v original) && \
cp "$JAR" app.jar && \
java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted
# ============================
# 2. JRE-Erstellung
# (noch innerhalb der build-Stage – benötigt die extrahierten Layer aus Phase 1)
# ============================
# WICHTIG: Umschalten auf root, um Pakete zu installieren
USER root
# Installiere die JMODS, die für jlink zwingend erforderlich sind. Red Hat trennt das, um Platz zu sparen.
# Wenn jlink anweist, Debug-Symbole zu entfernen (--strip-debug), sucht das Tool im Hintergrund
# nach dem System-Befehl objcopy (ein Teil der binutils), was natürlich nicht als notwendig erachtet wurde.
RUN microdnf install -y java-25-openjdk-jmods binutils && \
microdnf clean all
# Wir analysieren die extrahierten Layer, um ein minimales JRE zu bauen
RUN jdeps \
--ignore-missing-deps \
--print-module-deps \
--multi-release 25 \
--recursive \
--class-path "extracted/dependencies/*:extracted/observability-dependencies/*:extracted/snapshot-dependencies/*" \
extracted/application/BOOT-INF/classes > modules.txt
# Erzeuge das Custom JRE
# strip-debug -> keine Local Variable Tables, Line Number Tables, Native Debug Symbols
# strip-native-commands -> kein keytool, rmiregistry, jdb, jhsdb, serialver - breaker!
# compress zip-9 ist der moderne Ersatz für --compress=2
# NoClassDefFoundError: java.beans.PropertyEditorSupport -> java.desktop
# jmx, metrics -> java.management
# GC notifications will not be available -> jdk.management
# load jar files (swagger) -> jdk.zipfs
# needs jlink -> java.instrument
# jpa needs java.sql.Date -> java.sql
# jetty jndi config -> java.naming
# jdk25 warnings -> sun.misc.Unsafe, das von Objenesis/CGLIB -> jdk.unsupported
# org.ietf.jgss.GSSException -> java.security.jgss, java.security.sasl
RUN $JAVA_HOME/bin/jlink \
--module-path /usr/lib/jvm/java-25/jmods \
--add-modules java.base,$(cat modules.txt),jdk.crypto.ec,jdk.charsets,java.desktop,java.management,jdk.management,jdk.zipfs,java.instrument,java.sql,java.naming,jdk.unsupported,java.security.jgss,java.security.sasl \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress zip-9 \
--output /opt/jre
# Erzeugt das Basis-Archiv für das Custom JRE
# WICHTIG: -XX:+UseZGC muss hier angegeben werden! ZGC nutzt Colored Pointers (andere Heap-Struktur als G1GC).
# Das CDS-Archiv wird mit dieser Heap-Konfiguration erstellt. Wenn zur Laufzeit ein anderer GC
# aktiv ist, lehnt die JVM das Archiv mit "Loading static archive failed" ab.
RUN /opt/jre/bin/java -XX:+UseZGC -Xshare:dump
# CDS Archiv generieren
# 1. Liste der Klassen erstellen (Training)
# WICHTIG: Wir nutzen /opt/jre/bin/java, damit das Archiv zum Runtime-Pfad passt!
# WICHTIG: -XX:+UseZGC muss mit dem Runtime-GC übereinstimmen (Archiv ist GC-gebunden)!
# spring.context.exit weist Spring Boot an, den Application Context aufzubauen (damit alle Klassen geladen werden),
# aber die App sofort danach sauber zu beenden
# Training gegen die entpackten Layer (muss identisch zum Start sein!)
RUN /opt/jre/bin/java -XX:+UseZGC -XX:ArchiveClassesAtExit=app.jsa \
-Dspring.context.exit=onRefresh \
-Dspring.aot.enabled=true \
-cp "extracted/dependencies/*:extracted/observability-dependencies/*:extracted/snapshot-dependencies/*:extracted/application/" \
org.springframework.boot.loader.launch.JarLauncher || [ -f app.jsa ]
# Hinweis: Das "|| [ -f app.jsa ]" ist wichtig, da die App beim Training mangels DB/Config evtl. abbricht,
# aber das Archiv trotzdem schreibt. Der Exit-Code 1 ist nur akzeptabel, wenn app.jsa existiert.
# ============================
# 3. Runtime Stage (Minimales OS)
# ============================
# Wir nutzen hier ubi9-minimal statt der vollen Java-Runtime
#FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
FROM gcr.io/distroless/cc
LABEL org.opencontainers.image.title="Java mirrorservice with Custom JRE" \
org.opencontainers.image.version="0.3.1-SNAPSHOT"
WORKDIR /app
# Kopiere das Custom JRE aus der Build-Stage (wurde direkt nach /opt/jre gebaut)
COPY --from=build /opt/jre /opt/jre
ENV PATH="/opt/jre/bin:$PATH"
USER nonroot
# Layer kopieren (Reihenfolge entspricht layers.xml: stabilstes zuerst)
COPY --from=build --chown=65532:65532 /app/extracted/dependencies/ ./
COPY --from=build --chown=65532:65532 /app/extracted/observability-dependencies/ ./
COPY --from=build --chown=65532:65532 /app/extracted/spring-boot-loader/ ./
COPY --from=build --chown=65532:65532 /app/extracted/snapshot-dependencies/ ./
COPY --from=build --chown=65532:65532 /app/extracted/application-resources/ ./
COPY --from=build --chown=65532:65532 /app/extracted/application/ ./
COPY --from=build --chown=65532:65532 /app/app.jsa /app/app.jsa
COPY --chown=65532:65532 containerconfig/application.yml /app/config/application.yml
EXPOSE 8080
# ENTRYPOINT-Parameter:
# /opt/jre/bin/java
# Startet die JVM aus dem per jlink erzeugten Custom JRE (kein System-Java)
#
# -XX:SharedArchiveFile=/app/app.jsa
# Lädt das AppCDS-Archiv (Class Data Sharing) – verkürzt Startzeit erheblich,
# da Klassen bereits analysiert & gemappt vorliegen
#
# -Dspring.aot.enabled=true
# Aktiviert den AOT-Modus von Spring Boot – nutzt die beim Build vorberechneten
# Proxies, Reflection-Metadaten und Bean-Definitionen statt Runtime-Analyse
#
# -Djava.security.egd=file:/dev/./urandom
# Verhindert Blockierung auf /dev/random im Container (kaum Entropie verfügbar);
# /dev/./urandom ist der übliche Workaround für die JVM-interne Erkennung
#
# -XX:MaxRAMPercentage=80
# Begrenzt den Java-Heap auf 80 % des Container-Speicherlimits (statt fester -Xmx)
#
# -XX:InitialRAMPercentage=40
# Reserviert zu Beginn 40 % des Container-Speichers als Heap; reduziert Heap-Expansions
#
# -XX:+UseZGC
# Aktiviert den Z Garbage Collector: sehr kurze GC-Pausen (<1 ms), ideal für
# latenzempfindliche Services; in Java 21+ ist Generational ZGC der Standard
#
# -XX:+ExitOnOutOfMemoryError
# Beendet die JVM sofort bei OutOfMemoryError – der Container-Orchestrator
# (Kubernetes/Docker) kann neu starten; verhindert einen "lebenden Zombie"-Prozess
#
# -Dfile.encoding=UTF-8
# Erzwingt UTF-8 als Standard-Zeichensatz; das Custom JRE enthält kein
# Default-Locale-Setup aus dem OS
#
# org.springframework.boot.loader.launch.JarLauncher
# Spring Boot Launcher-Klasse; startet die Anwendung aus den extrahierten
# Layern (extracted/) statt aus einem einzelnen Fat-JAR
#
# --spring.config.location=file:/app/config/application.properties
# Überschreibt den Standard-Konfigurationspfad; Konfiguration liegt als
# separater Docker-Layer vor (besseres Cache-Verhalten bei Config-Änderungen)
ENTRYPOINT [ \
"/opt/jre/bin/java", \
"-XX:SharedArchiveFile=/app/app.jsa", \
"-Dspring.aot.enabled=true", \
"-Djava.security.egd=file:/dev/./urandom", \
"-XX:MaxRAMPercentage=80", \
"-XX:InitialRAMPercentage=40", \
"-XX:+UseZGC", \
"-XX:+ExitOnOutOfMemoryError", \
"-Dfile.encoding=UTF-8", \
"org.springframework.boot.loader.launch.JarLauncher", \
"--spring.config.location=file:/app/config/application.yml" \
]