Skip to content
0

Docker - Maven插件

NOTE

开发完一个 Java Web 项目,我们希望测试它,把它变成一个 Docker 的镜像,然后上传到服务器进行测试,本内容将介绍如何在项目打包的时候构建该包成 Docker 镜像,然后自动上传到服务器上。

2021-12-10 @YoungKbt

插件介绍

插件官网

本内容将在 IDEA 使用一个插件实现:写完一个项目如 Spring 项目,使用 Maven 打成 jar 包的时候,将该 jar 包构建成一个 Docker 镜像,然后自动上传到服务器上,接着你就可以进入服务器,用 Docker 启动这个镜像。

Eclipse 同理,只是界面布局、按钮位置不一样。

这个插件支持两种模式:

  • Docker 插件 docker-maven-plugin:在 pom.xml 文件指定生成镜像的 jar 包,生成的镜像名、版本等,也可以指定 Dockerfile 文件的路径进行构建
  • Dockerfile 插件 dockerfile-maven-plugin:完全依赖 Dockerfile 文件,利用 Docker 的缓存等优化技术,更快构建镜像

官方更推荐第二种「Dockerfile 插件」,但是身为开发的我们,第一种更直观。

开放 Docker

前面我们说过,将构建的镜像自动上传到服务器,那么怎么自动上传呢,当然是我们需要开放 Docker 的端口,让我们在本地能连接上服务器的 Docker,这样,IDEA 才能上传构建的镜像给 Docker。

开启远程访问

首先在服务器打开 Docker 的服务文件

sh
vim /usr/lib/systemd/system/docker.service

找到 ExecStart 开头的代码,我的是在 13 行左右,如图:

image-20211210221531464

将这段代码注释掉,# 代表注释,然后在它下方添加如下内容:

sh
# 注释掉自带的
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

# 开启 Docker 暴露端口,外界可以连接
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock

值得注意的是:2375 端口是 Docker 默认的端口,你也可以换成其他未被使用过的端口。除了端口,其他不用修改

配置完后重启 Docker

sh
systemctl daemon-reload
systemctl restart docker

虚拟机开放 2375 端口

sh
firewall-cmd --zone=public --add-port=2375/tcp --permanent
firewall-cmd --reload

如果是阿里云服务器,则前往阿里云的安全组开放端口。

远程访问测试

重启 Docker,开放端口后,则需要测试是否成功配置了。

在服务器里执行如下代码:(可以直接执行第 4 行代码)

sh
# 查看端口监听是否开启
netstat -nlpt
# curl测试是否生效
curl http://127.0.0.1:2375/info

如果返回一大堆东西,则代表 Docker 开放远程访问成功,如图:

image-20211210222229012

Docker 工具

使用 IDEA 安装 Docker 工具,这个工具能让你在 IDEA 直接连接并操作 Docker。不需要连接服务器,即可在 IDEA 启动容器。你想想,一旦将 jar 包构建成镜像并上传到远程服务器的 Docker 后,那么如何启动这个镜像呢,你可以利用 Xshell 等工具连接服务器,然后启动镜像,但是 IDEA 也能直接启动镜像。

IDEA 能操作 Docker,启动镜像,就需要安装这个 Docker 工具。

工具下载

IDEA 安装 Docker 工具,打开插件市场(File->Settings->Plugins)

image-20211210223345314

安装 Docker 后,配置 Docker 的远程访问连接

image-20211210223630030

记住连接的 URL 以 tcp 开头,并且填写之前在 Docker 服务文件配置的暴露端口。

工具使用

image-20211211010416448

如果启动一个镜像,则右键->create Container

image-20211210235419769

点击 create Container 后弹出一个窗口,这些就是 docker run 需要的其他指令,如 --name、-p 等

image-20211211000549010

如果觉得别扭,直接在 Run options 写除了 docerk run 镜像 的其他完整的命令。

image-20211211000221065

注意看 Command preview 的命令演示,对比确保自己的命令正确。

Docker 插件

首先构建的镜像需要基于 jdk 镜像,我安装的是 jdk8

docker pull openjdk:8u312-jdk-slim-buster

image-20211210225043053

标签方式

言语不及代码来的直接,在 pom.xml 文件添加如下代码

xml
<build>
    <plugins>
        <!-- 其他插件 -->
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>docker-maven-plugin</artifactId>
            <version>1.2.2</version>
            <executions>
                <!-- Maven 打包后,然后对该包执行 docker build 构建成镜像-->
                <execution>
                    <id>build-image</id>
                    <phase>package</phase>
                    <goals>
                        <!-- Maven 打包时,不希望构建镜像,则注释掉下面的 goal -->
                        <goal>build</goal>
                    </goals>
                </execution>
            </executions>
            <!-- 配置构建的镜像信息 -->
            < <configuration>
            <!-- 指定远程 Docker API 地址,http 开头 -->
            <dockerHost>http://192.168.199.27:2375</dockerHost>
            <!-- 构建的镜像名称以及版本号。${project.artifactId} 代表项目的 <artifactId> 名 -->
            <imageName>${project.artifactId}</imageName>
            <imageTags>
                <imageTag>latest</imageTag>
            </imageTags>
            <!-- 依赖的基础镜像 -->
            <baseImage>openjdk:8u312-jdk-slim-buster</baseImage>
            <!-- 暴露的端口,和项目保持一致 -->
            <exposes>8080</exposes>
            <!-- 工作目录,即进入容器后所处的默认目录 -->
            <workdir>/ROOT</workdir>
            <!-- 启动容器时,自动执行的命令。${project.build.finalName}是 打成 jar 包的名字 -->
            <entryPoint>["java", "-jar", "${project.build.finalName}.jar"]</entryPoint>
            <!-- 下面是复制 jar 包到 docker 容器指定目录配置-->
            <resources>
                <resource>
                    <!-- 复制到容器的 /ROOT 目录下 -->
                    <targetPath>/ROOT</targetPath>
                    <!-- 用于指定需要复制的根目录。${project.build.directory} 表示 target 目录 -->
                    <directory>${project.build.directory}</directory>
                    <!-- 用于指定需要复制的文件,${project.build.finalName}.jar 是打包后的 target 目录下的 jar 包名称 -->
                    <include>${project.build.finalName}.jar</include>
                </resource>
            </resources>
            </configuration>
        </plugin>
    </plugins>
</build>

该插件目前最新版是 1.2.2,可以在官网查看。

代码我都有注释讲解,而且和 Dockerfile 的知识差不多,只不过以标签形式输出

这里说明下 <workdir> 和 <targetPath> 的联系:

  • <workdir> 是容器的工作目录,即进入容器时,所处的默认目录,这里是 /ROOT 目录
  • <targetPath> 是将生成的 jar 包拷贝到的目录名

因为启动镜像后,镜像会执行 <entryPoint> 指定的命令:java -jar xx.jar,那么它会在哪里执行呢?肯定是在工作目录,也就是在 /ROOT 目录执行这个命令,那么 xx.jar 也需要处于这个 /ROOT 目录,不然会报错找不到 xx.jar 包。所以我们需要确保 jar 包拷贝到 <targetPath> 指定的目录和 <workdir> 一致。

要么两者都是 /ROOT 目录,要么都是其他名字一样的目录。

警告

无法在复制的时候,对 jar 包重命名,比如 jar 包名叫 bx.jar,无法在复制到容器时修改为其他名 xx.jar,如果你希望能在容器中重命名 jar 包,请使用 Dockerfile 插件方式。

如果写成 /ROOT/xx.jar,那么它会创建 xx.jar 目录而不是重命名为 xx.jar。

2021-11-11 @Young Kbt

Dockerfile 方式

该方式依然使用 docker-maven-plugin 插件,实际上就是先写个 Dockerfile 文件,然后填写这个文件所在的路径即可,文件内容就是上方指定的那些标签数据。

Dockerfile 文件

首先在与 pom.xml 同级目录下创建 Dockerfile 文件(你也可以在其他路径创建该文件,但我建议就与 pom.xml 同级,为什么?请看下面)

image-20211210230924920

Dockerfile 文件内容:

dockerfile
FROM openjdk:8u312-jdk-slim-buster8		# 基于 jdk8 镜像构建
MAINTAINER YoungKbt 2456019588@qq.com	# 镜像的作者信息
EXPOSE 8080								# 镜像暴露的端口
WORKDIR /ROOT							# 工作目录,也是进入容器所处的默认目录
ADD target/bx.jar /ROOT/app.jar	    	# 将 target 目录下的jar包,拷贝至容器里的 /ROO目录,并改名
ENTRYPOINT ["java", "-jar"]   			# 容器启动时,执行的命令
CMD ["app.jar"]							# 容器启动时,执行的命令

其实我给 openjdk 的版本重命名了,叫 8,所以我的是 FROM openjdk:8。不懂如何重命名?重命名传送门

你要确定你生成的 jar 包目录在哪,我的就在 target 目录下,因为 Dockerfile 和 target 目录同级,所以不需要使用 ..//xx/xx 之类的路径。建议 Dockerfile 文件路径和 pom.xml、target 保持一致。

这里主要介绍 ENTRYPOINT 和 CMD 之类的组合使用:

  • 两者都是在镜像启动时,执行相应的命令
  • CMD 要执行的命令在启动时可以被覆盖
  • ENTRYPOINT 要执行的命令在启动时虽然也可以被覆盖,但是需要 --entrypoint 指令,比较麻烦

举个例子,如果直接启动镜像,如 bx 镜像:

sh
docker run bx:1.0

那么启动镜像后,该镜像就会执行 java -jar app.jar 命令。

我们也可以这样写:

sh
docker run bx:1.0 bx.jar

此时启动镜像后,该镜像就会执行 java -jar bx.jarr 命令,将相当于可以传参给 CMD,如果不传参,CMD 也有一个自己的默认值 app.jar。

pom.xml 文件

写完 Dockerfile 文件后,我们需要在 pom.xml 文件引用写好的 Dockerfile 文件

xml
<build>
    <plugins>
        <!-- 其他插件 -->
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>docker-maven-plugin</artifactId>
            <version>1.2.2</version>
            <executions>
                <!-- Maven 打包后,然后对该包执行 docker build 构建成镜像-->
                <execution>
                    <id>build-image</id>
                    <phase>package</phase>
                    <goals>
                        <goal>build</goal>
                    </goals>
                </execution>
            </executions>
            <!-- 配置构建的镜像信息 -->
            <configuration>
                <!-- 指定远程 Docker API 地址,http 开头 -->
                <dockerHost>http://192.168.199.27:2375</dockerHost>
                <!-- 构建的镜像名称以及版本号。${project.artifactId} 代表项目的 <artifactId> 名 -->
                <imageName>${project.artifactId}</imageName>
                <imageTags>
                    <imageTag>latest</imageTag>
                </imageTags>
                <!-- Dockerfile 的位置。${project.basedir} 是项目的根路径-->
                <dockerDirectory>${project.basedir}</dockerDirectory>
            </configuration>
        </plugin>
    </plugins>
</build>

代码非常简单,除了指定构建镜像的名字和版本,只需要引入 Dockerfile 文件即可,${project.basedir} 是项目的根路径。

Dockerfile 插件

Dockerfile 的专属插件叫 dockerfile-maven-plugin,虽然 Docker 插件也支持引入 Dockerfile 文件,但是 Dockerfile 插件对 Dockerfile 的支持以及构建的过程处理更加得当。

xml
<build>
    <plugins>
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>dockerfile-maven-plugin</artifactId>
            <version>1.4.13</version>
            <executions>
                <execution>
                    <id>default</id>
                    <goals>
                        <!-- 如果 Maven 打包时不想用构建镜像,就注释掉这个 goal -->
                        <goal>build</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <!-- 上下文目录,也就是 Dockerfile 的目录 -->
                <contextDirectory>${project.basedir}</contextDirectory>
                <!-- 服务器地址,以及镜像名,斜杠隔开 -->
                <repository>192.168.199.27:2375/${project.artifactId}</repository>
                <!-- 镜像版本 TAG -->
                <tag>${project.version}</tag>
                <!-- 向 Dockerfile 传递参数-->
                <buildArgs>
                    <!-- 传递了打包的包路径给 Dockerfile 的 ARG 变量,基于当前目录下的 target -->
                    <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                </buildArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

值得做注意的是:服务器地址填写在 <repository> 标签里的第一个位置,/ 后的是镜像名。

Dockerfile 文件内容:

dockerfile
FROM openjdk:8
MAINTAINER YoungKbt 2456019588@qq.com
EXPOSE 8080
ARG JAR_FILE
ADD ${JAR_FILE} /app.jar
ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]

ARG 代表定义变量,并且获取传来的 jar 包路径,赋值给变量 JAR_FILE

Dockerfile 插件需要进行配置本地变量,容易报错。建议初学者使用 Docker 插件

插件使用

无论是使用 Docker 插件还是 Dockerfile 插件,使用起来非常简单,直接使用 Maven 执行打包即可,Maven 打包后会自动将该包构建成镜像,然后上传到服务器的 Docker 中。

image-20211211011030874

点击 package,执行打包操作后,等待、看着控制台的变化,当出现 BUILD SUCCESS,则代表成功。此时可以使用刚刚安装的 Docker 工具,连接服务器的 Docker,然后启动这个镜像。

image-20211211011144410

插件总结

至于选择 Docker 插件还是 Dockerfile 插件,取决于你的想法,我比较喜欢 Docker 插件,清晰直观,并且 Docker 插件也支持 Dockerfile 方式。最主要的是 Dockerfile 插件需要配置环境变量,如果快速开发选择 Docker 插件。

官方建议的是 Dockerfile 插件。

可能大家有些疑惑,如果 Docker 插件的两种方式都写上去,也就是把 <dockerDirectory>${project.basedir}</dockerDirectory> 写到 Docker 插件的 pom.xml 文件,那么谁生效,当然是 Dockerfile 生效,官方说:使用 Dockerfile 后,如果指定了 baseImagemaintainerexposescmdentryPoint 等元素,它们将被忽略掉,直接使用 Dockerfile 文件的内容。

当然你也可以像我一样,两种方式全部一股脑的放在 pom.xml 文件里

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <!--  Docker 镜像  -->
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>docker-maven-plugin</artifactId>
            <version>1.2.2</version>
            <executions>
                <!--执行 mvn package,即执行 mvn clean package docker:build-->
                <execution>
                    <id>build-image</id>
                    <phase>package</phase>
                    <goals>
                        <goal>build</goal>
                    </goals>
                </execution>
            </executions>

            <configuration>
               <!-- 指定远程 Docker API 地址,http 开头 -->
                <dockerHost>http://192.168.199.27:2375</dockerHost>
                <!-- 构建的镜像名称以及版本号。${project.artifactId} 代表项目的 <artifactId> 名 -->
                <imageName>${project.artifactId}</imageName>
                <imageTags>
                    <imageTag>latest</imageTag>
                </imageTags>
                <!--依赖的基础镜像-->
                <baseImage>openjdk:8</baseImage>
                <!-- Dockerfile 的位置-->
                <!--<dockerDirectory>${project.basedir}</dockerDirectory>-->
                <!-- 暴露的端口,和项目保持一致 -->
                <exposes>8080</exposes>
                <!-- 工作目录,即进入容器后所处的默认目录 -->
                <workdir>/ROOT</workdir>
                <!-- 启动容器时,自动执行的命令。${project.build.finalName}是 打成 jar 包的名字 -->
                <entryPoint>["java", "-jar", "${project.build.finalName}.jar"]</entryPoint>
                <!-- 下面是复制 jar 包到 docker 容器指定目录配置-->
                <resources>
                    <resource>
                        <!-- 复制到容器的 /ROOT 目录下 -->
                        <targetPath>/ROOT</targetPath>
                        <!-- 用于指定需要复制的根目录,${project.build.directory} 表示 target 目录 -->
                        <directory>${project.build.directory}</directory>
                        <!-- 用于指定需要复制的文件,${project.build.finalName}.jar 是打包后的 target 目录下的 jar 包名称 -->
                        <include>${project.build.finalName}.jar</include>
                    </resource>
                </resources>
            </configuration>
        </plugin>
    </plugins>
</build>

需要 Dockerfile 方式的时候,将 34 行代码取消注释,不需要的时候打开注释。

NOTE

如果构建两次名字和版本相同的镜像到 Docker 中,那么第一个镜像会变成 <none>,如图:

image-20211211005817554

2021-12-11 @Young Kbt

如果希望打成 war 包,那么只需要把基础镜像换成 tomcat 镜像,并将 target/xx.war 拷贝到 tomcat 容器的 usr/local/tomcat/webapps/ 目录下即可。

最近更新