目录

Docker - Maven插件

笔记

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

2021-12-10 @YoungKbt

# 插件介绍

插件官网 (opens new window)

本内容将在 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 的服务文件

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

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

image-20211210221531464

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

# 注释掉自带的
#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
1
2
3
4
5

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

配置完后重启 Docker

systemctl daemon-reload 
systemctl restart docker
1
2

虚拟机开放 2375 端口

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

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

# 远程访问测试

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

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

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

如果返回一大堆东西,则代表 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
1

image-20211210225043053

# 标签方式

言语不及代码来的直接,在 pom.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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

该插件目前最新版是 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 文件内容:

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"]							# 容器启动时,执行的命令
1
2
3
4
5
6
7

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

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

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

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

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

docker run bx:1.0
1

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

我们也可以这样写:

docker run bx:1.0 bx.jar
1

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

# pom.xml文件

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

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

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

# Dockerfile插件

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

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

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

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"]
1
2
3
4
5
6
7

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 文件里


































 






















<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

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

笔记

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

image-20211211005817554

2021-12-11 @Young Kbt

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

更新时间: 2024/01/17, 05:48:13
最近更新
01
JVM调优
12-10
02
jenkins
12-10
03
Arthas
12-10
更多文章>