SpringBoot-高效打包Images
SpringBoot-高效打包Images
介绍
虽然将一个Jar打包为镜像很容易,只需一个基础JRE镜像就可生成一个SpringBoot项目镜像。
但这种方式创建出的镜像过于臃肿,升级下一个版本仍需重新将Jar打包到镜像中。
我们知道Docker镜像是分层打包的,所以我们可以将一些基础内容封装到基镜像中,之后更新部分用另一层处理。
解析SpringBoot Jar
一般情况下,我们运行SpringBoot项目就是打一个Jar包并通过java -jar *.jar
指令去运行。
但其实我们还可以将Jar包解压后再运行,这样的话方便我们对项目进行热部署和分层打包。
我们来打一个样例Jar来看看里面的内容,只要一个基础的接口功能即可:
qz-demo.jar
├─BOOT-INF
│ ├─classes
│ │ └─com
│ │ └─example
│ │ └─qz
│ │ └─config
│ └─lib
├─META-INF
│ └─maven
│ └─com.example
│ └─qz
└─org
└─springframework
└─boot
└─loader
├─archive
├─data
├─jar
├─jarmode
└─util
解压后是上述样子,我们可以看一下 BOOT-INF 是我们真正的项目代码、
META-INF 是一些源信息、剩下的就是SpringBoot编译出来的基础文件。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.loader;
import org.springframework.boot.loader.archive.Archive;
public class JarLauncher extends ExecutableArchiveLauncher {
static final Archive.EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/");
};
public JarLauncher() {
}
protected JarLauncher(Archive archive) {
super(archive);
}
protected boolean isPostProcessingClassPathArchives() {
return false;
}
protected boolean isNestedArchive(Archive.Entry entry) {
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}
protected String getArchiveEntryPathPrefix() {
return "BOOT-INF/";
}
public static void main(String[] args) throws Exception {
(new JarLauncher()).launch(args);
}
}
从org.springframework.boot.loader.JarLauncher
这个类中我们可以看到,它是我们程序的入口。
也就是我们通过java -jar
执行的入口。不难看出这里定义了代码与依赖的文件位置。
打开BOOT-INF
可以看到有两个*.idx
文件,classpath.idx
文件中定义了所有依赖的 lib :
- "BOOT-INF/lib/lombok-1.18.24.jar"
- "BOOT-INF/lib/spring-boot-2.7.1.jar"
- "BOOT-INF/lib/spring-boot-autoconfigure-2.7.1.jar"
- "BOOT-INF/lib/logback-classic-1.2.11.jar"
- "BOOT-INF/lib/logback-core-1.2.11.jar"
- "BOOT-INF/lib/log4j-to-slf4j-2.17.2.jar"
- "BOOT-INF/lib/log4j-api-2.17.2.jar"
- "BOOT-INF/lib/jul-to-slf4j-1.7.36.jar"
- "BOOT-INF/lib/jakarta.annotation-api-1.3.5.jar"
- "BOOT-INF/lib/snakeyaml-1.30.jar"
- "BOOT-INF/lib/spring-context-support-5.3.21.jar"
- "BOOT-INF/lib/spring-beans-5.3.21.jar"
- "BOOT-INF/lib/spring-context-5.3.21.jar"
- "BOOT-INF/lib/spring-tx-5.3.21.jar"
- "BOOT-INF/lib/quartz-2.3.2.jar"
- "BOOT-INF/lib/mchange-commons-java-0.2.15.jar"
- "BOOT-INF/lib/slf4j-api-1.7.36.jar"
- "BOOT-INF/lib/jackson-databind-2.13.3.jar"
- "BOOT-INF/lib/jackson-annotations-2.13.3.jar"
- "BOOT-INF/lib/jackson-core-2.13.3.jar"
- "BOOT-INF/lib/jackson-datatype-jdk8-2.13.3.jar"
- "BOOT-INF/lib/jackson-datatype-jsr310-2.13.3.jar"
- "BOOT-INF/lib/jackson-module-parameter-names-2.13.3.jar"
- "BOOT-INF/lib/tomcat-embed-core-9.0.64.jar"
- "BOOT-INF/lib/tomcat-embed-el-9.0.64.jar"
- "BOOT-INF/lib/tomcat-embed-websocket-9.0.64.jar"
- "BOOT-INF/lib/spring-web-5.3.21.jar"
- "BOOT-INF/lib/spring-webmvc-5.3.21.jar"
- "BOOT-INF/lib/spring-aop-5.3.21.jar"
- "BOOT-INF/lib/spring-expression-5.3.21.jar"
- "BOOT-INF/lib/spring-core-5.3.21.jar"
- "BOOT-INF/lib/spring-jcl-5.3.21.jar"
- "BOOT-INF/lib/spring-boot-jarmode-layertools-2.7.1.jar"
可以看到这些都是我们初始创建SpringBoot项目所依赖的基础Jar。layers.idx
文件中定义了项目的一些依赖信息:
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/classpath.idx"
- "BOOT-INF/layers.idx"
- "META-INF/"
剩下的两个文件相信都很熟悉,这里我就不在赘述。
Java提供了解压Jar包的功能,我们可以用jar -xf *.jar
来解压Jar包。
并通过java org.springframework.boot.loader.JarLauncher
的方式来运行我们的项目。
这样的运行方式,相对与Jar的运行要快上一些,当然也方便我们以后做热部署之类的功能。
当然除了上述的启动方式外,你还可以直接运行自己写好的SpringBootApplication
类来启动。java -cp BOOT-INF/classes:BOOT-INF/lib/* com.example.qz.SpringbootQzDemoApplication
像这样。
当然用 JarLauncher 运行具有可预测的类路径顺序的好处。
Jar 包含一个 classpath.idx 文件,JarLauncher 在构建类路径时使用该文件。
分层打包Image
为了更容易创建优化的 Docker 镜像,Spring Boot 支持向 Jar 中添加层索引文件。
它提供了层列表和应包含在其中的 Jar 的部分。索引中的层列表是根据应将层添加到 Docker/OCI 映像的顺序进行排序的。
也就是之前提到的layers.idx
这个文件。
FROM eclipse-temurin:11-jre as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM eclipse-temurin:11-jre
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"]
假设Dockerfile的路径为项目根路径,我们可以通过docker build .
来开始编译过程。
或者使用
docker build --build-arg JAR_FILE=path/to/myapp.jar .
来编译也是可以的