Estou fazendo um projeto universitário em que precisamos executar vários aplicativos Spring Boot ao mesmo tempo.
Eu já havia configurado a compilação em vários estágios com a imagem do gradle docker e, em seguida, execute o aplicativo na imagem openjdk: jre.
Aqui está o meu 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"]
Estou construindo e executando tudo com o docker-compose. Parte da janela de encaixe-compor:
website_server:
build: website-server
image: website-server:latest
container_name: "website-server"
ports:
- "81:8080"
É claro que a primeira compilação leva idades. O Docker está puxando todas as suas dependências. E eu estou bem com isso.
Tudo está funcionando bem por enquanto, mas cada pequena alteração no código causa cerca de 1 min de tempo de compilação para um aplicativo.
Parte do log de construção: 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
Toda vez que congela após Starting a Gradle Daemon (subsequent builds will be faster)
Eu estava pensando em adicionar volume com dependências gradle em cache, mas não sei se esse é o núcleo do problema. Também não consegui encontrar bons exemplos para isso.
Existe alguma maneira de acelerar a compilação?
Respostas:
O Build leva muito tempo porque o Gradle toda vez que a imagem do Docker é criada baixa todos os plugins e dependências.
Não há como montar um volume no tempo de criação da imagem. Mas é possível introduzir um novo estágio que fará o download de todas as dependências e será armazenado em cache como camada de imagem do Docker.
O plug-in Gradle e o cache de dependência estão localizados em
$GRADLE_USER_HOME/caches
.GRADLE_USER_HOME
deve ser definido como algo diferente de/home/gradle/.gradle
./home/gradle/.gradle
na imagem principal do Gradle Docker é definida como volume e é apagada após cada camada da imagem.No código de amostra
GRADLE_USER_HOME
está definido como/home/gradle/cache_home
.Na
builder
fase de cache Gradle é copiado para evitar o download as dependências novamente:COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
.O palco
cache
será reconstruído apenas quandobuild.gradle
for alterado. Quando as classes Java são alteradas, a camada de imagem em cache com todas as dependências é reutilizada.Essas modificações podem reduzir o tempo de criação, mas a maneira mais limpa de criar imagens do Docker com aplicativos Java é o Jib do Google. Há um plug-in Jib Gradle que permite criar imagens de contêiner para aplicativos Java sem criar manualmente o Dockerfile. Criar imagem com o aplicativo e executar o contêiner é semelhante a:
fonte
build.gradle
do contexto é definitivamente o caminho a percorrer. Copiando apenasbuild.gradle
emcache
você garante dependências só será transferido uma vez, se o arquivo de construção Gradle não muda (Docker vai voltar a usar o cache)O Docker armazena em cache suas imagens em "camadas". Cada comando que você executa é uma camada. Cada alteração detectada em uma determinada camada invalida as camadas que vêm depois dela. Se o cache for invalidado, as camadas invalidadas deverão ser criadas do zero, incluindo dependências .
Eu sugeriria dividir suas etapas de compilação. Tenha uma camada anterior que copie apenas a especificação de dependência na imagem e execute um comando que resultará no download das dependências pelo Gradle. Depois de concluído, copie sua fonte no mesmo local em que você acabou de fazer isso e execute a compilação real.
Dessa forma, as camadas anteriores serão invalidadas apenas quando os arquivos de gradação forem alterados.
Não fiz isso com Java / Gradle, mas segui o mesmo padrão em um projeto Rust, guiado por esta postagem no blog.
fonte
Você pode tentar usar o BuildKit (agora ativado por padrão na última janela de encaixe-composição 1.25 )
Consulte " Acelere as imagens do Docker do aplicativo java construídas com o BuildKit! " Da Aboullaite Med .
(Isso foi para maven, mas a mesma idéia se aplica a gradle)
fonte
Dockerfile
. Eu acho que é a questão do cache. Eu tentei fazer o cache, mas ele ainda baixa o Gradle etc. a cada execução. Eu tentei diferentes combinações de destinos de volume também.RUN
, o BuildKit sempre recriará tudo em cada alteração de código (porque o contexto mudou), mas além da resposta do @Evgeniy Khyst ela pode se mover em direção a um resultado melhorNão sei muito sobre o docker internals, mas acho que o problema é que cada novo
docker build
comando copia todos os arquivos e os cria (se detectar alterações em pelo menos um arquivo). Então isso provavelmente mudará vários frascos e os segundos passos também precisam ser executados.Minha sugestão é construir no terminal (fora da janela de encaixe) e apenas a janela de encaixe construir a imagem do aplicativo.
Isso pode até ser automatizado com um plugin gradle:
fonte
Assim como as respostas de outras pessoas, se a sua conexão à Internet estiver lenta, pois ele faz o download de dependências todas as vezes, convém configurar o sonatype nexus, a fim de manter as dependências já baixadas.
fonte
Como as outras respostas mencionaram, a janela de encaixe armazena em cache cada etapa de uma camada. Se você pudesse, de alguma forma, obter apenas as dependências baixadas em uma camada, não seria necessário fazer o download novamente a cada vez, assumindo que as dependências não foram alteradas.
Infelizmente, o gradle não tem uma tarefa interna para fazer isso. Mas você ainda pode contornar isso. Aqui está o que eu fiz:
Além disso, verifique se o
.dockerignore
arquivo possui pelo menos esses itens, para que não sejam enviados no contexto de construção da janela de encaixe quando a imagem for criada:fonte