web-dev-qa-db-fra.com

Dépendances de Gradle du cache Docker

J'essaie de déployer notre Java sur aws élastique beanstalk à l'aide de docker, l'idée est de pouvoir exécuter le conteneur localement pour le développement et les tests et finalement le pousser à la production en utilisant git .

J'ai créé une image de base sur laquelle Tomcat8 et Java8 sont installés, l'image qui exécute les versions gradle héritées de cette image de base, accélérant ainsi le processus de génération.

Tout fonctionne bien, sauf que le conteneur d'application hérité qui est construit à l'aide de docker ne semble pas mettre en cache les dépendances de gradle, il le télécharge à chaque fois, y compris gradlew. Nous construisons notre application web en utilisant la commande suivante:

./gradlew war

Existe-t-il un moyen de mettre en cache les fichiers dans ~/.gradle cela accélérerait considérablement ma construction.

Ce n'est pas vraiment un problème sur Beanstalk mais c'est un gros problème pour les développeurs qui essaient de construire et d'exécuter localement car cela prend beaucoup de temps, comme vous pouvez l'imaginer.

Le fichier docker de l'image de base:

FROM phusion/baseimage
EXPOSE 8080
RUN apt-get update
RUN add-apt-repository ppa:webupd8team/Java
RUN apt-get update
RUN echo Oracle-Java8-installer shared/accepted-Oracle-license-v1-1 select true | Sudo /usr/bin/debconf-set-selections
RUN apt-get -y install Oracle-Java8-installer
RUN Java -version
ENV Tomcat_VERSION 8.0.9
RUN wget --quiet --no-cookies http://archive.Apache.org/dist/Tomcat/tomcat-8/v${Tomcat_VERSION}/bin/Apache-Tomcat-${Tomcat_VERSION}.tar.gz -O /tmp/catalina.tar.gz
# Unpack
RUN tar xzf /tmp/catalina.tar.gz -C /opt
RUN mv /opt/Apache-Tomcat-${Tomcat_VERSION} /opt/Tomcat
RUN ln -s /opt/Tomcat/logs /var/log/Tomcat
RUN rm /tmp/catalina.tar.gz
# Remove unneeded apps
RUN rm -rf /opt/Tomcat/webapps/examples
RUN rm -rf /opt/Tomcat/webapps/docs
RUN rm -rf /opt/Tomcat/webapps/ROOT
ENV CATALINA_HOME /opt/Tomcat
ENV PATH $PATH:$CATALINA_HOME/bin
ENV CATALINA_OPTS $PARAM1
# Start Tomcat
CMD ["/opt/Tomcat/bin/catalina.sh", "run"]

Le Dockerfile d'application:

FROM <tag name here for base image>
RUN mkdir ~/.gradle
# run some extra stuff here to add things to gradle.properties file
# Add project Source
ADD . /var/app/myapp
# Compile and Deploy Application, this is what is downloading gradlew and all the maven dependencies every time, if only there was a way to take the changes it makes to ~/.gradle and persist it as a cache layer
RUN cd /var/app/myapp/ && ./gradlew war
RUN mv /var/app/myapp/build/libs/myapp.war /opt/Tomcat/webapps/ROOT.war
# Start Tomcat
CMD ["/opt/Tomcat/bin/catalina.sh", "run"]
32
martin treurnicht

J'ai fait face à ce problème. Comme vous pouvez en convenir, il est recommandé de télécharger les dépendances seules en tant qu'étape distincte lors de la création de l'image docker. Cela devient peu délicat avec gradle, car il n'y a pas de support direct pour télécharger uniquement les dépendances.

Option 1: utilisation de l'image Docker-gradle Docker


Nous pouvons utiliser l'image Docker Gradle pré-construite pour créer l'application. Cela garantit qu'il ne s'agit pas d'une génération de système local mais d'une génération effectuée sur une image de docker propre.

docker volume create --name gradle-cache
docker run --rm -v gradle-cache:/home/gradle/.gradle -v "$PWD":/home/gradle/project -w /home/gradle/project gradle:4.7.0-jdk8-Alpine gradle build
ls -ltrh ./build/libs
  • le cache gradle est chargé ici en tant que volume. Les versions ultérieures réutilisent donc les dépendances téléchargées.
  • Après cela, nous pourrions avoir un Dockerfile pour prendre cet artefact et générer une image spécifique à l'application pour exécuter l'application.
  • De cette façon, l'image du générateur n'est pas requise. Le flux de génération d'application et le flux d'exécution d'application sont séparés.
  • Puisque le volume gradle-cache est monté, nous pourrions réutiliser les dépendances téléchargées sur différents projets gradle.

Option 2: construction en plusieurs étapes


----- Dockerfile -----

FROM openjdk:8 AS TEMP_BUILD_IMAGE
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY build.gradle settings.gradle gradlew $APP_HOME
COPY gradle $APP_HOME/gradle
RUN ./gradlew build || return 0 
COPY . .
RUN ./gradlew build

FROM openjdk:8
ENV ARTIFACT_NAME=your-application.jar
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY --from=TEMP_BUILD_IMAGE $APP_HOME/build/libs/$ARTIFACT_NAME .
EXPOSE 8080
CMD ["Java","-jar",$ARTIFACT_NAME]

Dans le Dockerfile ci-dessus

  • Nous essayons d'abord de copier seuls les fichiers de notes du projet, comme build.gradle, gradlew etc.,
  • Ensuite, nous copions le répertoire gradle lui-même
  • Et puis nous essayons d'exécuter la build. À ce stade, il n'existe aucun autre fichier de code source dans le répertoire. La construction échouera donc. Mais avant cela, il téléchargera les dépendances.
  • Puisque nous nous attendons à ce que la construction échoue, j'ai essayé une technique simple pour retourner 0 et permettre au docker de continuer l'exécution
  • cela accélérera les flux de génération suivants, car toutes les dépendances sont téléchargées et docker a mis en cache cette couche. Comparativement, le montage en volume du répertoire de cache gradle reste la meilleure approche.
  • L'exemple ci-dessus présente également la construction image de docker à plusieurs étapes , qui évite plusieurs fichiers de construction de docker.
13
Sairam Krish

Je

Ajouter resolverDépendances tâche dans build.gradle:

task resolveDependencies {
    doLast {
        project.rootProject.allprojects.each { subProject ->
            subProject.buildscript.configurations.each { configuration ->
                configuration.resolve()
            }
            subProject.configurations.each { configuration ->
                configuration.resolve()
            }
        }
    }
}

et mettre à jour Dockerfile:

ADD build.gradle /opt/app/
WORKDIR /opt/app
RUN gradle resolveDependencies

ADD . .

RUN gradle build -x test --parallel && \
    touch build/libs/api.jar

II

Voici ce que je fais maintenant:

build.gradle

ext {
    speed = project.hasProperty('speed') ? project.getProperty('speed') : false
    offlineCompile = new File("$buildDir/output/lib")
}

dependencies {
    if (speed) {
        compile fileTree(dir: offlineCompile, include: '*.jar')
    } else {
        // ...dependencies
    }
}

task downloadRepos(type: Copy) {
    from configurations.all
    into offlineCompile
}

Dockerfile

ADD build.gradle /opt/app/
WORKDIR /opt/app

RUN gradle downloadRepos

ADD . /opt/app
RUN gradle build -Pspeed=true
11
Daniel Wei

Vous voudrez peut-être envisager de diviser l'image de votre application en deux images: l'une pour la construction de myapp.war et l'autre pour l'exécution de votre application. De cette façon, vous pouvez utiliser des volumes de docker pendant la génération réelle et lier le ~/.gradle dossier dans le conteneur effectuant la construction. Au lieu d'une seule étape pour exécuter votre application, vous auriez cependant plus d'étapes. Exemple:

image de générateur

FROM <tag name here for base image including all build time dependencies>

# Add project Source
# -> you can use a project specific gradle.properties in your project root
# in order to override global/user gradle.properties
ADD . /var/app/myapp

RUN mkdir -p /root/.gradle
ENV HOME /root
# declare shared volume path
VOLUME /root/.gradle
WORKDIR /var/app/myapp/ 

# Compile only
CMD ["./gradlew", "war"]

image d'application

FROM <tag name here for application base image>

ADD ./ROOT.war /opt/Tomcat/webapps/ROOT.war

# Start Tomcat
CMD ["/opt/Tomcat/bin/catalina.sh", "run"]

Comment utiliser dans la racine de votre projet, en supposant que le générateur Dockerfile s'y trouve et que l'application Dockerfile se trouve dans le sous-dossier webapp (ou tout autre chemin que vous préférez):

$ docker build -t builder .
$ docker run --name=build-result -v ~/.gradle/:/root/.gradle/ builder
$ docker cp build-result:/var/app/myapp/myapp.war webapp/ROOT.war
$ cd webapp
$ docker build -t application .
$ docker run -d -P application

Je n'ai pas testé le code affiché, mais j'espère que vous avez compris l'idée. L'exemple pourrait même être amélioré en utilisant des volumes de données pour le .gradle/cache, voir le Docker guide de l'utilisateur pour plus de détails.

7
gesellix