背景
在云原生的趋势下,越来越多的程序开始跑在容器上,而这就绕不开构建镜像这个操作,而镜像的大小又会关系到这个镜像的拉取时间、网络开销、磁盘占用等问题。因此如何能够更好的构建镜像就变得越来越重要了。
优化过程
基础镜像
这里选用ibm-semeru-runtimes:open-17.0.3_7-jre-focal
和ibm-semeru-runtimes:open-17.0.3_7-jdk-focal
分别作为运行时和编译时的基础镜像。
这里都以gradle项目为例。
最初方式
Dockerfile
:
1 2 3 4 5
| FROM ibm-semeru-runtimes:open-17.0.3_7-jdk-focal
ARG JAR_FILE COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"]
|
shell
:
1 2 3
| cd /path/to/project ./gradlew build docker build --build-arg JAR_FILE=build/libs/your-spring-boot-version.jar -t myorg/myapp .
|
这种方式其实是将编译过程放在了外面,就会显得不那么优雅。因此我们决定将其放入image build 环节。
先编译
Dockerfile
:
1 2 3 4 5 6
| FROM ibm-semeru-runtimes:open-17.0.3_7-jdk-focal VOLUME /tmp COPY . / RUN ./gradlew build && / cp build/libs/your-application-version.jar /app.jar ENTRYPOINT ["java","-jar","/app.jar"]
|
shell
:
1
| docker build -t myorg/myapp .
|
此时你会发现这个image里包含了很多不必要的文件,都是在编译过程中生成的。这我们应该怎么优化呢,此时就要用到Docker的多阶段编译(Multi-Stage Build
)功能了。
![img]()
Multi-Stage Build
Dockerfile
:
1 2 3 4 5 6 7 8
| FROM ibm-semeru-runtimes:open-17.0.3_7-jdk-focal as builder VOLUME /tmp COPY . / RUN ./gradlew build
FROM ibm-semeru-runtimes:open-17.0.3_7-jre-focal COPY --from=builder /build/libs/your-application-version.jar /app.jar ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} ${JAVA_TOOL_OPTIONS} -jar app.jar ${0} ${@}"]
|
shell
:
1
| docker build -t myorg/myapp .
|
咋一看目前的dockerfile
已经足够完美了,似乎已经没有什么可以优化的了。其实不然,目前的image其实还是很大的,docker构建镜像是通过layer来进行叠加,从而达到能够复用已存在的模块来减少新版本镜像的改动部分大小,从而减少网络传输。
那layers是怎么生成的呢,其实很简单就是根据你Dockerfile
里的一行行指令,每个指令都对应一个layer:
![img]()
而spring boot
build产生的jar包其实是一个**fat jar
**,里面除了你自己应用的代码其他绝大部分都是可以复用的,因此我们可以对其进行分解处理。
Spring Boot Layer Index
当然这个有个前提条件,需要spring boot 版本大于 2.3.0
。
Dockerfile
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| FROM ibm-semeru-runtimes:open-17.0.3_7-jdk-focal as builder WORKDIR /workspace/app COPY . . RUN ./gradlew build RUN mkdir extracted && \ cd /workspace/app/build/libs && \ java -Djarmode=layertools -jar app.jar extract --destination /workspace/app/extracted
FROM ibm-semeru-runtimes:open-17.0.3_7-jre-focal VOLUME /tmp ARG EXTRACTED=/workspace/app/extracted COPY --from=builder ${EXTRACTED}/dependencies/ ./ COPY --from=builder ${EXTRACTED}/spring-boot-loader/ ./ COPY --from=builder ${EXTRACTED}/snapshot-dependencies/ ./ COPY --from=builder ${EXTRACTED}/application/ ./ ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} ${JAVA_TOOL_OPTIONS} org.springframework.boot.loader.JarLauncher ${0} ${@}"]
|
shell
:
1
| docker build -t myorg/myapp .
|
Cache Build Dependencies
目前的dockerfile你会发现,每次进行build时,都会先下载gradle,然后下载相关依赖包,会占用极大的带宽以及消耗大量时间,那这些数据是否可以共享呢,答案是当然可以了。
Dockerfile
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| FROM ibm-semeru-runtimes:open-17.0.3_7-jdk-focal as builder WORKDIR /workspace/app COPY . . RUN --mount=type=cache,target=/root/.gradle ./gradlew build RUN mkdir extracted && \ cd /workspace/app/build/libs && \ java -Djarmode=layertools -jar app.jar extract --destination /workspace/app/extracted
FROM ibm-semeru-runtimes:open-17.0.3_7-jre-focal VOLUME /tmp ARG EXTRACTED=/workspace/app/extracted COPY --from=builder ${EXTRACTED}/dependencies/ ./ COPY --from=builder ${EXTRACTED}/spring-boot-loader/ ./ COPY --from=builder ${EXTRACTED}/snapshot-dependencies/ ./ COPY --from=builder ${EXTRACTED}/application/ ./ ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} ${JAVA_TOOL_OPTIONS} org.springframework.boot.loader.JarLauncher ${0} ${@}"]
|
shell
:
1
| docker build -t myorg/myapp .
|
到目前为止对于image的优化,已经基本完成了,大家可以通过以下命令来观察每个image的各个layer的大小已经images之间的关系。
1
| docker history myorg/myapp
|