关于Docker的详细内容以及快速入门, 请参考https://blog.dhx.icu/2022/11/25/springcloud/Springcloud03-Docker/

简单来讲, 将项目部署到 Docker 中可以带来更高效、更稳定、更灵活的部署和管理体验,有助于提高开发和运维效率,同时也增强了应用程序的可扩展性和可维护性。

  1. 隔离和独立性: Docker 容器提供了隔离的运行环境,前端和后端可以在各自的容器中独立运行,不会相互影响。这样可以确保应用的稳定性和安全性。
  2. 环境一致性: Docker 容器包含了应用所需的所有依赖和运行环境,保证了开发、测试和生产环境之间的一致性,避免了“在我这里是好的”问题。
  3. 简化部署: 使用 Docker 部署前后端分离的项目可以更快速、更简单地进行。只需创建 Docker 镜像并在不同环境中运行容器,无需手动安装和配置环境。
  4. 易于扩展: Docker 容器可以根据需要进行水平扩展,以适应不断增长的流量。你可以根据负载情况,动态调整容器的数量。
  5. 持续集成与持续交付: Docker 可以与持续集成和持续交付(CI/CD)流程无缝集成,帮助自动化构建、测试和部署过程,加速交付新功能。
  6. 微服务架构: 将前端和后端分离的项目部署到 Docker 容器中有助于实现微服务架构。每个微服务都可以打包为一个独立的容器,提高模块化和可维护性。
  7. 多语言支持: 如果你的项目前后端使用了不同的编程语言,Docker 可以容纳多种语言的应用,不会产生冲突。
  8. 资源有效利用: Docker 容器共享主机的操作系统内核,因此可以更高效地使用系统资源,避免资源浪费。
  9. 易于迁移: Docker 容器可以在不同的环境中移植,例如从开发环境到测试环境,再到生产环境,无需进行大量的适应性修改。

本文以Springboot项目(后端)以及React项目(前端)为例, 从零到一实现项目部署

准备工作

需要准备好

  1. 能够成功编译运行的前后端项目
  2. 知道自己的项目运行环境 (比如JDK , MySQL 等)
  3. 一台云服务器虚拟机

一般来讲, 我们的部署顺序是后端->前端。

后端部署

注意事项

一般我们在本地PC开发的过程中,只要项目能跑起来,那么环境基本上是没有问题的。

但是对于线上环境,往往会存在着许多注意不到的地方,对于线上环境,在部署的时候应当主要注意以下的几个问题

  1. 配置文件 : 非常简单的例子 ,比如下图中的几个配置文件,一定要做出区分,比如数据库或其他配置是否是线上环境准备好的(包括但不限于访问路径,账户,密码以及其他的配置信息)。
  2. 环境配置:比如springboot项目中的spring.profiles.active, 如果我们是手动执行命令, 每次都需要去输入--spring.profiles.active=prod等参数, 对于像我这样手懒的同志非常的不友好, 好在Dockerfile可以帮助我们很好的解决这个问题。
  3. 安全性: 比如防火墙配置、HTTPS 配置、认证和授权等(对于腾讯云, 阿里云等云服务厂商, 一定要在服务器的安全组中去配置访问权限)。

有关其他常见容器的配置 , 请参考 docker常用容器部署命令总结


打包

接着就可以开始准备jar包了

对于默认使用spring-initialer准备的项目或者是git clone的项目 , pom文件中一般都会配备 spring-boot-maven-plugin的插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>

在pom的build标签中我们可以自定义构建jar包时候的选项.

比如使用<finalName>${name}</finalName> 来定义jar包的名称。

这里使用项目名称来命名

打包建议使用IDEA的UI来进行操作(防止手抖输错命令)

package完成之后我们可以看到在project/target目录下有打包好的jar文件

这里我们直接在当前目录 java -jar ${name}.jar即可运行项目

准备Dockerfile

这里先给出一个Dockerfile的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM openjdk:8-jdk-alpine
LABEL maintainer="adorabled4 <dhx2648466390@163.com>"
# 设置时区
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata
# 创建工作目录
RUN mkdir -p /app/hxBI
# 将所有jar文件添加到对应模块的目录中 => 需要注意的是 , TODO 构建的时候是以dockerfile所在的目录开始的
COPY jar/hxBI.jar /app/hxBI
# 暴露端口号
EXPOSE 6848

# 运行所有jar文件 : --spring.profiles.active=prod 指定项目的运行环境
CMD ["sh", "-c", "java -jar /app/hxBI/hxBI.jar --spring.profiles.active=prod"]

其中必要的注释都已在上面给出 ,

关于FROM openjdk:8-jdk-alpine

FROM openjdk:8-jdk-alpine:这条指令定义了基础镜像。它告诉 Docker 使用名为 openjdk 的镜像,并选择标签为 8-jdk-alpine,这意味着基础镜像是一个包含 OpenJDK 8 和 Alpine Linux 的镜像。Alpine Linux 是一个轻量级的 Linux 发行版。

简单来讲 , 这一行代码就可以代替我们手动去完成

  1. 下载jdk
  2. export path

并且丝毫不用担心服务器中JDK过多导致的版本冲突问题。

需要注意的是 : RUN , COPY CMD 等操作都是以以dockerfile所在的目录开始的 , 也就是说使用的是相对路径

构建镜像

使用docker build 命令来构建镜像

docker build 命令的常见参数和用法如下:

1
docker build [OPTIONS] PATH | URL | -

其中,OPTIONS 是一些可选参数,PATH 是 Dockerfile 所在的路径。URL 表示可以从远程仓库中获取 Dockerfile,而 - 表示从标准输入中读取 Dockerfile。

常用的 docker build 参数包括:

  • --tag-t:为构建的镜像指定标签。标签的格式一般是 repository:tag,例如 myapp:latest
  • --file-f:指定要使用的 Dockerfile 文件的路径。如果你的 Dockerfile 不是默认的 Dockerfile,可以使用这个参数来指定。
  • --build-arg:传递构建参数给 Dockerfile。可以在 Dockerfile 中使用 ARG 指令来引用这些参数。
  • --no-cache:禁止使用缓存的镜像层。如果在构建过程中某一层的镜像发生了变化,Docker 默认会使用缓存,但使用这个参数会禁用缓存。

示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在当前目录下的 Dockerfile 中构建镜像,并设置标签为 myapp:latest
docker build -t myapp:latest .

# 使用指定的 Dockerfile 文件构建镜像,并设置标签
docker build -t myapp:latest -f Dockerfile.dev .

# 从远程 Git 仓库中的 Dockerfile 构建镜像,设置标签
docker build -t myapp:latest https://github.com/user/repo.git#branch:path/to/Dockerfile

# 传递构建参数给 Dockerfile
docker build --build-arg APP_VERSION=1.0 -t myapp:latest .

# 构建镜像时禁用缓存
docker build --no-cache -t myapp:latest .

这里我使用的命令是 docker build -t hxbi:v1 .

. 表示使用当前工作目录下的Dockerfile文件

具体过程记录如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@iZ0jld3sffhskpkba9tnt9Z app]# pwd
/app
[root@iZ0jld3sffhskpkba9tnt9Z app]# ls
dist dist.tgz Dockerfile Dockerfile-frontend jar
[root@iZ0jld3sffhskpkba9tnt9Z app]# ls jar
hxBI.jar
[root@iZ0jld3sffhskpkba9tnt9Z app]# docker build -t hxbi:v1 .
[+] Building 0.8s (9/9) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 664B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/openjdk:8-jdk-alpine 0.7s
=> [1/4] FROM docker.io/library/openjdk:8-jdk-alpine@sha256:94792824df2df33402f201713f932b58cb9de94a0cd524164a0f228334 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 59B 0.0s
=> CACHED [2/4] RUN apk add --no-cache tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo " 0.0s
=> CACHED [3/4] RUN mkdir -p /app/hxBI 0.0s
=> CACHED [4/4] COPY jar/hxBI.jar /app/hxBI 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:24d55a0575bee2ecab97b9c04dc1e5fe680942ca4c8af765ff58418f969cf3cb 0.0s
=> => naming to docker.io/library/hxbi:v1 0.0s
[root@iZ0jld3sffhskpkba9tnt9Z app]#

运行容器

运行容器使用 docker run 命令

1
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

其中,OPTIONS 是一些可选参数,IMAGE 是要运行的镜像名称或镜像 ID。COMMAND 表示容器启动后要执行的命令,ARG... 是传递给命令的参数。

常用的 docker run 参数包括:

  • --detach-d:以后台模式运行容器,即使容器内的主进程没有在前台运行,容器也会继续运行。
  • --name:为容器指定一个自定义的名称,可以在后续操作中使用。
  • --publish-p:将容器的端口映射到主机的端口。格式为 hostPort:containerPort
  • --volume-v:将主机文件系统的目录或文件挂载到容器中,以实现数据持久化。
  • --env-e:设置环境变量,供容器内的应用程序使用。
  • --network:指定容器的网络模式,可以是 bridgehostnone 等。
  • --restart:设置容器的重启策略,如 alwaysunless-stopped 等。
  • --rm:容器停止后自动删除容器。通常用于一次性任务。

这里我执行的命令是docekr run -d -p 6848:6848 --name hxbi hxbi:v1

如果你想设置容器自动启动 , 请在后面加上--restart=always

错误排查

首次在线上环境上部署项目总是会伴随着各种各样的问题 ,灵活的使用docker logs命令可以帮助我们更好的去进行运维工作

docker logs 命令的常见参数和用法如下:

1
docker logs [OPTIONS] CONTAINER

其中,OPTIONS 是一些可选参数,CONTAINER 是要查看日志的容器的名称或容器 ID。

常用的 docker logs 参数包括:

  • --follow-f:实时跟踪容器日志输出,类似于 tail -f 命令。在容器内有新的日志输出时会显示在终端上。
  • --since:显示从指定时间戳开始的日志。可以是相对时间(如 10m 表示过去的 10 分钟)或绝对时间(如 2023-08-01T00:00:00)。
  • --tail:只显示最后指定行数的日志,默认是全部显示。
  • --timestamps-t:显示日志条目的时间戳。
  • --details:显示更多的容器运行细节,如容器的创建时间、运行时间等。

示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看容器名为 my-container 的日志(默认显示全部日志)
docker logs my-container

# 实时跟踪容器名为 my-container 的日志
docker logs -f my-container

# 查看容器名为 my-container 的日志,只显示最近 100 行
docker logs --tail 100 my-container

# 查看容器名为 my-container 的日志,显示从过去的 1 小时内的日志
docker logs --since 1h my-container

# 查看容器名为 my-container 的日志,显示时间戳
docker logs -t my-container

当排除到错误之后, 我们可能会需要去删除容器以及镜像, 来重新构建

这里使用docker stop , docker rm , docker rmi 命令

rmi 顾名思义 , remove image( 删除镜像)

比如

1
2
3
4
5
docker stop hxbi;

docker rm hxbi;

docker rmi hxbi:v1;

Shell脚本

对于上面中的部署或者是删除的命令 , 我们可以会需要重复的去执行 , 这里可以去编写一个shell脚本来代理手动去敲命令

关于如何创建并编写文件

  1. touch xxx.sh
  2. vi xxx.sh
  3. shift + insert 复制内容
  4. Esc , 接着在命令行模式下输入 !wq 回车即可

创建容器

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

# 切换到工作目录
cd /app

# 构建 Docker 镜像
docker build -t hxbi:v1 .

# 运行容器
docker run -d -p 6848:6848 --name hxbi hxbi:v1

删除容器

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

# 停止容器
docker stop hxbi

# 删除容器
docker rm hxbi

# 删除镜像
docker rmi hxbi:v1

接着我们可以 run xxx.sh 来执行shell脚本.

如果没有权限请先试用chmod命令来修改文件的权限(推荐 chmod xxx.sh 755)

前端部署

打包Dist目录

直接在前端的工作目录下输入npm build即可

打包好的Dist目录下是一些静态的资源文件 ,我们只需要把它放入nginx中即可

下载Nginx

如果你想用Docker去部署nginx容器 , 请参考 https://blog.dhx.icu/2023/01/30/Linux/docker常用容器/

在下载nginx之前, 建议先在服务器上安装宝塔面板 , 通过面板的UI去执行操作 , 十分方便快捷。

宝塔安装命令 :

if [ -f /usr/bin/curl ];then curl -sSO download.cnnbt.net/install_panel.sh;else wget -O install_panel.sh download.cnnbt.net/install_panel.sh;fi;bash install_panel.sh ed8484bec

接着我们访问宝塔面板

如果你忘记了面板的地址, 请在命令行中输入bt接着通过提示信息来进行操作。

软件商店中搜索nginx ,直接安装即可。

部署静态文件

首先在面板中添加站点(如果没有域名建议去国内云服务厂商购买域名,第一年只需要不到十块钱(

这里在添加完了域名之后 , 记得去域名解析中添加域名配置

  • A记录
  • 指向服务器的IP地址即可

点击宝塔面板左侧 文件选项, 进入到站点的工作目录 , 将我们dist目录下的静态资源文件上传到其中即可。

记得替换掉原本的index.html

更改配置文件

由于我们部署的是前后端分离的相面, 并且nginx本身处于安全考虑 , 原本的访问路径都会被替换到我们当前的前端的路径中 , 因此需要在nginx中配置反向代理.

这里给出核心的配置

1
2
3
4
5
6
7
8
9
10
location / {
# 用于配合 browserHistory使用
try_files $uri $uri/index.html /index.html;
}
location /api {
proxy_pass http://${backend_project_ip}:${backend_project_port};
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}

其中/api表示 代理的路径前缀

关于NGINX反向代理

那么对于代理我们可以这样来理解

  • 正向代理 : 比如我们平时使用的VPN , 是用户主动代理的, 就是正向代理

  • 反向代理 : 用户不知道的, 由服务提供者来设置的代理, 表面上用户访问的域名通过DNS解析到了某一台服务器的IP地址, 可实际上为用户提供服务的并不一定是这台机器(或者是端口) ,

    那么也就是NGINX这里起到的作用 : 反向代理 , 我们也可以在这里做其他的操作, 比如负载均衡 , 黑白名单等等

Nginx配置反向代理,一篇搞定! - 知乎 (zhihu.com)

在线访问

当你正确完成了上述的内容 , 访问部署的域名(或者是ip) , 即可查看到部署的前端页面。