Je fais un projet universitaire où nous devons exécuter plusieurs applications Spring Boot à la fois.
J'avais déjà configuré la construction en plusieurs étapes avec l'image Gradle Docker, puis exécuté l'application dans l'image openjdk: jre.
Voici mon Dockerfile:
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"]
Je construis et gère tout avec docker-compose. Partie de docker-compose:
website_server:
build: website-server
image: website-server:latest
container_name: "website-server"
ports:
- "81:8080"
Bien sûr, la première construction prend du temps. Docker tire toutes ses dépendances. Et je suis d'accord avec ça.
Tout fonctionne bien pour l'instant, mais chaque petit changement de code entraîne un temps de construction d'environ 1 min pour une application.
Partie du journal de construction: 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
Chaque fois qu'il se fige après Starting a Gradle Daemon (subsequent builds will be faster)
Je pensais à ajouter du volume avec des dépendances de gradle mises en cache mais je ne sais pas si c'est le cœur du problème. De plus, je n'ai pas pu trouver de bons exemples pour cela.
Existe-t-il un moyen d'accélérer la construction?
La construction prend beaucoup de temps car Gradle chaque fois que l'image Docker est construite télécharge tous les plugins et les dépendances.
Il n'y a aucun moyen de monter un volume au moment de la création de l'image. Mais il est possible d'introduire une nouvelle étape qui téléchargera toutes les dépendances et sera mise en cache en tant que couche d'image Docker.
FROM gradle:5.6.4-jdk11 as cache
RUN mkdir -p /home/gradle/cache_home
ENV GRADLE_USER_HOME /home/gradle/cache_home
COPY build.gradle /home/gradle/Java-code/
WORKDIR /home/gradle/Java-code
RUN gradle clean build -i --stacktrace
FROM gradle:5.6.4-jdk11 as builder
COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
COPY . /usr/src/Java-code/
WORKDIR /usr/src/Java-code
RUN gradle bootJar -i --stacktrace
FROM openjdk:11-jre-slim
EXPOSE 8080
USER root
WORKDIR /usr/src/Java-app
COPY --from=builder /usr/src/Java-code/build/libs/*.jar ./app.jar
ENTRYPOINT ["Java", "-jar", "app.jar"]
Le plugin Gradle et le cache de dépendances sont situés dans $GRADLE_USER_HOME/caches
. GRADLE_USER_HOME
doit être différent de /home/gradle/.gradle
. /home/gradle/.gradle
dans l'image parent Gradle Docker est défini comme le volume et est effacé après chaque couche d'image.
Dans l'exemple de code GRADLE_USER_HOME
est réglé sur /home/gradle/cache_home
.
À l'étape builder
, le cache Gradle est copié pour éviter de télécharger à nouveau les dépendances: COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
.
L'étape cache
ne sera reconstruite que lorsque build.gradle
est changé. Lorsque Java sont des modifications, la couche d'image mise en cache avec toutes les dépendances est réutilisée.
Ces modifications peuvent réduire le temps de construction mais une manière plus propre de construire des images Docker avec Java sont Jib par Google. Il y a un plugin Jib Gradle qui permet de construire des images de conteneur pour Java applications sans créer manuellement Dockerfile. Construire l'image avec l'application et exécuter le conteneur est similaire à:
gradle clean build jib
docker-compose up
Docker met ses images en cache dans des "couches". Chaque commande que vous exécutez est un calque. Chaque modification détectée dans une couche donnée invalide les couches qui la suivent. Si le cache est invalidé, les couches invalidées doivent être construites à partir de zéro, y compris les dépendances .
Je suggérerais de diviser vos étapes de construction. Avoir une couche précédente qui copie uniquement la spécification de dépendance dans l'image, puis exécute une commande qui entraînera le téléchargement des dépendances par Gradle. Une fois terminé, copiez votre source dans le même emplacement que celui que vous venez de faire, et exécutez la vraie construction.
De cette façon, les calques précédents ne seront invalidés que lorsque les fichiers de gradle changent.
Je n'ai pas fait cela avec Java/Gradle, mais j'ai suivi le même schéma avec un projet Rust, guidé par this blog).
Vous pouvez essayer d'utiliser BuildKit (maintenant activé par défaut dans le dernier docker-compose 1.25 )
Voir " Accélérez votre Java application Docker images de build avec BuildKit! " de Aboullaite Med .
(C'était pour maven, mais la même idée s'applique au gradle)
considérons le Dockerfile suivant:
FROM maven:3.6.1-jdk-11-slim AS build
USER MYUSER
RUN mvn clean package
La modification de la deuxième ligne invalide toujours le cache maven en raison d'une fausse dépendance, ce qui expose un problème de mise en cache inefficace.
BuildKit résout cette limitation en introduisant le solveur de graphique de génération simultané, qui peut exécuter des étapes de génération en parallèle et optimiser les commandes qui n'ont pas d'impact sur le résultat final.
De plus, Buildkit suit uniquement les mises à jour apportées aux fichiers entre les appels de génération répétés qui optimisent l'accès aux fichiers source locaux. Ainsi, il n'est pas nécessaire d'attendre la lecture ou le téléchargement des fichiers locaux avant de commencer le travail.
Tout comme un ajout aux réponses d'autres personnes, si votre connexion Internet est lente, car elle télécharge les dépendances à chaque fois, vous pouvez configurer le lien sonatype, afin de conserver les dépendances déjà téléchargées.
Je ne sais pas grand-chose sur les internes de docker, mais je pense que le problème est que chaque nouveau docker build
, copiera tous les fichiers et les construira (s'il détecte des changements dans au moins un fichier). Ensuite, cela changera probablement plusieurs pots et la deuxième étape doit également s'exécuter.
Ma suggestion est de construire sur le terminal (en dehors de docker) et seulement docker construire l'image de l'application.
Cela peut même être automatisé avec un plugin gradle:
Comme les autres réponses l'ont mentionné, Docker met en cache chaque étape d'une couche. Si vous pouviez d'une manière ou d'une autre obtenir uniquement les dépendances téléchargées dans une couche, il ne devrait pas être à nouveau téléchargé à chaque fois, en supposant que les dépendances n'ont pas changé.
Malheureusement, Gradle n'a pas de tâche intégrée pour ce faire. Mais vous pouvez toujours contourner ce problème. Voici ce que j'ai fait:
# Only copy dependency-related files
COPY build.gradle gradle.properties settings.gradle /app/
# Only download dependencies
# Eat the expected build failure since no source code has been copied yet
RUN gradle clean build --no-daemon > /dev/null 2>&1 || true
# Copy all files
COPY ./ /app/
# Do the actual build
RUN gradle clean build --no-daemon
Assurez-vous également que votre .dockerignore
le fichier contient au moins ces éléments, afin qu'ils ne soient pas envoyés dans le contexte de génération du docker lors de la génération de l'image:
.gradle/
bin/
build/
gradle/