Кэш-память докеров gradle
Я пытаюсь развернуть наше веб-приложение на Java с помощью docker для создания гибкого beanstalk. Идея состоит в том, чтобы иметь возможность запускать контейнер локально для разработки и тестирования и, в конечном итоге, запустить его в производство с помощью git.
Я создал базовый образ, на котором установлены tomcat8 и java8, образ, который выполняет сборку gradle, наследуется от этого базового образа, ускоряя процесс сборки.
Все работает хорошо, за исключением того факта, что контейнер наследуемого приложения, который создается с помощью docker, похоже, не кэширует зависимости gradle, он загружает его каждый раз, включая gradlew. Мы создаем наше веб-приложение, используя следующую команду:
./gradlew war
Есть ли какой-нибудь способ, которым я могу кэшировать файлы в ~/.gradle
это значительно ускорит мою сборку.
Это не такая большая проблема для beanstalk, но это большая проблема для разработчиков, пытающихся создавать и запускать локально, так как это занимает много времени, как вы можете себе представить.
Базовый образ dockerfile:
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"]
Dockerfile приложения:
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"]
Ответы
Ответ 1
Я столкнулся с этой проблемой. Как вы можете согласиться, лучше всего загружать зависимости отдельно как отдельный шаг при создании образа докера. С Gradle становится немного сложнее, поскольку нет прямой поддержки загрузки только зависимостей.
Вариант 1. Использование Docker-Gradle.
Мы можем использовать готовый образ док-станции gradle для сборки приложения. Это гарантирует, что это не локальная сборка системы, а сборка, выполненная на чистом образе докера.
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
- Gradle Cache загружается здесь как том. Таким образом, последующие сборки будут повторно использовать загруженные зависимости.
- После этого у нас может быть Dockerfile, чтобы взять этот артефакт и сгенерировать специфический образ приложения для запуска приложения.
- Таким образом, изображение строителя не требуется. Поток сборки приложения и поток запуска приложения разделены.
- Поскольку том кэша gradle-кэша смонтирован, мы можем повторно использовать загруженные зависимости в разных проектах gradle.
Вариант 2: многоэтапная сборка
----- 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]
В приведенном выше Dockerfile
- Сначала мы пытаемся скопировать только файлы проекта gradle, такие как build.gradle, gradlew и т.д.,
- Затем мы копируем сам каталог Gradle
- И тогда мы пытаемся запустить сборку. На данный момент в каталоге нет других файлов с исходным кодом. Так что строить не удастся. Но перед этим он загрузит зависимости.
- Поскольку мы ожидаем, что сборка не удастся, я попробовал простую технику, чтобы вернуть 0 и позволить докеру продолжить выполнение
- это ускорит последующие потоки сборки, так как все зависимости загружаются и докер кэширует этот слой. Для сравнения, объемное монтирование каталога кэширования gradle все еще является лучшим подходом.
- Вышеприведенный пример также демонстрирует многоэтапное построение образа докера, которое позволяет избежать нескольких файлов сборки докера.
Ответ 2
I
Добавьте задачу resolveDependencies в build.gradle:
task resolveDependencies {
doLast {
project.rootProject.allprojects.each { subProject ->
subProject.buildscript.configurations.each { configuration ->
configuration.resolve()
}
subProject.configurations.each { configuration ->
configuration.resolve()
}
}
}
}
и обновить файл Docker:
ADD build.gradle /opt/app/
WORKDIR /opt/app
RUN gradle resolveDependencies
ADD . .
RUN gradle build -x test --parallel && \
touch build/libs/api.jar
II
Ниже приводится то, что я делаю сейчас:
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
Ответ 3
Возможно, вам стоит рассмотреть возможность разбиения образа приложения на два изображения: один для создания myapp.war и другого для запуска вашего приложения. Таким образом, вы можете использовать тоны докеров во время фактической сборки и привязать папку хоста ~/.gradle
к контейнеру, выполняющему сборку. Однако вместо одного шага для запуска вашего приложения у вас будет больше шагов. Пример:
Изображение строителя
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"]
Изображение приложения
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"]
Как использовать в корне вашего проекта, предполагая, что там расположен файл Docker, и файл приложения Dockerfile находится в подпапке webapp
(или любом другом пути, который вы предпочитаете):
$ 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
Я не тестировал показанный код, но надеюсь, что вы поняли эту идею. Пример может быть даже улучшен за счет использования томов данных для .gradle/cache. Подробнее см. В руководстве пользователя Docker .
Ответ 4
В качестве документации: https://docs.gradle.org/5.4.1/userguide/dependency_cache.html#header
Кэш зависимостей Gradle состоит из двух типов хранилищ, расположенных в GRADLE_USER_HOME/caches
Не .gradle
.
Ответ 5
попробуйте изменить домашний каталог пользователя gradle
RUN mkdir -p/opt/gradle/.gradle
ENV GRADLE_USER_HOME =/opt/ gradle/. Gradle