前言

笔者最近正在写一个前后端分离项目,涉及的技术栈比较传统,就是SpringBoot+Vue3。

整个项目需要部署到一个新的Linux服务器,鉴于配置各种环境和依赖大概率要碰一鼻子的灰,我使用了Docker进行环境的部署。

这个过程可以顺带复习一下很久没用的Docker命令,经过一番折腾和踩坑后,我决定写一篇博客记录一下整个部署过程以及一些比较坑的点。

安装Docker

笔者采用的云服务器进行部署,系统为Ubuntu22,该系统镜像并未预装Docker,因此需要手动安装。

安装docker

  1. 首先,更新系统的软件包列表:
sudo apt update
  1. 安装必要的软件包,以允许apt使用HTTPS:
sudo apt install apt-transport-https ca-certificates curl software-properties-common
  1. 添加Docker的官方GPG密钥:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
  1. 设置稳定的Docker存储库:
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  1. 更新软件包列表:
sudo apt update
  1. 安装Docker Engine:
sudo apt install docker-ce docker-ce-cli containerd.io
  1. 添加当前用户到docker用户组,以避免每次运行docker命令都需要sudo:
sudo usermod -aG docker $USER
  1. 重新登录或运行以下命令以使用户组更改生效:
newgrp docker

安装Docker Compose

网上的Docker Compose安装教程都要从GitHub上获取最新的二进制文件,但是由于笔者的服务器在国内,这一步根本跑不起来。在这种情况下,可以采用apt的方式进行安装。

sudo apt install docker-compose

现在,你已经成功在Ubuntu服务器上安装了Docker和Docker Compose。你可以通过运行docker --versiondocker-compose --version来验证安装。

准备Jar包和Dockerfile

  1. 使用IDEA对Java程序进行打包。

这一步需要配置好maven的打包插件spring-boot-maven-plugin,否则启动后会报出找不到主类的错误。

  1. 编写DockerFile文件。

就简单的使用jdk8的镜像,然后做个入口即可。

FROM openjdk:8-jdk-alpine

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY myapp.jar /app.jar

ENTRYPOINT ["java", "-jar", "/app.jar"]

前端文件打包

笔者的前端项目采用Vite构建,关于前端文件的打包和部署,开源项目pure-admin写得非常详细,非常值得参考。文档地址:打包和部署 | Pure Admin 保姆级文档

下面演示大致流程:

  1. 打包为dist目录
pnpm build
  1. 常用的Nginx配置
location / {
    root   html;
    index  index.html index.htm;
    # 用于配合前端路由为h5模式使用,防止刷新404 https://router.vuejs.org/zh/guide/essentials/history-mode.html#nginx
    try_files $uri $uri/ /index.html;
}

# 代理后端地址(与vite.config.ts 内的保持一致,有多个后端就写多个)
location /api {
    # 如果后端在本地比如127.0.0.1或者localhost请解开下面的rewrite注释即可
    # rewrite  ^.+api/?(.*)$ /$1 break;
    # 这里填写后端地址(后面一定不要忘记添加 / )
    proxy_pass http://127.0.0.1:3000/;
    proxy_set_header Host $host;
    proxy_set_header Cookie $http_cookie;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_redirect default;
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Headers X-Requested-With;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
}

数据卷挂载准备和放置前端文件

主要完成MySQL和nginx的数据卷挂载,同时将前端文件上传到Nginx的目录。

# mysql 挂载准备
mkdir mysql
cd mysql
mkdir data
mkdir log
mkdir conf
cd ..
# nginx 挂载准备
mkdir nginx
cd nginx
mkdir html
# nginx配置文件
vim nginx.conf # 根据需求写配置文件,默认用80端口
# 将dist文件放到nginx/html并解压
cd ..
mv dist.zip nginx/html
cd nginx/html
unzip dist.zip
rm -rf dist.zip

配置docker-compose.yml

下面的docker-compose文件部署了MySQL5.7、Redis、Nginx,并且会自动构建刚才写好的Dockerfile。

version: "3.8"

services:
  mysql:
    image: mysql:5.7
    container_name: myapp-mysql
    ports:
      - "3306:3306"
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 你的MYSQL密码
    volumes: # 数据卷挂载
      - /root/mysql/conf/my.cnf:/etc/mysql/my.cnf
      - /root/mysql/data:/var/lib/mysql
      - /root/mysql/log:/var/log/mysql
    networks:
      - myapp-net
  redis:
    image: redis
    container_name: myapp-redis
    ports:
      - "6379:6379"
    networks:
      - myapp-net
  myapp-java:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: myapp-java
    ports:
      - "8089:8089"
    networks:
      - myapp-net
    depends_on:
      - mysql
      - redis
  nginx:
    image: nginx
    container_name: nginx
    ports:
      - "80:80"
    volumes:
      - "./nginx/nginx.conf:/etc/nginx/nginx.conf"
      - "./nginx/html:/usr/share/nginx/html"
    depends_on:
      - myapp-java
    networks:
      - myapp-net
networks:
  myapp-net:
    name: myapp-java

部署

执行下面命令构建并运行容器:

docker compose up -d

检查容器情况,执行下面命令,如果redis、MySQL、Java、nginx四个容器都启动了,说明部署成功。

docker compose ps

导入数据库脚本

接下来是导入数据库数据,按理说将SQL脚本放到mysql/init下并且在docker-compose文件中挂载好数据卷即可,但我试过一直有问题。鉴于我的SQL就一个,我采用直接进入容器执行SQL脚本的办法。

  1. 用FTP上传sql脚本。myapp.sql
  2. 将sql放到容器内。执行:docker cp myapp.sql myapp-mysql:/home
  3. 进入容器运行MYSQL,执行下面命令。
docker exec -it myapp-mysql mysql -uroot -p # 进入容器
# 下面是进入MySQL终端后执行的命令
CREATE DATABASE IF NOT EXISTS myapp CHARACTER SET utf8mb4
use myapp
source /home/myapp.sql

最后,查看各个容器的日志,观察服务是否正常启动,尤其是Java程序,可以观察有无报错信息。

常见问题

部署后java容器提示:no main manifest attribute, in test-0.0.1-SNAPSHOT.jar

这表明maven中未配置打包插件,可以在pom.xml上加入:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.6.3</version>
    <configuration>
        <mainClass>你的启动类</mainClass>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

明明配好了MySQL密码,但是进不去。

解决方法;

  1. 在配置my.cnf中新增:
[mysqld]
skip-grant-tables
  1. 重启MySQL容器
docker restart myapp-mysql
  1. 跳过密码进入MySQL,设置密码
# 进入mysql
docker exec -it myapp-mysql mysql -uroot -p

# 执行下面SQL命令
mysql> use mysql;
mysql> update mysql.user set authentication_string = password("123456") where user="root";
mysql> exit
  1. 删除第一步添加的配置,重启MySQL容器。
  2. 再次进入MySQL容器就正常了。

部署Java容器的日志提示:找不到DataSource

  1. 检查生产环境和开发环境有没有配置对。
  2. 在application-prod.yml中检查是否开启了多数据源,如果不需要就切换为单数据源。

笔者就是在这里卡住了很久,因为在开发过程中我使用了p6spy并开启了MybatisPlus的多数据源,刚开始我没注意,并没有修改,导致容器一直无法运行。

随后我定位到此问题,在yml文件中改成了单数据源,奇怪的是容器还是依旧跑不起来,一直提示数据源出问题。一顿排查才知道,原来还需要注释掉pom.xml中的多数据源依赖,否则会报错!!