介绍
为了把Spring Boot应用部署到k8s环境中,我们需要提前把Spring Boot应用打包成OCI镜像(一般是Docker镜像)。而这个过程我们希望可以跟Maven等打包工具集成,以便在执行mvn package
命令的时候,可以直接生成镜像。
可选方案
Spotify的dockerfile-maven-plugin
这是一个从2016年开始的项目,提供了一个maven plugin,用在来打包的时候,生成一个镜像。它的使用步骤如下:
1)在pom.xml文件中,加上这个plugin的配置
xml
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>dadaer.com/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
2)在项目根目录下,增加一个Dockerfile,描述镜像内容
sh
FROM openjdk:17.0.2-oraclelinux7
EXPOSE 17001
WORKDIR /opt
ARG JAR_FILE
ADD target/${JAR_FILE} /opt/app.jar
ENTRYPOINT ["java", "-jar","/opt/app.jar"]
在MacOS M1上使用Spotify的dockerfile-maven-plugin
在MacOS M1上面,dockerfile-maven-plugin不工作,在stack overflow上,有人给出了解决办法,如下:
xml
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.6</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>dadaer.com/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
<!-- 为了让这个插件在Mac M1上工作,需要使用下面的依赖,同时,plugin的版本必须是1.4.6-->
<dependencies>
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-unixsocket</artifactId>
<version>0.38.17</version>
</dependency>
</dependencies>
</plugin>
即:
- 在plugin中增加依赖:com.github.jnr:jnr-unixsocket:0.38.17
- plugin的版本必须是1.4.6
这其实更像一个workaround,只能限定在特定的版本上。但至少是工作的:)
Deprecation提示
在2022年4月1日,该项目已经被作者归档了,不再演进新的功能,也不会修复非严重问题。
At this point, we're not developing or accepting new features or even fixing non-critical bugs.
fabric8io的fabric8-maven-plugin
这个plug-in也被deprecate了(maven.fabric8.io/)
Fabric8 Maven Plugin is deprecated and is no longer supported. Please consider migrating to Eclipse JKube plugins: Kubernetes Maven Plugin or OpenShift Maven Plugin . You can read the Migration Guide for more details.
fabric8io的docker-maven-plugin
用于管理Docker镜像以及容器(dmp.fabric8.io//#docker:bu...)
配置
docker-maven-plugin的配置包含两大部分:
- 全局配置,比如docker url、证书位置等等
- 一组具体的image配置列表
其中具体的image配置又可以分成如下四个部分:
- 镜像名字(name)、别名(alias)等通用配置
<build>
配置,用于描述如何生成镜像的配置<run>
配置,用于描述如何创建和启动容器的配置<copy>
配置,用于描述如何从容器中拷贝文件到宿主机的配置
示例1:通过Configuration构造镜像
不用编写Dockerfile,直接通过在docker-maven-plugin的<configuration>...</configuration>
配置生成镜像需要的指令。比如:
xml
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.43.0</version>
<configuration>
<images>
<image>
<name>dadaer.com/%a:%l</name>
<build>
<!-- 指定基础镜像 -->
<from>eclipse-temurin:17.0.7_7-jdk-jammy</from>
<!-- 指定暴露的端口 -->
<ports>
<port>17004</port>
</ports>
<!-- 拷贝jar包到容器的/app目录 -->
<assembly>
<mode>dir</mode>
<descriptorRef>artifact</descriptorRef>
<targetDir>/app</targetDir>
</assembly>
<!-- 指定容器启动的命令 -->
<entryPoint>
<exec>
<arg>java</arg>
<arg>-jar</arg>
<arg>/app/${project.build.finalName}.jar</arg>
</exec>
</entryPoint>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
如上所示,在plug-in的配置里面,通过xml的方式指定生成镜像需要执行的指令,本质上与写Dockerfile一样。在执行mvn package
命令后,可以查看生成的Dockerfile:target/docker/dadaer.com/fabric8io-docker-helloworld/1.0.0/build/Dockerfile
示例2:在项目根目录下写Dockerfile生成镜像
如果不喜欢使用xml的方式指定生成镜像需要的指令,也可以手写一个Dockerfile文件。最简单的方式是在项目的根目录(即与pom.xml同级目录)下写一个Dockerfile即可,而不需要再在docker-maven-plugin里面写任何配置。这种方式,只能用于生成一个镜像。比如:下面是一个Dockerfile:
sh
FROM eclipse-temurin:17.0.7_7-jdk-jammy
RUN mkdir -p /app
WORKDIR /app
ADD target/fabric8io-docker-helloworld-1.0.0.jar /app
EXPOSE 17004
ENTRYPOINT ["java", "-jar", "fabric8io-docker-helloworld-1.0.0.jar"]
在pom.xml中,只需要列出docker-maven-plugin,并指定执行的phase即可,不需要做额外的配置。
xml
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.43.0</version>
<executions>
<execution>
<id>build</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
示例3:在指定位置写Dockerfile生成镜像
上面的示例中,在一个项目中,只能生成一个镜像(满足大多数情况)。如果想要更加灵活,比如在一个项目中生成多个镜像,那么可以在docker-maven-plugin中指定Dockerfile文件的位置,并配合一些额外的配置(最常见的是assembly)来指定额外需要拷贝的文件。
比如:在目录src/main/docker目录下创建一个demo文件夹,在这个文件夹下,写一个Dockerfile文件。
sh
FROM eclipse-temurin:17.0.7_7-jdk-jammy
RUN mkdir -p /app
WORKDIR /app
# 这里的maven目录是assembly中的name值,默认是maven
ADD maven/fabric8io-docker-helloworld-1.0.0.jar /app
EXPOSE 17004
ENTRYPOINT ["java", "-jar", "fabric8io-docker-helloworld-1.0.0.jar"]
然后在pom.xml文件中,配置docker-maven-plugin如下:
xml
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.43.0</version>
<configuration>
<images>
<image>
<!-- 指定镜像的名字,可以使用%g,%a、%l等来表示项目的坐标GAV -->
<name>dadaer.com/%a:%l</name>
<build>
<!-- 告诉docker-maven-plugin,Dockerfile在src/main/docker/demo目录下 -->
<dockerFileDir>demo</dockerFileDir>
<!-- 把jar文件放到Dockerfile文件所在文件夹的maven子文件夹中 -->
<assemblies>
<assembly>
<descriptorRef>artifact</descriptorRef>
</assembly>
</assemblies>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
在执行mvn package
命令后,在目录target/docker/dadaer.com/fabric8io-docker-helloworld/1.0.0/build
下,包含了Dockerfile以及maven目录,而maven目录下包含了要执行的jar文件。
lua
target/docker/dadaer.com/fabric8io-docker-helloworld/1.0.0/build
+-- Dockerfile
+-- maven
+-- fabric8io-docker-helloworld-1.0.0.jar
JKube的kubernetes-maven-plugin
Eclipse JKube是一组plug-in和library,使用Docker、JIB或者S2I等策略来构建容器镜像。
这里指关注它提供的kubernetes-maven-plugin
,这个插件聚焦在两个任务上:
- 构建Docker镜像(Docker image)
- 生成k8s资源描述器(Kubernetes resource descriptor)
这个plug-in来自于fabric8io的docker-maven-plugin,所以在构建镜像方面,很多配置选项都相同,包括生成的目标文件和目录也相同。所以了解了docker-maven-plugin,对于理解JKube的kubernetes-maven-plugin有很大帮助。
示例1:Zero-Config withou Dockerfile
Kubernetes-maven-plugin采取了opinionated预配置,即:假定了你想要的各种参数的默认值。那么如果什么配置都不指定的情况下生成镜像,就是零配置(Zero-Config)。本示例展示了在没有配置、没有Dockerfile的情况下生成的镜像。
xml
<plugin>
<groupId>org.eclipse.jkube</groupId>
<artifactId>kubernetes-maven-plugin</artifactId>
<version>${jkube.version}</version>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
在执行mvn package
命令以后,会生成一个镜像,如果想查看生成镜像的Dockerfile文件,可以查看如下目录:
lua
target/docker/k8s/jkube-demo-helloworld/1.0.0/build
+-- Dockerfile
+-- application
+-- denpendencies
+-- deployments
+-- spring-boot-loader
生成的Dockerfile内容类似下面:
sh
FROM quay.io/jkube/jkube-java:0.0.20
ENV JAVA_MAIN_CLASS=org.springframework.boot.loader.JarLauncher JAVA_APP_DIR=/deployments
LABEL org.label-schema.description="Parent pom providing dependency and plugin management for applications built with Maven" org.label-schema.version=1.0.0 org.label-schema.schema-version=1.0 org.label-schema.build-date=2023-11-26 org.label-schema.name=jkube-demo-helloworld org.label-schema.vcs-ref=93cecba013734ac0d6b3979f77e653940f784d2f
EXPOSE 8080 8778 9779
COPY /dependencies/deployments /deployments/
COPY /spring-boot-loader/deployments /deployments/
COPY /application/deployments /deployments/
WORKDIR /deployments
可以看到,在Zero-Confix情况下:
- 基础镜像:quay.io/jkube/jkube-java:0.0.20
- 暴露端口,除了我们自己的应用,还有两个:8778 for Jolokia和9779 for jmx_exporter
- 拷贝了所有的依赖以及classes文件到容器的/deployments目录
- 工作目录事/deployments
很多情况下,Zero-Config并不适合我们,比如我想使用自己指定的基础镜像,不想使用quay.io/jkube/jkube-java:0.0.20。
示例2:Zero-Config with Dockerfile
同样使用Zero-Config,但是这次在项目根目录下(与pom.xml同级目录)创建了一个Dockerfile文件:
sh
FROM eclipse-temurin:17.0.7_7-jdk-jammy
RUN mkdir -p /app
WORKDIR /app
ADD maven/jkube-demo-helloworld-1.0.0.jar /app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "jkube-demo-helloworld-1.0.0.jar"]
此时,Kubernetes-maven-plugin会使用这个Dockerfile文件来生成镜像。
但是要注意,在编写Dockerfile的时候,必须遵循plugin的opinion:
- 在拷贝jar包到容器镜像时,必须使用maven目录。这是因为默认的assembly名字是maven
如果观察此时的输出目录,会发现是这样的:
lua
target/docker/k8s/jkube-demo-helloworld/1.0.0/build
+-- Dockerfile
+-- maven
+-- jkube-demo-helloworld-1.0.0.jar
+-- src/
+-- target/
+-- pom.xml
示例3:XML Config with Dockerfile
与示例2类似,只不过我们想进一步定制化编写Dockerfile的规则,比如不使用maven作为保存artifacts的目录名,并且把文件放入src/main/docker的子目录下。
1)在src/main/docker/hello目录下创建Dockerfile:
sh
FROM eclipse-temurin:17.0.7_7-jdk-jammy
RUN mkdir -p /app
WORKDIR /app
ADD target/jkube-demo-helloworld-1.0.0.jar /app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "jkube-demo-helloworld-1.0.0.jar"]
2)在pom.xml文件中,指定kubernetes-maven-plugin的配置参数(指定Dockerfile的位置、保存artifacts的目录)
xml
<plugin>
<groupId>org.eclipse.jkube</groupId>
<artifactId>kubernetes-maven-plugin</artifactId>
<version>${jkube.version}</version>
<configuration>
<images>
<image>
<!-- 指定镜像的名字 -->
<name>dadaer.com/${project.artifactId}:${project.version}</name>
<build>
<!-- 指定Dockerfile的位置:src/main/docker/hello -->
<contextDir>hello</contextDir>
<!-- 指定artifacts的目录名为target,而不是默认的maven -->
<assembly>
<name>target</name>
</assembly>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
结论
目前Spotify的dockerfile-maven-plugin已经停止更新了,除非因为历史原因无法替换,否则不要继续使用它了。
如果是单纯想生成Docker镜像,那么个人觉得使用fabric8io的docker-maven-plugin更好一些,它的职责单一,配置直观。当然,如果使用JKube的kubernetes-maven-plugin也是可以的。
如果是要部署到k8s环境,那么无疑选择JKube的kubernetes-maven-plugin是最好的。
当然,随着CNCF的状态,目前有一个更加强大的东东,叫做CNB(Cloud Native Buildpacks),现在已经直接集成在了spring-boot-maven-plugin里面。关于CNB的介绍,会在以后单独写一篇文章。
xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-image-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>