linux 部署文档

主要是微服务部署

一、Docker Compose 介绍

在介绍 Docker Compose 前,先简单介绍下 Docker。
Docker 是一种容器技术,允许开发者将应用程序和所有依赖项(如代码、库、配置等)制作为 镜像。可以把镜像简单理解为软件安装包,可以在不同的计算机上通过它快速安装和启动应用程序(容器),这些程序独立隔离地运行,不受外部环境的影响。

image-20231006141017536

如果要部署微服务项目,可能要启动多个 Docker 容器,比如 MySQL 容器、用户服务容器等。这时就需要 Docker Compose 了。它是一个容器编排助手,用于集中管理多个 Docker 容器的启动和协同工作。可以在一个配置文件中集中定义所有容器以及它们的关系。然后,可以使用一行命令启动所有容器,而不需要手动运行多个命令。

需要注意的是,Docker Compose 通常适用于把所有微服务部署在同一台服务器的场景,在真实的企业级项目中,往往会使用 K8S 等更专业的容器编排和自动化部署工具,更方便地在多个服务器上部署容器。

二、部署流程

了解了 Docker 和 Docker Compose 的作用后,我们来快速了解下部署流程,分为 2 大阶段 —— 本地部署和服务器部署。

一、本地部署

1)梳理服务部署表格
2)Maven 子父模块打包
3)Dockerfile 编写
4)编写环境依赖配置
5)编写服务配置
6)调整程序配置
7)测试访问

二、服务端部署

1)准备服务器
2)Docker Compose 安装
3)同步文件
4)获取 jar 包
5)服务启动
6)测试访问

三、本地部署

一般win上面是不能部署的 docker 有问题 ,mac上面还差不多

3.1 梳理服务部署表格

在部署微服务项目前,首先要规划好要部署哪些服务、以及各服务的关键信息,比如服务名称、版本号、占用端口号、关键配置等。
对于我的在线判题项目,梳理好的服务表格如下:

服务名称英文名端口号版本号服务类别
数据库mysql3306v8环境依赖
缓存redis6379v6环境依赖
消息队列rabbitmq5672, 15672v3.12.6环境依赖
注册中心nacos8848v2.2.0环境依赖
网关服务gateway8101java 8业务服务
用户服务staroj-backend-user-service8102java 8业务服务
题目服务staroj-backend-question-service8103java 8业务服务
判题服务staroj-backend-judge-service8104java 8业务服务

为什么这里我要划分服务类别为 “环境依赖” 和 “业务服务” 呢?
因为在启动服务时,必须要先启动环境依赖,才能启动业务服务,否则就会报类似 “无法连接数据库” 之类的错误。、

3.2 maven 子父模块打包

对于微服务项目,我们通常是使用 Maven 的子父模块功能进行管理的。需要部署项目时,不用针对每个子服务单独执行 mvn package 命令进行打包,而是可以一键打包所有服务。
想要实现这个功能,需要给子父模块的依赖文件(pom.xml)进行一些配置,主要包括:

1) 父模块配置

在父模块的 pom.xml 文件中引入 spring-boot-maven-plugin 即可,注意一定不要配置 configuration 和 repackage!
示例代码如下:

1
2
3
4
5
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>

2)子模块配置

修改所有需要启动 Spring Boot 的服务(用户服务、题目服务、判题服务、网关服务)的子模块 pom.xml 文件。
主要是增加 executions 配置,使用 spring-boot-maven-plugin 的 repackage 命令来构建子模块,从而自动在构建时将公共模块的依赖打入 jar 包。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>

3.3 DockerFile 编写

Dockerfile 是定义 Docker 容器镜像构建过程的文件,包括容器镜像使用的基础环境、容器内的依赖和文件、容器的配置、启动命令等。

有了 Dockerfile,我们就能很轻松地制作出自己的容器镜像。

虽然 Dockerfile 的写法并不复杂,但我还是建议大家尽量不要自己写,直接去网上找个差不多的项目,复制粘贴别人的 Dockerfile 就足够了!

这里给大家提供 2 种常用的 Spring Boot 项目的 Dockerfile。

1) 复制jar包版

思路:在本地打好 jar 包后,复制 jar 包到容器中运行
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基础镜像
FROM openjdk:8-jdk-alpine

# 指定工作目录
WORKDIR /app

# 将 jar 包添加到工作目录,比如 target/yuoj-backend-user-service-0.0.1-SNAPSHOT.jar
ADD {本地 jar 包路径} .

# 暴露端口
EXPOSE {服务端口号}

# 启动命令
ENTRYPOINT ["java","-jar","/app/{jar 包名称}","--spring.profiles.active=prod"]

2) maven 打包版

思路:复制本地代码到容器中,在容器中使用 Maven 打包再运行
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基础镜像
FROM maven:3.8.1-jdk-8-slim as builder

# 指定工作目录
WORKDIR /app

# 添加源码文件
COPY pom.xml .
COPY src ./src

# 构建 jar 包,跳过测试
RUN mvn package -DskipTests

# 启动命令
ENTRYPOINT ["java","-jar","/app/target/{jar 包名称}","--spring.profiles.active=prod"]

此处由于我们的微服务项目可以一键打好所有子服务的 jar 包,就没必要每个服务单独在容器中打包了,所以选择第一种方式的 Dockerfile。

我们需要给每个 Spring Boot 服务(用户服务、题目服务、判题服务、网关服务)都编写一个 Dockerfile,放到每个子服务的根目录下。

3.4 编写环境依赖配置

接下来,我们就要编写 Docker Compose 的配置文件了,可以根据配置文件快速启动多个服务。

之前我们已经梳理了服务部署表格,将服务划分为了 “环境依赖” 和 “业务服务”。

由于业务服务依赖 MySQL 等环境依赖,所以需要拆分 2 套 Docker Compose 的配置文件,分别为 docker-compose.env.yml 环境配置和 docker-compose.service.yml 业务服务配置,保证先成功启动依赖,再启动服务。

学过 Docker Compose 的同学可能听说过 depends_on 配置,也能决定服务的启动顺序。但是千万注意,depends_on 并不会等待服务完全就绪,只是确保它们在启动时的顺序,并不稳定。

如何编写 Docker Compose 文件呢?
和 Dockerfile 一样,直接去网上找现成的 Docker Compose file,复制粘贴过来略做修改就能使用了~
再配合以下 2 个网站,完全无需记忆 Docker Compose 的写法!

当然,现在 AI 时代了,还有更简单的方式!
直接把我们整理好的服务部署需要喂给 GPT,让 AI 帮我们生成配置即可~
示例 prompt:

1
2
现在我需要用 docker compose 来部署 mysql 83306 端口)username=root,password=123456
redis 6(无密码,6379端口)、rabbitmq v.3.12.65672 端口 password: guest,username: guest)、nacos 2.2.08848端口);还有 4 个本地的 springboot 服务(名称分别为:staroj-backend-user-service 8102端口、staroj-backend-question-service 8103端口、staroj-backend-judge-service 8104端口、staroj-backend-gateway 8101 端口),每个服务本地目录都有一个 Dockerfile,请帮我自动生成 docker compose 的 yml 配置文件,要求这些服务网络能够连通

我们要分别在 Docker Compose 中定义 4 大基础依赖,包括 MySQL、Redis、RabbitMQ 和 Nacos。

1)Mysql

我们不仅要创建一个 MySQL 服务,还要在创建服务后自动创建我们需要的库表结构。
所以需要先准备数据库 SQL 脚本文件,里面包含了建库、建表语句,我们把它放在微服务项目根目录的 mysql-init 文件夹中:

image-20231008160159017

由于要在本地启动 MySQL,还需要定义一个文件夹 .mysql-data 来存放 MySQL 的持久化数据,防止容器重启后数据丢失。

做好这两点后,就可以编写 docker-compose.env.yml 文件了,先只写一个 MySQL 服务,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3'
services:
mysql:
image: mysql:8 # 使用的镜像
container_name: staroj-mysql # 启动的实例名称
environment:
MYSQL_ROOT_PASSWORD: 123456 # root 用户密码
ports:
- "3306:3306" # 端口映射
volumes:
- ./.mysql-data:/var/lib/mysql # 将数据目录挂载到本地目录以进行持久化
- ./mysql-init:/docker-entrypoint-initdb.d # 自动执行启动脚本
restart: always # 崩溃后自动重启
networks:
- mynetwork # 指定网络
networks:
mynetwork: # 自定义网络,实现网络互通和隔离

2)Redis

Redis 服务的定义和启动操作和 MySQL 服务几乎一致,Redis 的 Docker Compose 配置示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'
services:
redis:
image: redis:6
container_name: staroj-redis
ports:
- "6379:6379"
networks:
- mynetwork
volumes:
- ./.redis-data:/data # 持久化
networks:
mynetwork:

3)RabbitMQ

同 MySQL 和 Redis,RabbitMQ 的 Docker Compose 配置示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3'
services:
rabbitmq:
image: rabbitmq:3.12.6-management # 支持管理面板的消息队列
container_name: staroj-rabbitmq
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "5672:5672"
- "15672:15672" # RabbitMQ Dashboard 端口
volumes:
- ./.rabbitmq-data:/var/lib/rabbitmq # 持久化
networks:
- mynetwork
networks:
mynetwork:

本地执行 Docker Compose 文件,启动 RabbitMQ 服务,然后可以访问 localhost:15672 查看到管理面板,就表示启动成功了~

4)Nacos

和其他服务一样,Nacos 也需要编写 Docker Compose 配置。
但是在选择 Nacos 镜像时必须要注意,建议选择支持 linux/arm64 架构的镜像版本,比如 v2.2.0-slim,否则后面可能会无法运行:

image-20231008160547002

Nacos 示例配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3'
services:
nacos:
image: nacos/nacos-server:v2.2.0-slim
container_name: staroj-nacos
ports:
- "8848:8848"
volumes:
- ./.nacos-data:/home/nacos/data
networks:
- mynetwork
environment:
- MODE=standalone # 单节点模式启动
- PREFER_HOST_MODE=hostname # 支持 hostname
- TZ=Asia/Shanghai # 控制时区
networks:
mynetwork:

完整 Docker Compose 文件

分别调试完上述服务后,我们把所有的配置拼在一起,就得到了完整的文件,文件名为 docker-compose.env.yml。
完整代码如下:

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
version: '3'
services:
mysql:
image: mysql:8 # 使用的镜像
container_name: staroj-mysql # 启动的实例名称
environment:
MYSQL_ROOT_PASSWORD: 123456 # root 用户密码
ports:
- "3306:3306" # 端口映射
volumes:
- ./.mysql-data:/var/lib/mysql # 将数据目录挂载到本地目录以进行持久化
- ./mysql-init:/docker-entrypoint-initdb.d # 启动脚本
restart: always # 崩溃后自动重启
networks:
- mynetwork # 指定网络
redis:
image: redis:6
container_name: staroj-redis
ports:
- "6379:6379"
networks:
- mynetwork
volumes:
- ./.redis-data:/data # 持久化
rabbitmq:
image: rabbitmq:3.12.6-management # 支持管理面板的消息队列
container_name: staroj-rabbitmq
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "5672:5672"
- "15672:15672" # RabbitMQ Dashboard 端口
volumes:
- ./.rabbitmq-data:/var/lib/rabbitmq # 持久化
networks:
- mynetwork
nacos:
image: nacos/nacos-server:v2.2.0-slim
container_name: staroj-nacos
ports:
- "8848:8848"
volumes:
- ./.nacos-data:/home/nacos/data
networks:
- mynetwork
environment:
- MODE=standalone # 单节点模式启动
- PREFER_HOST_MODE=hostname # 支持 hostname
- TZ=Asia/Shanghai # 控制时区
networks:
mynetwork:

3.5 编写业务服务配置

用同样的方式,我们可以编写业务服务的 Docker Compose 文件,文件名称 docker-compose.service.yml

示例代码如下,其中需要格外关注的配置是 build 和 depends_on:

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
version: '3'
services:
yuoj-backend-gateway:
container_name: yuoj-backend-gateway
build: # 服务的 Docker 构建文件位置
context: ./yuoj-backend-gateway
dockerfile: Dockerfile
ports:
- "8101:8101"
networks:
- mynetwork

yuoj-backend-user-service:
container_name: yuoj-backend-user-service
build:
context: ./yuoj-backend-user-service
dockerfile: Dockerfile
ports:
- "8102:8102"
networks:
- mynetwork
depends_on: # 本服务依赖的服务,控制启动先后顺序
- yuoj-backend-gateway

yuoj-backend-question-service:
container_name: yuoj-backend-question-service
build:
context: ./yuoj-backend-question-service
dockerfile: Dockerfile
ports:
- "8103:8103"
networks:
- mynetwork
depends_on:
- yuoj-backend-user-service
- yuoj-backend-gateway

yuoj-backend-judge-service:
container_name: yuoj-backend-judge-service
build:
context: ./yuoj-backend-judge-service
dockerfile: Dockerfile
ports:
- "8104:8104"
networks:
- mynetwork
depends_on:
- yuoj-backend-user-service
- yuoj-backend-question-service
- yuoj-backend-gateway

# 网络,不定义的话就是默认网络
networks:
mynetwork:

3.6、调整程序配置

编写好上述配置文件后,本地尝试运行 Docker Compose 业务服务,结果发现:报错啦!依赖服务的地址访问不通!

这是由于之前我们的项目访问依赖服务时,全部是使用了固定的 IP 地址(比如 localhost),而容器内部的 localhost(或 127.0.0.1)通常指向容器本身,而不是宿主主机。所以为了在容器内访问其他服务,程序中应该使用服务名称而不是 localhost。

我们给每个 Spring Boot 服务都增加一套 prod 上线配置,在配置中更改服务调用地址。

用户服务、题目服务和判题服务的 application-prod.yml 配置修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 生产环境配置文件
spring:
# 数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/yuoj # localhost 改为 mysql
username: root
password: 123456
# Redis 配置
redis:
database: 1
host: redis # localhost 改为 redis
port: 6379
timeout: 5000
cloud:
nacos:
discovery:
server-addr: nacos:8848 # localhost 改为 nacos
rabbitmq:
host: rabbitmq # localhost 改为 rabbitmq
port: 5672
password: guest
username: guest

Gateway 网关服务的配置修改如下:

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
spring:
cloud:
nacos:
discovery:
server-addr: nacos:8848 # localhost 改为 nacos 修改处
gateway:
routes:
- id: yuoj-backend-user-service
uri: lb://yuoj-backend-user-service
predicates:
- Path=/api/user/**
- id: yuoj-backend-question-service
uri: lb://yuoj-backend-question-service
predicates:
- Path=/api/question/**
- id: yuoj-backend-judge-service
uri: lb://yuoj-backend-judge-service
predicates:
- Path=/api/judge/**
application:
name: yuoj-backend-gateway
main:
web-application-type: reactive
server:
port: 8101
knife4j:
gateway:
enabled: true
strategy: discover
discover:
enabled: true
version: swagger2

能不使用硬编码就绝对不要使用硬编码

1
2
@Value("${spring.rabbitmq.host:localhost}")
private String host;

四、 服务器部署

4.1 Docker Compose 安装

有了服务器后,直接参考 Docker Compose 官方文档来安装。这里我们使用 Docker Compose V2 版本,相比 V1 版本统一了命令,使用更方便:
Docker Compose V2 地址:https://docs.docker.com/compose/migrate/
Docker Compose Linux 安装:https://docs.docker.com/compose/install/linux/#install-using-the-repository

安装过程很简单,跟着官方文档来就行了,主要包括以下几个步骤:

1)设定安装来源:

1
2
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

2)安装 Docker 和 Docker Compose:

1
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

3)启动 Docker:

1
sudo systemctl start docker

4)测试 Docker:

1
2
systemctl status docker
sudo docker run hello-world

4.2 同步文件

接下来,我们需要把本地折腾好的微服务项目源码上传到服务器上,可以选择用 FTP 或 SSH 连接文件手动上传文件。

我这里使用 JetBrains 开发工具的远程部署功能,可以配置文件自动上传,步骤如下:

1)进入远程部署配置

image-20231008161717827

2)添加远程部署配置:

image-20231008161832508

3)指定连接的服务器配置:

image-20231008162119978

4)配置本地文件和服务器文件路径映射:

image-20231008162351179

5)开启自动上传:

根据个人,个人觉得没有必要

image-20231008162430646

6)首次需要手动上传文件。
上传前记得先删除无用的文件,然后右键项目根目录,点击部署上传代码:

image-20231008162513428

上传成功,在服务器对应路径(/code/staroj-backend-microservice)下能看到已上传的文件列表:

image-20231008164458529

4.4 获取 jar 包

光把代码上传到服务器还是不够的,因为我们构建 Docker 镜像需要 jar 包。
有 2 种方式得到 jar 包:

1 ) 本地执行 mvn package 打好 jar 包,然后再上传

2 ) 服务器上装 Maven,在服务器上打包
但是因为 jar 包比较大,频繁改动的话同步速度会比较慢,所以更建议第二种方式,步骤如下:

1)安装 Maven:

1
sudo yum install maven

2)安装好后,执行打包:

1
sudo mvn package

打包成功:

image-20231008174520354

4.5 服务启动

所有一切准备就绪,接下来就是使用 Docker Compose 命令分别启动环境依赖和业务服务啦。

1)启动环境依赖

先使用 docker compose 一行命令启动环境依赖:

1
docker compose -f docker-compose.env.yml up

注意:
1)老版本使用 “docker-compose” 替代 “docker compose”
2)如果没有权限,命令前加上 “sudo”

记得开启防护墙

然后电脑远程连接一下,看看是否成功开启

由于进程在前台启动会影响我们的操作,所以先按 ctrl + c 退出,加上 -d 参数让容器在后台启动:

1
sudo docker compose -f docker-compose.env.yml up -d

试着查看下 docker 容器的状态:

1
sudo docker stats

2)启动业务服务

确保环境依赖都启动成功后,接下来启动业务服务:

1
docker compose -f docker-compose.service.yml up

正常来说,应该会启动成功;但如果运气背,可能会有失败,比如我这的网关服务就启动失败了。

如果某个服务启动失败,可以再次单独只启动它,比如网关服务:

1
sudo docker compose -f docker-compose.service.yml up yuoj-backend-gateway

4.6 测试访问

最后,像验证本地微服务项目部署一样,访问线上网关的接口文档(http://你的服务器 IP:8101/doc.html),

依次调用用户注册 => 登录 => 获取登录用户信息 => 创建题目,全部成功~

由于我的是2g的部署一个环境依赖就已经占用1.8g了,所有我采用两个服务器来进行这个操作,一个用来部署环境依赖 一个用来部署业务服务