在Spring Boot中生成Docker镜像

介绍

为了把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.

github.com/spotify/doc...

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>
相关推荐
yannan20190313几秒前
简述kubernetes(k8s)
云原生·容器·kubernetes
辣机小司3 分钟前
【踩坑记录:EasyExcel 生产级实战:策略模式重构与防御性导入导出校验指南(实用工具类分享)】
java·spring boot·后端·重构·excel·策略模式·easyexcel
计算机毕设指导67 分钟前
基于微信小程序的博物馆文创系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·intellij-idea
汪碧康12 分钟前
【k8s-1.34.2安装部署】十.gateway Api v1.4.0和istio安装
云原生·容器·kubernetes·gateway·istio·cilium·xkube
后端小张13 分钟前
【JAVA 进阶】Spring Boot自动配置详解
java·开发语言·人工智能·spring boot·后端·spring·spring cloud
有趣灵魂15 分钟前
Java SpringBoot批量获取Minio中多个文件进行压缩成zip下载
java·开发语言·spring boot
IT 行者17 分钟前
Spring Security Session 序列化策略分析
java·spring boot·后端·spring
IT 行者19 分钟前
Spring Boot 4.0 整合Spring Security 7 后的统一异常处理指南
spring boot·后端·spring
学博成1 小时前
在 Spring Boot 中使用 Kafka 并保证顺序性(Topic 分区为 100)的完整案例
spring boot·kafka
無欲無为2 小时前
Spring Boot 整合 RabbitMQ 详细指南:从入门到实战
spring boot·rabbitmq·java-rabbitmq