Skip to content

Commit 7436fe3

Browse files
author
wlanboy
committed
Added Layers
1 parent 5035e38 commit 7436fe3

7 files changed

Lines changed: 270 additions & 8 deletions

File tree

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,18 @@ USER 185
6666
COPY --from=build --chown=185:185 /app/extracted/dependencies/ ./
6767
# → Kopiert nur die Dependency-Layer. Ändern sich selten.
6868

69+
COPY --from=build --chown=185:185 /app/extracted/observability-dependencies/ ./
70+
# → Micrometer, SpringDoc – eigener Release-Zyklus.
71+
6972
COPY --from=build --chown=185:185 /app/extracted/spring-boot-loader/ ./
7073
# → Enthält den Spring Boot Launcher (Main-Class Loader). Ändern sich selten.
7174

7275
COPY --from=build --chown=185:185 /app/extracted/snapshot-dependencies/ ./
7376
# → Snapshot-Dependencies (z. B. lokale libs), ändern sich häufiger.
7477

78+
COPY --from=build --chown=185:185 /app/extracted/application-resources/ ./
79+
# → Konfigurationsdateien (yml, properties, xml). Invalidiert nicht den Class-Layer.
80+
7581
COPY --from=build --chown=185:185 /app/extracted/application/ ./
7682
# → Der eigentliche Applikationscode (Kompilat). Ändert sich.
7783

Dockerfile25

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,18 @@ USER 185
6666
COPY --from=build --chown=185:185 /app/extracted/dependencies/ ./
6767
# → Kopiert nur die Dependency-Layer. Ändern sich selten.
6868

69+
COPY --from=build --chown=185:185 /app/extracted/observability-dependencies/ ./
70+
# → Micrometer, SpringDoc – eigener Release-Zyklus.
71+
6972
COPY --from=build --chown=185:185 /app/extracted/spring-boot-loader/ ./
7073
# → Enthält den Spring Boot Launcher (Main-Class Loader). Ändern sich selten.
7174

7275
COPY --from=build --chown=185:185 /app/extracted/snapshot-dependencies/ ./
7376
# → Snapshot-Dependencies (z. B. lokale libs), ändern sich häufiger.
7477

78+
COPY --from=build --chown=185:185 /app/extracted/application-resources/ ./
79+
# → Konfigurationsdateien (yml, properties, xml). Invalidiert nicht den Class-Layer.
80+
7581
COPY --from=build --chown=185:185 /app/extracted/application/ ./
7682
# → Der eigentliche Applikationscode (Kompilat). Ändert sich.
7783

Dockerfile25Jlink

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ RUN jdeps \
3232
--print-module-deps \
3333
--multi-release 25 \
3434
--recursive \
35-
--class-path "extracted/dependencies/*:extracted/snapshot-dependencies/*" \
35+
--class-path "extracted/dependencies/*:extracted/observability-dependencies/*:extracted/snapshot-dependencies/*" \
3636
extracted/application/BOOT-INF/classes > modules.txt
3737

3838
# Erzeuge das Custom JRE
@@ -76,7 +76,7 @@ RUN /custom-jre/bin/java -Xshare:dump
7676
RUN /custom-jre/bin/java -XX:ArchiveClassesAtExit=app.jsa \
7777
-Dspring.context.exit=onRefresh \
7878
-Dspring.aot.enabled=true \
79-
-cp "extracted/dependencies/*:extracted/snapshot-dependencies/*:extracted/application/" \
79+
-cp "extracted/dependencies/*:extracted/observability-dependencies/*:extracted/snapshot-dependencies/*:extracted/application/" \
8080
org.springframework.boot.loader.launch.JarLauncher || [ -f app.jsa ]
8181
# Hinweis: Das "|| true" ist wichtig, da die App beim Training mangels DB/Config evtl. abbricht,
8282
# aber das Archiv trotzdem schreibt.
@@ -101,8 +101,10 @@ USER nonroot
101101

102102
# Layer kopieren (wie gehabt)
103103
COPY --from=build --chown=65532:65532 /app/extracted/dependencies/ ./
104+
COPY --from=build --chown=65532:65532 /app/extracted/observability-dependencies/ ./
104105
COPY --from=build --chown=65532:65532 /app/extracted/spring-boot-loader/ ./
105106
COPY --from=build --chown=65532:65532 /app/extracted/snapshot-dependencies/ ./
107+
COPY --from=build --chown=65532:65532 /app/extracted/application-resources/ ./
106108
COPY --from=build --chown=65532:65532 /app/extracted/application/ ./
107109
COPY --from=build --chown=65532:65532 /app/app.jsa /app/app.jsa
108110
COPY --chown=65532:65532 containerconfig/application.properties /app/config/application.properties

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,20 @@ docker build -t wlanboy/javahttpclient:latest .
3030
# Docker build with jlink and without
3131

3232
```bash
33-
docker build -f Dockerfile25 -t wlanboy/javahttpclient:jre .
34-
docker build -f Dockerfile25Jlink -t wlanboy/javahttpclient:jlink .
33+
docker build -f Dockerfile25 -t javahttpclient:jre .
34+
docker build -f Dockerfile25Jlink -t javahttpclient:jlink .
3535

3636
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep "javahttpclient"
37-
wlanboy/javahttpclient jre 510MB
38-
wlanboy/javahttpclient jlink 175MB
37+
javahttpclient jre 510MB
38+
javahttpclient jlink 295MB
3939
```
4040

4141
# Run container
4242

4343
```bash
44-
docker run --rm --name httpclient --publish 8080:8080 wlanboy/javahttpclient:latest
44+
docker run --rm --name httpclient --publish 8080:8080 javahttpclient:jre
4545

46-
docker run --rm --name httpclient --publish 8080:8080 wlanboy/javahttpclient:jlink
46+
docker run --rm --name httpclient --publish 8080:8080 javahttpclient:jlink
4747
```
4848

4949
# Docker hub

layers.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Spring Boot Layered JAR – Strategie & Anwendungsanleitung
2+
3+
## Warum Layering?
4+
5+
Docker-Images bestehen aus aufeinander gestapelten Layern. Ändert sich ein Layer, werden alle darüber liegenden Layer neu erstellt – unabhängig davon, ob sie sich selbst verändert haben. Ein gut strukturiertes Layered JAR sorgt dafür, dass bei einem typischen Deployment (nur App-Code ändert sich) die großen Dependency-Layer aus dem Cache kommen und nur die kleinen oberen Layer neu übertragen werden.
6+
7+
**Ergebnis:** Schnellere Builds, schnellere Deploys, geringerer Netzwerk-Traffic.
8+
9+
---
10+
11+
## Schritt 1 – layers.xml anlegen
12+
13+
**Pfad:** `src/main/resources/layers.xml`
14+
15+
Das ist die Vorlage. Kommentare beschreiben, was je nach Projekt angepasst werden muss.
16+
17+
```xml
18+
<?xml version="1.0" encoding="UTF-8"?>
19+
<layers xmlns="http://www.springframework.org/schema/boot/layers"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
22+
https://www.springframework.org/schema/boot/layers/layers-3.0.xsd">
23+
<application>
24+
<!-- Spring Boot Launcher separat – ändert sich nur bei Spring Boot Version-Update -->
25+
<into layer="spring-boot-loader">
26+
<include>org/springframework/boot/loader/**</include>
27+
</into>
28+
29+
<!-- Ressourcen getrennt vom Kompilat.
30+
Konfigurationsänderungen invalidieren nicht den Class-Layer.
31+
Anpassen: Dateiendungen und Pfade je nach Projekt ergänzen/entfernen.
32+
Thymeleaf-Projekte: templates/** hinzufügen.
33+
Projekte ohne Templates: die templates-Zeile weglassen. -->
34+
<into layer="application-resources">
35+
<include>BOOT-INF/classes/**/*.yml</include>
36+
<include>BOOT-INF/classes/**/*.yaml</include>
37+
<include>BOOT-INF/classes/**/*.properties</include>
38+
<include>BOOT-INF/classes/**/*.xml</include>
39+
<!-- nur wenn Thymeleaf / andere Template-Engines vorhanden: -->
40+
<include>BOOT-INF/classes/templates/**</include>
41+
</into>
42+
43+
<!-- Kompilierter Code + AOT-Metadaten – ändert sich am häufigsten -->
44+
<into layer="application" />
45+
</application>
46+
47+
<dependencies>
48+
<!-- SNAPSHOT-Dependencies getrennt (zukunftssicher, auch wenn aktuell keine vorhanden) -->
49+
<into layer="snapshot-dependencies">
50+
<include>*:*:*SNAPSHOT</include>
51+
</into>
52+
53+
<!-- Observability-Stack: eigener Release-Zyklus, unabhängig von Spring-Core.
54+
Nur einfügen wenn Micrometer / Prometheus / SpringDoc / OpenTelemetry im Projekt vorhanden.
55+
Weglassen wenn das Projekt kein Monitoring hat. -->
56+
<into layer="observability-dependencies">
57+
<include>io.micrometer:*</include>
58+
<include>io.prometheus:*</include>
59+
<include>org.springdoc:*</include>
60+
<include>io.opentelemetry:*</include>
61+
</into>
62+
63+
<!-- Alle übrigen stabilen Release-Bibliotheken (Spring, Jetty/Tomcat, …) -->
64+
<into layer="dependencies" />
65+
</dependencies>
66+
67+
<!-- Reihenfolge: stabilste Layer zuerst → bestes Docker-Cache-Verhalten.
68+
Jeder Layer der oben definiert wird MUSS hier aufgeführt sein. -->
69+
<layerOrder>
70+
<layer>dependencies</layer>
71+
<layer>observability-dependencies</layer> <!-- entfernen wenn kein Observability-Layer -->
72+
<layer>spring-boot-loader</layer>
73+
<layer>snapshot-dependencies</layer>
74+
<layer>application-resources</layer>
75+
<layer>application</layer>
76+
</layerOrder>
77+
</layers>
78+
```
79+
80+
### Wichtige Regeln
81+
82+
- **Reihenfolge der XML-Elemente ist fest:** `<application>``<dependencies>``<layerOrder>`
83+
- Jeder Layer in `<layerOrder>` muss irgendwo in `<application>` oder `<dependencies>` befüllt werden
84+
- `<into>` ohne `<include>` fängt alles auf, was die vorherigen `<into>`-Blöcke nicht gematcht haben (Catch-All)
85+
- Muster für Dependencies: `groupId:artifactId` – Wildcards mit `*` möglich (`io.micrometer:*`)
86+
- Muster für Application-Inhalte: Ant-Style-Pfade (`BOOT-INF/classes/**/*.yml`)
87+
88+
---
89+
90+
## Schritt 2 – pom.xml anpassen
91+
92+
Im `spring-boot-maven-plugin` die Layers-Konfiguration aktivieren und auf die `layers.xml` zeigen:
93+
94+
```xml
95+
<plugin>
96+
<groupId>org.springframework.boot</groupId>
97+
<artifactId>spring-boot-maven-plugin</artifactId>
98+
<configuration>
99+
<layers>
100+
<enabled>true</enabled>
101+
<configuration>${project.basedir}/src/main/resources/layers.xml</configuration>
102+
</layers>
103+
</configuration>
104+
<executions>
105+
<!-- bestehende executions bleiben erhalten -->
106+
</executions>
107+
</plugin>
108+
```
109+
110+
---
111+
112+
## Schritt 3 – Dockerfile anpassen
113+
114+
### Build Stage: JAR extrahieren
115+
116+
```dockerfile
117+
RUN cp target/PROJEKTNAME-VERSION.jar app.jar && \
118+
java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted
119+
```
120+
121+
`--launcher` ist in Spring Boot 4.x erforderlich, damit der Loader extrahiert wird.
122+
123+
### Runtime Stage: Layer kopieren
124+
125+
Die COPY-Befehle müssen **exakt der `<layerOrder>`** in der `layers.xml` entsprechen (stabilstes zuerst):
126+
127+
```dockerfile
128+
COPY --from=build --chown=USER:USER /app/extracted/dependencies/ ./
129+
COPY --from=build --chown=USER:USER /app/extracted/observability-dependencies/ ./ # nur wenn vorhanden
130+
COPY --from=build --chown=USER:USER /app/extracted/spring-boot-loader/ ./
131+
COPY --from=build --chown=USER:USER /app/extracted/snapshot-dependencies/ ./
132+
COPY --from=build --chown=USER:USER /app/extracted/application-resources/ ./
133+
COPY --from=build --chown=USER:USER /app/extracted/application/ ./
134+
```
135+
136+
`USER` ersetzen: z. B. `185` (UBI/OpenJDK), `65532` (distroless nonroot).
137+
138+
---
139+
140+
## Schritt 4 – Dockerfile25Jlink: Sonderfälle
141+
142+
Bei Verwendung von `jdeps` und AppCDS müssen alle Dependency-Layer im Classpath stehen:
143+
144+
### jdeps-Aufruf
145+
146+
```dockerfile
147+
RUN jdeps \
148+
--ignore-missing-deps \
149+
--print-module-deps \
150+
--multi-release 25 \
151+
--recursive \
152+
--class-path "extracted/dependencies/*:extracted/observability-dependencies/*:extracted/snapshot-dependencies/*" \
153+
extracted/application/BOOT-INF/classes > modules.txt
154+
```
155+
156+
### CDS-Training
157+
158+
```dockerfile
159+
RUN /custom-jre/bin/java -XX:ArchiveClassesAtExit=app.jsa \
160+
-Dspring.context.exit=onRefresh \
161+
-Dspring.aot.enabled=true \
162+
-cp "extracted/dependencies/*:extracted/observability-dependencies/*:extracted/snapshot-dependencies/*:extracted/application/" \
163+
org.springframework.boot.loader.launch.JarLauncher || [ -f app.jsa ]
164+
```
165+
166+
`application-resources` gehört **nicht** in den Classpath — die Ressourcen werden zur Laufzeit über den `JarLauncher` aus `BOOT-INF/classes/` geladen, nicht direkt über `-cp`.
167+
168+
---
169+
170+
## Referenz: Layer-Übersicht
171+
172+
| Layer | Inhalt | Wann anpassen |
173+
|---|---|---|
174+
| `dependencies` | Spring, Jetty/Tomcat, Thymeleaf, Reactor, … | Nie – Catch-All für stabile Libs |
175+
| `observability-dependencies` | Micrometer, Prometheus, SpringDoc, OTel | Weglassen wenn kein Monitoring |
176+
| `spring-boot-loader` | `org/springframework/boot/loader/**` | Nie |
177+
| `snapshot-dependencies` | Alle `*SNAPSHOT`-Deps | Nie |
178+
| `application-resources` | yml, yaml, properties, xml, templates | Dateiendungen je nach Projekt |
179+
| `application` | Kompilierter Code, AOT-Metadaten | Nie – Catch-All für App-Content |
180+
181+
## Entscheidungsbaum: Welche Layer brauche ich?
182+
183+
```
184+
Hat das Projekt Micrometer/Prometheus/SpringDoc?
185+
├── Ja → observability-dependencies Layer anlegen
186+
└── Nein → weglassen, direkt zu dependencies
187+
188+
Hat das Projekt Template-Engines (Thymeleaf, FreeMarker)?
189+
├── Ja → templates/** in application-resources aufnehmen
190+
└── Nein → Zeile weglassen
191+
192+
Wird jlink / AppCDS verwendet?
193+
├── Ja → observability-dependencies im jdeps- und CDS-Classpath ergänzen
194+
└── Nein → nur COPY-Befehle im Dockerfile anpassen
195+
```

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@
7979
<plugin>
8080
<groupId>org.springframework.boot</groupId>
8181
<artifactId>spring-boot-maven-plugin</artifactId>
82+
<configuration>
83+
<layers>
84+
<enabled>true</enabled>
85+
<configuration>${project.basedir}/src/main/resources/layers.xml</configuration>
86+
</layers>
87+
</configuration>
8288
<executions>
8389
<execution>
8490
<goals>

src/main/resources/layers.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<layers xmlns="http://www.springframework.org/schema/boot/layers"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
5+
https://www.springframework.org/schema/boot/layers/layers-3.0.xsd">
6+
<application>
7+
<into layer="spring-boot-loader">
8+
<include>org/springframework/boot/loader/**</include>
9+
</into>
10+
11+
<!-- Ressourcen getrennt vom Kompilat: yml, properties, xml -->
12+
<!-- Kein Thymeleaf/Templates in diesem Projekt -->
13+
<into layer="application-resources">
14+
<include>BOOT-INF/classes/**/*.yml</include>
15+
<include>BOOT-INF/classes/**/*.yaml</include>
16+
<include>BOOT-INF/classes/**/*.properties</include>
17+
<include>BOOT-INF/classes/**/*.xml</include>
18+
</into>
19+
20+
<into layer="application" />
21+
</application>
22+
23+
<dependencies>
24+
<into layer="snapshot-dependencies">
25+
<include>*:*:*SNAPSHOT</include>
26+
</into>
27+
28+
<!-- Micrometer (tracing) + SpringDoc vorhanden -->
29+
<into layer="observability-dependencies">
30+
<include>io.micrometer:*</include>
31+
<include>io.prometheus:*</include>
32+
<include>org.springdoc:*</include>
33+
<include>io.opentelemetry:*</include>
34+
</into>
35+
36+
<into layer="dependencies" />
37+
</dependencies>
38+
39+
<layerOrder>
40+
<layer>dependencies</layer>
41+
<layer>observability-dependencies</layer>
42+
<layer>spring-boot-loader</layer>
43+
<layer>snapshot-dependencies</layer>
44+
<layer>application-resources</layer>
45+
<layer>application</layer>
46+
</layerOrder>
47+
</layers>

0 commit comments

Comments
 (0)