Spring Boot 分层构建 Docker 镜像实战
大家好,我是你们的鸭哥
记得点击关注下方公众号,领取 编程资料
FROM openjdk:8u275
VOLUME /tmp
ADD target/*.jar app.jar
ENV TZ="Asia/Shanghai"
ENV JAVA_OPTS=""
ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar"]
## 构建镜像的命令
$ docker build -t java-test:latest .
## 命令执行的过程
Step 1/7 : FROM openjdk:8u275
---> 82f24ce79de6
Step 2/7 : VOLUME /tmp
---> Running in a6361fdfc193
Removing intermediate container a6361fdfc193
---> a43948bf1b98
Step 3/7 : ADD target/*.jar app.jar
---> 18f4bc60818f
Step 4/7 : ENV TZ="Asia/Shanghai"
---> Running in cc738aa5865b
Removing intermediate container cc738aa5865b
---> 538adb85609e
Step 5/7 : ENV JAVA_OPTS=""
---> Running in f8b635d32b2b
Removing intermediate container f8b635d32b2b
---> 34e7a8cd7b6e
Step 6/7 : ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"
---> Running in 9331cb6e443e
Removing intermediate container 9331cb6e443e
---> 232b9c6c1d29
Step 7/7 : ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]
---> Running in c3a24fba3a10
Removing intermediate container c3a24fba3a10
---> a41974d5f0e3
FROM openjdk:8u275
VOLUME /tmp
ADD target/*.jar app.jar
ENV TZ="Asia/Macao" #与原来 Dockerfile 不同
ENV JVM_OPTS="-Xmx512m -Xss256k" #与原来 Dockerfile 不同
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]
$ docker build -t java-test2:latest .
Step 1/6 : FROM openjdk:8u275
---> 82f24ce79de6
Step 2/6 : VOLUME /tmp
---> Using cache
---> a43948bf1b98
Step 3/6 : ADD target/*.jar app.jar
---> Using cache
---> 18f4bc60818f
Step 4/6 : ENV TZ="Asia/Macao"
---> Running in fd98b90a5485
Removing intermediate container fd98b90a5485
---> afab3fcdab07
Step 5/6 : ENV JVM_OPTS="-Xmx512m -Xss256k"
---> Running in 19a99576fba9
Removing intermediate container 19a99576fba9
---> 4eeab7d7c720
Step 6/6 : ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]
---> Running in 2dba72e1eef4
Removing intermediate container 2dba72e1eef4
---> 7c706ecf7698
镜像缓在镜像推送的体现:如镜像推送时候,也是将镜像整体构成的中间层镜像并行推送到镜像仓库,如果镜像仓库中已经存某个中间层镜像,那么推送过程就不会再次将该层镜像推送到镜像仓库,而是将仓库中并不存在中间层镜像推送到其中。
镜像缓存在镜像拉取的体现:在拉取镜像时候,如果本地某个大镜像的中间层镜像的组成中,已经包含新拉取镜像的中间层部分镜像,那么将直接复用本地已经镜像的中间层镜像,不必再将其进行拉取,而本地不存在的中间层镜像将会被继续拉取。
# SpringBoot 2.3.x 新增对分层的支持
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
<relativePath/>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--开启分层编译支持-->
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>
$ mvn install
classpath.idx:文件列出了依赖的 jar 包列表,到时候会按照这个顺序载入。
layers.idx:文件清单,记录了所有要被复制到 Dokcer 镜像中的文件信息。
$ java -Djarmode=layertools -jar target/springboot-layer-0.0.1.jar list
dependencies
spring-boot-loader
snapshot-dependencies
application
dependencies:存储项目正常依赖 Jar 的文件夹。
snapshot-dependencies:存储项目快照依赖 Jar 的文件夹。
resources:用于存储静态资源的文件夹。
application:用于存储应用程序类相关文件的文件夹。
# 创建测试的 SpringBoot 应用
1、Maven 中引入相关依赖和插件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
</parent>
<artifactId>springboot-dockerfile-layer</artifactId>
<packaging>jar</packaging>
<name>springboot-dockerfile-layer</name>
<description>springboot build layer example</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>
</project>
2、创建测试的 Controller 类
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/hello")
public String hello() {
return "hello world!";
}
}
3、创建 SpringBoot 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
# 创建两种构建镜像的 Dockerfile 脚本
FROM openjdk:8u275
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV TZ="Asia/Shanghai"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
TZ:时区设置,而 Asia/Shanghai 表示使用中国上海时区。
JVM_OPTS:指定 JVM 启动时候的参数,-XX:MaxRAMPercentage 参数和 -Xmx 类似,都是限制堆内存大小,只不过 -Xmx 需要手动指定限制大小,而 -XX:MaxRAMPercentage 则是根据虚拟机可用内存百分比限制。
JAVA_OPTS:在镜像启动时指定的自定义 Java 参数,例如 -Dspring.application.name=xxx。
2、分层镜像构建脚本文件 dockerfile-layer
FROM openjdk:8u275 as builder
WORKDIR application
COPY target/*.jar application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM openjdk:8u275
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./
ENV TZ="Asia/Shanghai"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"
ENV JAVA_OPTS=""
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]
TZ:时区设置,而 Asia/Shanghai 表示使用中国上海时区。
-Djarmode=layertools:指定构建 Jar 的模式。
extract:从 Jar 包中提取构建镜像所需的内容。
-from=builder 多级镜像构建中,从上一级镜像复制文件到当前镜像中。
# 使用两种 Dockerfile 构建项目镜像
1、在服务器一构建普通 Docker 镜像
## 执行 Maven 命令,将源代码构建 Jar 包
$ mvn clean install
## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.1 .
## 修改 pom.xml 里面的依赖,随意添加一个新的依赖包,然后再次将源代码构建 Jar 包
$ mvn clean install
## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.2 .
## 修改源代码任意内容后,然后再次将源代码构建 Jar 包
$ mvn clean install
## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.3 .
2、在服务器二构建分层 Docker 镜像
## 执行 Maven 命令,将源代码构建 Jar 包
$ mvn clean install
## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.1 .
## 修改 pom.xml 里面的依赖,随意添加一个新的依赖包,然后再次将源代码构建 Jar 包
$ mvn clean install
## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.2 .
## 修改源代码任意内容后,然后再次将源代码构建 Jar 包
$ mvn clean install
## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.3 .
# 镜像推送到镜像仓库测试
1、推送镜像到镜像仓库测试
## 第一次推送镜像
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.1
real 0m35.215s
## 第二次推送镜像
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.2
real 0m14.051s
## 第三次推送镜像
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.3
real 0m14.183s
服务器二推送分层镜像到镜像仓库2:
## 第一次推送镜像
$ time docker push hub.mydlq.club/library/springboot-layer:0.0.1
real 0m34.121s
## 第二次推送镜像
$ time docker push hub.mydlq.club/library/springboot-layer:0.0.2
real 0m13.605s
## 第三次推送镜像
$ time docker push hub.mydlq.club/library/springboot-layer:0.0.3
real 0m4.805s
2、镜像仓库拉取镜像测试
## 清理全部镜像
$ docker rm --force $(docker images -qa)
## 拉取镜像 springboot-normal:0.0.1
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.1
real 0m35.395s
## 拉取镜像 springboot-normal:0.0.2
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.2
real 0m6.501s
## 拉取镜像 springboot-normal:0.0.3
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.3
real 0m6.993s
## 清理全部镜像
$ docker rm --force $(docker images -qa)
## 拉取镜像 springboot-layer:0.0.1
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.1
real 0m30.615s
## 拉取镜像 springboot-layer:0.0.2
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.2
real 0m4.811s
## 拉取镜像 springboot-layer:0.0.3
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.3
real 0m1.293s
# 镜像构建、推送、拉取时间汇总
1、不使用分层构建镜像
2、使用分层构建镜像
3、总结
镜像构建:在构建上,使用分层 Jar 构建镜像可能比普通方式构建镜像更繁琐,所以也更耗时,故而在构建上分层 Jar 构建镜像没有太多优势。
镜像推送:在推送上,如果每次构建镜像都只是修改构建镜像项目的源码,使用分层 Jar 构建镜像,可以大大加快镜像推送速度。
如果是修改构建镜像项目中的依赖包,则和普通构建一样速度很慢。
镜像拉取:拉取和推送类似,如果只修改构建镜像项目的源码,只会拉取源码相关的中间层镜像,该层非常小(一般几百KB),拉取速度自然非常快。