Giriş
Burada amaç üretilen fat jar'ı katmanlara bölmek ve bunları layer olarak Docker image'a kopyalamak. Açıklaması şöyle
This uber jar can easily reach 400MB or more. This means that for every new build, even for the simplest code change in our application:- A new 400MB layer will be created- The layer will be uploaded to your OCI registry- When Kubernetes pulls the latest image to the node that runs the container, the entire 400 MB layer will need to be pulled.This will happen on every single code change and rebuild of the image. In reality, only a very small sliver of compiled code has changed. Dockerfile treats each new line as a new layer, so it would make a lot more sense to put the third-party dependencies in their own layer and have our custom code in its own layer. Spring boot provides a way to explode the jar into layers from 2.3 onwards.
Fat jar'ı katmanlara bölmek için komut şöyle. Eğer sadece listelemek istersek extract yerine list yaparız
RUN java -Djarmode=layertools -jar /home/app/target/*.jar extract
Ortaya çıkan layer'lar şöyle
Dependencies — the Spring Boot and other frameworks’ release-based dependencies. These only change when we upgrade to a Spring Boot version or the third-party framework version.Spring Boot Loader — this is the code that loads our Spring Boot app into the JVM to manage the bean lifecycle, among other things. This also rarely changes.Snapshot Dependencies — these dependencies are changing much more often. It’s possible that on each new build it would require us to pull the latest snapshot. As a result, this layer is the closest to our application code.Application — This is our application code from src/main/java (in the case of maven).
Eğer custom layer yaratmak istersek layers.xml dosyası tanımlarız. Şöyle yaparız
<layers xmlns="http://www.springframework.org/schema/boot/layers" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/boot/layers https://www.springframework.org/schema/boot/layers/layers-2.7.xsd"> <application> <into layer="spring-boot-loader"> <include>org/springframework/boot/loader/**</include> </into> <into layer="application" /> </application> <dependencies> <into layer="snapshot-dependencies"> <include>*:*:*SNAPSHOT</include> </into> <into layer="custom-dependencies"> <include>com.company:*</include> </into> <into layer="dependencies" /> </dependencies> <layerOrder> <layer>dependencies</layer> <layer>spring-boot-loader</layer> <layer>snapshot-dependencies</layer> <layer>custom-dependencies</layer> <layer>application</layer> </layerOrder> </layers>
Şöyle yaparız
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifacdId>lombok</artifacdId> </exclude> </excludes> <layers> <enabled>true</enabled> <configuration>${project.basedir}/src/layers.xml</configuration> </layers> </configuration> </plugin>
Örnek
Şöyle yaparız
#Multi Stage Docker Build #STAGE 1 - Build the layered jar #Use Maven base image FROM maven-3.8.3-openjdk-17 AS builder COPY src /home/app/src COPY pom.xml /home/app #Build an uber jar RUN mvn -f /home/app/pom.xml package WORKDIR /home/app/target #Extract the uber jar into layers RUN java -Djarmode=layertools -jar /home/app/target/*.jar extract #STAGE 2 - Use the layered jar to run Spring Boot app #Use OpenJDK17 base image FROM openjdk:17-alpine USER root #Copy individual layers one by one COPY --from=builder /home/app/target/dependencies/ ./ #Add this to fix a bug which happens during sequential copy commands RUN true COPY --from=builder /home/app/target/spring-boot-loader/ ./ RUN true COPY --from=builder /home/app/target/snapshot-dependencies/ ./ RUN true COPY --from=builder /home/app/target/custom-dependencies/ ./ RUN true COPY --from=builder /home/app/target/application/ ./ #Expose port on which Spring Boot app will run EXPOSE 8080 #Switch to non root user USER 1001 #Start Spring Boot app ENTRYPOINT ["java","org.springframework.boot.loader.JarLauncher"]
Örnek - adoptopenjdk/maven-openjdk11
Şöyle yaparız
# STAGE 1 FROM adoptopenjdk/maven-openjdk11 as builder WORKDIR application COPY mvnw . COPY .mvn .mvn COPY pom.xml . COPY src src RUN ./mvnw install -DskipTests RUN java -Djarmode=layertools -jar target/rest-service-0.0.1-SNAPSHOT.jar extract # STAGE 2 FROM adoptopenjdk:11-jre-hotspot WORKDIR application COPY --from=builder application/dependencies/ ./ COPY --from=builder application/spring-boot-loader/ ./ COPY --from=builder application/snapshot-dependencies/ ./ COPY --from=builder application/application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
Açıklaması şöyle. Bu yöntemin özelliği Docker cache özelliğinin de devreye girmesiyle, derleme zamanının çok hızlanması
This approach uses a maven image in Stage 1. Therefore, you do not need to have maven installed on your machine to run the maven build commands.This approach uses a jre base image in Stage 2. Furthermore it uses layers as opposed to a fat jar.There are four layers consisting of the following:
- java dependencies
- Spring Boot loader
- snapshot dependencies
- application code
These four dependencies are organized in order of least likely to change. Naturally, the application code is ordered last since it changes the most often. Therefore, when application code changes, only the java files that have changed need to be compiled and added to the new image during a build. The docker build engine can use the cached layers created from previous build to reuse the other layers.
Örnek - openjdk:17-alpine
Şöyle yaparız. Burada dockerfile-maven-plugin kullanılıyor. maven install komutu ile önce jar yapılandırılıyor. En son olarak ta Docker dosyası çalıştırılıyor
FROM openjdk:17-alpine as builder WORKDIR application ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} application.jar RUN java -Djarmode=layertools -jar application.jar extract FROM openjdk:17-alpine WORKDIR application COPY --from=builder application/dependencies/ ./ COPY --from=builder application/spring-boot-loader/ ./ COPY --from=builder application/snapshot-dependencies/ ./ COPY --from=builder application/application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
Örnek - eclipse-temurin:17.0.4_8-jdk-alpine
Şöyle yaparız
# Making here use of a docker multi-stage build # https://docs.docker.com/develop/develop-images/multistage-build/ # Build-time container FROM eclipse-temurin:17.0.4_8-jdk-alpine as builder ARG JAR_FILE WORKDIR application COPY $JAR_FILE application.jar COPY build_application.sh ./ RUN sh build_application.sh # Run-time container FROM alpine:3.16.2 ARG APP_NAME=app ARG USER=exie ARG GROUP=party ARG LOG_FOLDER=/srv/app/logs ENV APP=$APP_NAME \ JAVA_HOME=/opt/java \ PATH="${JAVA_HOME}/bin:${PATH}" ## Adding programs for operation RUN apk update && \ apk --no-cache add dumb-init curl jq jattach && \ rm -rf /var/cache/apk/* WORKDIR /srv/$APP COPY run_application.sh /etc RUN addgroup -S $GROUP && \ adduser -S -D -H $USER -G $GROUP && \ mkdir -p $LOG_FOLDER && \ chgrp $GROUP $LOG_FOLDER && \ chmod g+rwx $LOG_FOLDER && \ chmod +x /etc/run_application.sh ## Application-specif created JRE COPY --from=builder /opt/java-runtime $JAVA_HOME ## Spring Boot Layers COPY --from=builder application/spring-boot-loader/ ./ COPY --from=builder application/dependencies/ ./ COPY --from=builder application/snapshot-dependencies/ ./ COPY --from=builder application/application/ ./ USER $USER ENTRYPOINT ["/usr/bin/dumb-init", "--"] CMD ["/etc/run_application.sh"]
build.sh şöyle
#!/bin/sh JAR_FILE=application.jar if [ ! -f "$JAR_FILE" ]; then echo "build.error: application jar is missing!" exit 1 fi jar xf application.jar REQUIRED_JAVA_MODULES="$(jdeps \ --print-module-deps \ --ignore-missing-deps \ --recursive \ --multi-release 17 \ --class-path="./BOOT-INF/lib/*" \ --module-path="./BOOT-INF/lib/*" \ ./application.jar)" if [ -z "$REQUIRED_JAVA_MODULES" ]; then echo "build.error: required java modules are not determined!" exit 1 fi jlink \ --no-header-files \ --no-man-pages \ --compress 2 \ --add-modules "${REQUIRED_JAVA_MODULES}" \ --output /opt/java-runtime java -Djarmode=layertools -jar application.jar extract exit 0
run_application.sh şöyle
#!/bin/sh JAVA_OPTS="-Dfile.encoding=UTF-8 -Duser.timezone=UTC -XX:NativeMemoryTracking=summary -XX:+HeapDumpOnOutOfMemoryError" PORT="8080" cd /srv/"$APP" || exit /opt/java/bin/java $JAVA_OPTS org.springframework.boot.loader.JarLauncher --bind 0.0.0.0:$PORT
Hiç yorum yok:
Yorum Gönder