Docker - Maven插件
笔记
开发完一个 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 的服务文件
vim /usr/lib/systemd/system/docker.service
找到 ExecStart 开头的代码,我的是在 13 行左右,如图:
将这段代码注释掉,# 代表注释,然后在它下方添加如下内容:
# 注释掉自带的
#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
2
3
4
5
值得注意的是:2375 端口是 Docker 默认的端口,你也可以换成其他未被使用过的端口。除了端口,其他不用修改
配置完后重启 Docker
systemctl daemon-reload
systemctl restart docker
2
虚拟机开放 2375 端口
firewall-cmd --zone=public --add-port=2375/tcp --permanent
firewall-cmd --reload
2
如果是阿里云服务器,则前往阿里云的安全组开放端口。
# 远程访问测试
重启 Docker,开放端口后,则需要测试是否成功配置了。
在服务器里执行如下代码:(可以直接执行第 4 行代码)
# 查看端口监听是否开启
netstat -nlpt
# curl测试是否生效
curl http://127.0.0.1:2375/info
2
3
4
如果返回一大堆东西,则代表 Docker 开放远程访问成功,如图:
# Docker工具
使用 IDEA 安装 Docker 工具,这个工具能让你在 IDEA 直接连接并操作 Docker。不需要连接服务器,即可在 IDEA 启动容器。你想想,一旦将 jar 包构建成镜像并上传到远程服务器的 Docker 后,那么如何启动这个镜像呢,你可以利用 Xshell 等工具连接服务器,然后启动镜像,但是 IDEA 也能直接启动镜像。
IDEA 能操作 Docker,启动镜像,就需要安装这个 Docker 工具。
# 工具下载
IDEA 安装 Docker 工具,打开插件市场(File->Settings->Plugins)
安装 Docker 后,配置 Docker 的远程访问连接
记住连接的 URL 以 tcp
开头,并且填写之前在 Docker 服务文件配置的暴露端口。
# 工具使用
如果启动一个镜像,则右键->create Container
点击 create Container 后弹出一个窗口,这些就是 docker run
需要的其他指令,如 --name、-p 等
如果觉得别扭,直接在 Run options
写除了 docerk run 镜像
的其他完整的命令。
注意看 Command preview
的命令演示,对比确保自己的命令正确。
# Docker插件
首先构建的镜像需要基于 jdk 镜像,我安装的是 jdk8
docker pull openjdk:8u312-jdk-slim-buster
# 标签方式
言语不及代码来的直接,在 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>
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 同级,为什么?请看下面)
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"] # 容器启动时,执行的命令
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
那么启动镜像后,该镜像就会执行 java -jar app.jar
命令。
我们也可以这样写:
docker run bx:1.0 bx.jar
此时启动镜像后,该镜像就会执行 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>
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>
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"]
2
3
4
5
6
7
ARG 代表定义变量,并且获取传来的 jar 包路径,赋值给变量 JAR_FILE
。
Dockerfile 插件需要进行配置本地变量,容易报错。建议初学者使用 Docker 插件。
# 插件使用
无论是使用 Docker 插件还是 Dockerfile 插件,使用起来非常简单,直接使用 Maven 执行打包即可,Maven 打包后会自动将该包构建成镜像,然后上传到服务器的 Docker 中。
点击 package,执行打包操作后,等待、看着控制台的变化,当出现 BUILD SUCCESS
,则代表成功。此时可以使用刚刚安装的 Docker 工具,连接服务器的 Docker,然后启动这个镜像。
# 插件总结
至于选择 Docker 插件还是 Dockerfile 插件,取决于你的想法,我比较喜欢 Docker 插件,清晰直观,并且 Docker 插件也支持 Dockerfile 方式。最主要的是 Dockerfile 插件需要配置环境变量,如果快速开发选择 Docker 插件。
官方建议的是 Dockerfile 插件。
可能大家有些疑惑,如果 Docker 插件的两种方式都写上去,也就是把 <dockerDirectory>${project.basedir}</dockerDirectory>
写到 Docker 插件的 pom.xml 文件,那么谁生效,当然是 Dockerfile 生效,官方说:使用 Dockerfile 后,如果指定了 baseImage
、maintainer
、exposes
、cmd
和 entryPoint
等元素,它们将被忽略掉,直接使用 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>
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>,如图:
2021-12-11 @Young Kbt
如果希望打成 war 包,那么只需要把基础镜像换成 tomcat 镜像,并将 target/xx.war 拷贝到 tomcat 容器的 usr/local/tomcat/webapps/
目录下即可。