Ich mache ein Universitätsprojekt, bei dem wir mehrere Spring Boot-Anwendungen gleichzeitig ausführen müssen.
Ich hatte bereits einen mehrstufigen Build mit Gradle Docker-Image konfiguriert und dann die App in openjdk: jre image ausgeführt.
Hier ist meine Docker-Datei:
FROM gradle:5.3.0-jdk11-slim as builder
USER root
WORKDIR /usr/src/java-code
COPY . /usr/src/java-code/
RUN gradle bootJar
FROM openjdk:11-jre-slim
EXPOSE 8080
WORKDIR /usr/src/java-app
COPY --from=builder /usr/src/java-code/build/libs/*.jar ./app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
Ich baue und betreibe alles mit Docker-Compose. Teil von Docker-Compose:
website_server:
build: website-server
image: website-server:latest
container_name: "website-server"
ports:
- "81:8080"
Natürlich dauert der erste Bau ewig. Docker zieht alle seine Abhängigkeiten. Und damit bin ich einverstanden.
Im Moment funktioniert alles in Ordnung, aber jede kleine Änderung des Codes führt zu einer Erstellungszeit von ca. 1 Minute für eine App.
Teil des Build-Protokolls: docker-compose up --build
Step 1/10 : FROM gradle:5.3.0-jdk11-slim as builder
---> 668e92a5b906
Step 2/10 : USER root
---> Using cache
---> dac9a962d8b6
Step 3/10 : WORKDIR /usr/src/java-code
---> Using cache
---> e3f4528347f1
Step 4/10 : COPY . /usr/src/java-code/
---> Using cache
---> 52b136a280a2
Step 5/10 : RUN gradle bootJar
---> Running in 88a5ac812ac8
Welcome to Gradle 5.3!
Here are the highlights of this release:
- Feature variants AKA "optional dependencies"
- Type-safe accessors in Kotlin precompiled script plugins
- Gradle Module Metadata 1.0
For more details see https://docs.gradle.org/5.3/release-notes.html
Starting a Gradle Daemon (subsequent builds will be faster)
> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootJar
BUILD SUCCESSFUL in 48s
3 actionable tasks: 3 executed
Removing intermediate container 88a5ac812ac8
---> 4f9beba838ed
Step 6/10 : FROM openjdk:11-jre-slim
---> 0e452dba629c
Step 7/10 : EXPOSE 8080
---> Using cache
---> d5519e55d690
Step 8/10 : WORKDIR /usr/src/java-app
---> Using cache
---> 196f1321db2c
Step 9/10 : COPY --from=builder /usr/src/java-code/build/libs/*.jar ./app.jar
---> d101eefa2487
Step 10/10 : ENTRYPOINT ["java", "-jar", "app.jar"]
---> Running in ad02f0497c8f
Removing intermediate container ad02f0497c8f
---> 0c63eeef8c8e
Successfully built 0c63eeef8c8e
Successfully tagged website-server:latest
Jedes Mal friert es danach ein Starting a Gradle Daemon (subsequent builds will be faster)
Ich habe darüber nachgedacht, Volumen mit zwischengespeicherten Gradle-Abhängigkeiten hinzuzufügen, aber ich weiß nicht, ob dies der Kern des Problems ist. Auch dafür konnte ich keine guten Beispiele finden.
Gibt es eine Möglichkeit, den Build zu beschleunigen?
Antworten:
Das Erstellen nimmt viel Zeit in Anspruch, da Gradle jedes Mal, wenn das Docker-Image erstellt wird, alle Plugins und Abhängigkeiten herunterlädt.
Es gibt keine Möglichkeit, ein Volume zum Zeitpunkt der Image-Erstellung bereitzustellen. Es ist jedoch möglich, eine neue Phase einzuführen, in der alle Abhängigkeiten heruntergeladen und als Docker-Bildebene zwischengespeichert werden.
Das Gradle-Plugin und der Abhängigkeits-Cache befinden sich in
$GRADLE_USER_HOME/caches
.GRADLE_USER_HOME
muss auf etwas anderes eingestellt sein als/home/gradle/.gradle
./home/gradle/.gradle
Im übergeordneten Gradle Docker-Bild wird das Bild als Volumen definiert und nach jeder Bildebene gelöscht.Im Beispiel wird der Code auf
GRADLE_USER_HOME
gesetzt/home/gradle/cache_home
.In der
builder
Phase wird der Gradle-Cache kopiert, um ein erneutes Herunterladen der Abhängigkeiten zu vermeiden :COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
.Die Bühne
cache
wird erst wieder aufgebaut, wenn siebuild.gradle
geändert wird. Wenn Java-Klassen geändert werden, wird die zwischengespeicherte Bildebene mit allen Abhängigkeiten wiederverwendet.Diese Änderungen können die Erstellungszeit verkürzen, aber eine sauberere Methode zum Erstellen von Docker-Images mit Java-Anwendungen ist Jib von Google. Es gibt ein Jib Gradle-Plugin , mit dem Container-Images für Java-Anwendungen erstellt werden können, ohne Dockerfile manuell erstellen zu müssen. Das Erstellen eines Images mit einer Anwendung und das Ausführen des Containers ähnelt:
quelle
build.gradle
aus dem Kontext stammt, ist definitiv der richtige Weg. Wenn Sie nurbuild.gradle
in kopieren, stellencache
Sie sicher, dass Abhängigkeiten nur einmal heruntergeladen werden, wenn sich die Gradle-Builddatei nicht ändert (Docker verwendet den Cache erneut)Docker speichert seine Bilder in "Ebenen" zwischen. Jeder Befehl, den Sie ausführen, ist eine Ebene. Jede Änderung, die in einer bestimmten Ebene erkannt wird, macht die darauf folgenden Ebenen ungültig. Wenn der Cache ungültig ist, müssen die ungültigen Ebenen einschließlich der Abhängigkeiten von Grund auf neu erstellt werden .
Ich würde vorschlagen, Ihre Build-Schritte aufzuteilen. Haben Sie eine vorherige Ebene, die nur die Abhängigkeitsspezifikation in das Bild kopiert, und führen Sie dann einen Befehl aus, der dazu führt, dass Gradle die Abhängigkeiten herunterlädt. Nachdem das erledigt ist, kopieren Sie die Quelle in der gleichen Stelle , wo Sie das nur tat, und führen Sie den realen bauen.
Auf diese Weise werden die vorherigen Ebenen nur ungültig, wenn sich die Gradle-Dateien ändern.
Ich habe dies nicht mit Java / Gradle gemacht, aber ich bin dem gleichen Muster mit einem Rust-Projekt gefolgt, das von diesem Blog-Beitrag geleitet wird.
quelle
Sie können versuchen, BuildKit zu verwenden (jetzt standardmäßig in der neuesten Docker-Compose 1.25 aktiviert ).
Siehe " Beschleunigen Sie die Erstellung Ihrer Java-Anwendung Docker-Images mit BuildKit! " Von Aboullaite Med .
(Dies war für Maven, aber die gleiche Idee gilt für Gradle)
quelle
Dockerfile
. Ich denke, es ist das Caching-Problem. Ich habe versucht, zwischenzuspeichern, aber es lädt immer noch Gradle usw. bei jedem einzelnen Lauf herunter. Ich habe auch verschiedene Kombinationen von Volumenzielen ausprobiert.RUN
Builds und Verwenden von BuildKit wird bei jeder Codeänderung immer alles neu erstellt (da sich der Kontext geändert hat), jedoch zusätzlich mit der Antwort von @Evgeniy Khyst es könnte sich in Richtung eines besseren Ergebnisses bewegenIch weiß nicht viel über Docker-Interna, aber ich denke, dass das Problem darin besteht, dass jeder neue
docker build
Befehl alle Dateien kopiert und erstellt (wenn er Änderungen in mindestens einer Datei erkennt). Dann werden höchstwahrscheinlich mehrere Gläser geändert, und die zweiten Schritte müssen ebenfalls ausgeführt werden.Mein Vorschlag ist, auf dem Terminal (außerhalb von Docker) zu bauen und nur Docker das App-Image zu erstellen.
Dies kann sogar mit einem Gradle-Plugin automatisiert werden:
quelle
Wenn Ihre Internetverbindung langsam ist und jedes Mal Abhängigkeiten heruntergeladen werden, möchten Sie möglicherweise einen Sonatyp-Nexus einrichten, um die bereits heruntergeladenen Abhängigkeiten beizubehalten.
quelle
Wie in den anderen Antworten erwähnt, speichert Docker jeden Schritt in einer Ebene zwischen. Wenn Sie irgendwie nur die heruntergeladenen Abhängigkeiten in eine Ebene bringen könnten, müsste sie nicht jedes Mal neu heruntergeladen werden, vorausgesetzt, die Abhängigkeiten haben sich nicht geändert.
Leider hat gradle keine eingebaute Aufgabe, um dies zu tun. Aber Sie können es trotzdem umgehen. Folgendes habe ich getan:
Stellen Sie außerdem sicher, dass Ihre
.dockerignore
Datei mindestens diese Elemente enthält, damit sie beim Erstellen des Images nicht im Docker-Build-Kontext gesendet werden:quelle