步骤分析

把SpringBoot项目部署到服务端Docker大致就分这几步:

打包(Package)

配置pom文件中的<build>属性,这样在mvn build时可以把项目打成可直接运行的Jar包,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>${mainClass}</mainClass>
<finalName>${finalName}</finalName>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>

</build>

这步操作就是引入了SpringBoot的打包插件,借助这个插件可以把SpringBoot打包成可直接运行的Jar包。

编写DockerFile,把Jar包做成镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#基础镜像使用jdk1.8
FROM openjdk:8
#作者
MAINTAINER yozuru
#设置时区
ENV TZ="Asia/Shanghai"
ENV JAVA_OPTS=""
# 默认的Spring Boot启动参数
ENV SPRING_PROFILES_ACTIVE="pro"
ARG JAR_FILE
# 创建日志目录
RUN mkdir -p /app/logs && \
chmod 777 /app
# 将jar包添加到容器中并更名
ADD ${JAR_FILE} /app/app.jar
# 设置时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
# 运行jar包
CMD java $JAVA_OPTS -jar /app/app.jar --spring.profiles.active=$SPRING_PROFILES_ACTIVE
# 暴露8080端口作为微服务
EXPOSE 8080

这个镜像中通过对外暴露JAVA_OPTSSPRING_PROFILES_ACTIVE,让外部可以控制JVM的启动参数和SpringBoot的配置环境。

编写docker-compose.yml(可选)

我习惯于用docker-compose来帮助构建Docker容器。特别是在微服务项目中,通过命令实例化容器过于繁琐。

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
version: "3"

networks:
huel-oj-network:
services:

hueloj-admin:
image: hueloj/admin:1.0
container_name: hueloj-admin
ports:
- "8080:8080"
deploy:
resources:
limits:
memory: 1024M
reservations:
memory: 512M
environment:
- JAVA_OPTS=-Xmx512m -Xss256k
volumes:
- /root/app/admin:/app/logs
networks:
- huel-oj-network
depends_on:
- hueloj-gateway-1

hueloj-gateway-1:
image: hueloj/gateway:1.0
container_name: hueloj-gateway-1
ports:
- "10010:8080"
deploy:
resources:
limits:
memory: 1024M
reservations:
memory: 512M
environment:
- JAVA_OPTS=-Xms512m -Xmx512m -Xss128k
volumes:
- /root/app/gateway:/app/logs
networks:
- huel-oj-network

# OtherService……

这里指定了具体的镜像名称和tag,等会打包时要跟这个一致。

上传至服务器

把打好的Jar包、DockerFile、docker-compose.yml 统统上传至服务器。

构建镜像

把Jar包和DockerFile放在同一目录中,运行以下命令:

1
docker build -t survey_star_app:1.0 .
  • -t 参数表示为镜像指定一个名称和标签
  • .是构建上下文的路径。Docker 在构建镜像时会将这个路径下的所有文件和子文件夹包含在内,以供构建过程中使用。

除此之外,docker build命令还有这参数:

  • -f <Dockerfile>:指定要使用的 Dockerfile 文件的路径。如果您的构建上下文中有多个 Dockerfile,可以使用这个参数来明确指定要使用哪一个。
  • --build-arg <key=value>:定义构建过程中传递给 Dockerfile 的构建参数。在 Dockerfile 中,您可以使用 ARG 指令来引用这些参数。

实例化容器

创建一个工作目录,把刚才上传的docker-compose.yml文件给也放进来。启动容器

1
docker compose up -d
  • up会构建、启动容器
  • -d指后台运行

更新步骤

  • 进入工作目录,运行以下命令

    1
    docker compose down
    • down会把这个docker-compose.yml文件所定义的容器全部停止(stop)和删除(remove)
  • 删除镜像,运行以下命令

    1
    2
    3
    docker images
    # 查看需要删除的镜像id
    docker rmi [镜像id]
  • 重复1、4、5、6步操作

存在问题

如果是单个的SpringBoot项目还好,只有1个Jar包1个镜像。手动进行以上操作也还能操作的过来。

但!是!在Spring Cloud项目中,这简直就是噩梦。就目前开发的算是比较简单的项目,就已经有5个Jar包了。

如果不优化方案的话,每次更新项目的画风是这样的:

  • 在IDEA中把这5个模块包在本地打包。

  • 把Jar包上传到服务器。

  • down掉正在运行的容器,删除掉旧的镜像

  • 把5个Jar包打成镜像,还要注意Jar包和DockerFile的目录

  • 重新up服务

听着就麻烦!

优化方案

docker-maven-plugin

docker-maven-plugin是Maven的一个插件,可以在本地直接把Jar包打到远端的Docker中。这样就可以省略掉第4、5步操作。

首先在pom文件中添加如下配置:

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
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.2.2</version>
<configuration>
<!-- 远程Docker的地址 -->
<dockerHost>https://**:2375</dockerHost>
<dockerCertPath>F:\**\pem</dockerCertPath>
<!-- 镜像名称、前缀、项目名 -->
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
<imageTags>
<imageTag>${project.version}</imageTag>
<imageTag>latest</imageTag>
</imageTags>
<!-- Dockerfile的位置 -->
<dockerDirectory>${project.basedir}</dockerDirectory>
<buildArgs>
<JAR_FILE>${finalName}.jar</JAR_FILE>
</buildArgs>
<resources>
<resource>
<targetPath>/</targetPath>
<!-- 表示的target文件夹 -->
<directory>${project.build.directory}</directory>
<!-- 表示打出来的JAR包-->
<include>${finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
  • 这里我由于之前配置了远程登录Docker的相关设置,可以通过Https的方式连接到服务端的Docker。具体配置方法我就不在这里展开了。后续可能会在其它文章中详细说明。似乎这个插件是支持账号密码连接的。

    注意,不要轻易的打开Docker的远程连接设置。除非你知道自己在做什么

    通过dockerHost和`dockerCertPath连接到Docker

  • 常用的参数就这些,基本都可以见名知意。

这样配置之后,在本地运行以下命令

1
2
mvn docker:removeImage
mvn docker:build

就可以对服务端的容器进行删除、构建了。

配置SSH连接

由于每次更新操作执行的命令都是固定的,所以完全可以在本地编写一个脚本远程执行命令,在这之前要先配置SSH公私钥连接。大致步骤如下:

生成SSH密钥对

在本地计算机上生成一对密钥。打开终端并运行以下命令:

1
ssh-keygen -t rsa

这将生成一对RSA密钥,通常保存在~/.ssh目录下的id_rsa(私钥)和id_rsa.pub(公钥)文件中。复制公钥的内容。

配置服务器SSH访问

  1. 在服务器上,打开/root/.ssh目录(如果不存在,则创建它):

    1
    2
    mkdir -p ~/.ssh
    chmod 700 ~/.ssh
  2. 创建或编辑~/.ssh/authorized_keys文件:

    1
    vi ~/.ssh/authorized_keys
  • 粘贴之前复制的公钥内容到authorized_keys文件中。

  • 保存文件并退出编辑器。

  1. 配置文件权限

    1
    chmod 600 ~/.ssh/authorized_keys
  2. 如果是root用户,则需要对sshd_config进行配置

    1
    vi /etc/ssh/sshd_config
  • 确保以下设置处于启用状态:

    1
    2
    PubkeyAuthentication yes
    PasswordAuthentication yes # 可选,如果您决定只使用公钥身份验证,请将其设置为“no”

重启SSH服务

重新启动SSH服务以使更改生效:

1
systemctl restart sshd

测试连接

在本地的终端执行

1
ssh root@server_ip

若能直接连接,则证明已经成功配置。

编写bat脚本

经过以上配置,虽然操作已经没那么繁琐了,但是还是要在IDEA中点来点去,着实不够优雅。最终决定编写一个bat脚本来实现一键部署、更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@echo off
ssh root@server_ip "cd ~/app && docker compose down"
rem 列出所有需要处理的模块
set "MODULES=admin gateway judge-service problem-service user-service"
call mvn clean package -f pom.xml -DskipTests=true

for %%M in (%MODULES%) do (
echo Building Docker image for %%M
rem 进入模块目录
cd %%M
rem 执行Maven的Docker插件,构建镜像
call mvn docker:removeImage docker:build -f pom.xml -DskipTests=true
rem 返回项目根目录
cd ..
)
ssh root@server_ip "cd ~/app && docker compose up -d"

这样就可以实现一键打包、构建、删除、更新、部署操作。

优雅!太优雅了!

classy.gif