Estoy haciendo un proyecto universitario donde necesitamos ejecutar múltiples aplicaciones Spring Boot a la vez.
Ya había configurado la compilación de varias etapas con la imagen de gradle docker y luego ejecuté la aplicación en openjdk: jre image.
Aquí está mi 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"]
Estoy construyendo y ejecutando todo con docker-compose. Parte de docker-compose:
website_server:
build: website-server
image: website-server:latest
container_name: "website-server"
ports:
- "81:8080"
Por supuesto, la primera construcción lleva años. Docker está sacando todas sus dependencias. Y estoy de acuerdo con eso.
Todo está funcionando bien por ahora, pero cada pequeño cambio en el código causa alrededor de 1 minuto de tiempo de compilación para una aplicación.
Parte del registro de compilación: 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
Cada vez que se congela después Starting a Gradle Daemon (subsequent builds will be faster)
Estaba pensando en agregar volumen con dependencias de gradle en caché, pero no sé si ese es el núcleo del problema. Además, no pude encontrar buenos ejemplos para eso.
¿Hay alguna forma de acelerar la construcción?
Respuestas:
La compilación lleva mucho tiempo porque Gradle cada vez que se construye la imagen de Docker descarga todos los complementos y dependencias.
No hay forma de montar un volumen en el momento de creación de la imagen. Pero es posible introducir una nueva etapa que descargará todas las dependencias y se almacenará en caché como capa de imagen de Docker.
El complemento Gradle y la caché de dependencia se encuentran en
$GRADLE_USER_HOME/caches
.GRADLE_USER_HOME
debe establecerse en algo diferente a/home/gradle/.gradle
./home/gradle/.gradle
en la imagen principal de Gradle Docker se define como volumen y se borra después de cada capa de imagen.En el código de muestra
GRADLE_USER_HOME
se establece en/home/gradle/cache_home
.En la
builder
etapa de caché Gradle se copia para evitar la descarga de las dependencias de nuevo:COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
.El escenario
cache
se reconstruirá solo cuandobuild.gradle
se cambie. Cuando las clases Java son cambios, la capa de imagen en caché con todas las dependencias se reutiliza.Esta modificación puede reducir el tiempo de construcción, pero de forma más limpia de la construcción de imágenes acoplables con las aplicaciones Java es la horca por Google. Hay un complemento Jib Gradle que permite crear imágenes de contenedor para aplicaciones Java sin crear manualmente Dockerfile. Crear una imagen con la aplicación y ejecutar el contenedor es similar a:
fuente
build.gradle
contexto es definitivamente el camino a seguir. Al copiar solobuild.gradle
encache
usted, se asegura de que las dependencias solo se descargarán una vez si el archivo de compilación de Gradle no cambia (Docker volverá a usar el caché)Docker almacena en caché sus imágenes en "capas". Cada comando que ejecuta es una capa. Cada cambio que se detecta en una capa dada invalida las capas que le siguen. Si la memoria caché está invalidada, las capas invalidadas deben construirse desde cero, incluidas las dependencias .
Sugeriría dividir sus pasos de compilación. Tenga una capa anterior que solo copie la especificación de dependencia en la imagen, luego ejecute un comando que hará que Gradle descargue las dependencias. Una vez que esté completo, copie su fuente en la misma ubicación donde acaba de hacer eso y ejecute la compilación real.
De esta manera, las capas anteriores se invalidarán solo cuando cambien los archivos gradle.
No he hecho esto con Java / Gradle, pero he seguido el mismo patrón con un proyecto Rust, guiado por esta publicación de blog.
fuente
Puede probar y usar BuildKit (ahora activado por defecto en la última versión de Docker -compose 1.25 )
Consulte " Acelere la creación de imágenes Docker de su aplicación java con BuildKit! " De Aboullaite Med .
(Esto fue para Maven, pero la misma idea se aplica a Gradle)
fuente
Dockerfile
. Creo que es el problema del almacenamiento en caché. He intentado el almacenamiento en caché pero todavía descarga Gradle, etc. en cada ejecución. También probé diferentes combinaciones de destinos de volumen.RUN
, BuildKit siempre reconstruirá todo en cada cambio de código (porque el contexto cambió), pero además con la respuesta @Evgeniy Khyst puede avanzar hacia un mejor resultadoNo sé mucho sobre los componentes internos de Docker, pero creo que el problema es que cada nuevo
docker build
comando copiará todos los archivos y los compilará (si detecta cambios en al menos un archivo). Entonces, lo más probable es que esto cambie varios frascos y los segundos pasos también deben ejecutarse.Mi sugerencia es construir en la terminal (fuera de Docker) y solo Docker construye la imagen de la aplicación.
Esto incluso se puede automatizar con un complemento de gradle:
fuente
Del mismo modo que responde una adición a otras personas, si su conexión a Internet es lenta, ya que descarga dependencias cada vez, es posible que desee configurar sonatype nexus, para mantener las dependencias ya descargadas.
fuente
Como las otras respuestas han mencionado, la ventana acoplable almacena en caché cada paso de una capa. Si de alguna manera pudiera obtener solo las dependencias descargadas en una capa, entonces no tendría que volver a descargarse cada vez, suponiendo que las dependencias no hayan cambiado.
Desafortunadamente, gradle no tiene una tarea incorporada para hacer esto. Pero aún puedes solucionarlo. Esto es lo que hice:
Además, asegúrese de que su
.dockerignore
archivo tenga al menos estos elementos, para que no se envíen en el contexto de compilación de la ventana acoplable cuando se genera la imagen:fuente