在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>
相关推荐
杨哥带你写代码19 分钟前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
weixin_453965001 小时前
[单master节点k8s部署]30.ceph分布式存储(一)
分布式·ceph·kubernetes
weixin_453965001 小时前
[单master节点k8s部署]32.ceph分布式存储(三)
分布式·ceph·kubernetes
tangdou3690986551 小时前
1分钟搞懂K8S中的NodeSelector
云原生·容器·kubernetes
A尘埃1 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23071 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
代码之光_19801 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
苹果醋32 小时前
快速玩转 Mixtral 8x7B MOE大模型!阿里云机器学习 PAI 推出最佳实践
spring boot·nginx·毕业设计·layui·课程设计
程序员大金3 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
later_rql4 小时前
k8s-集群部署1
云原生·容器·kubernetes