Docker实践:部署前后端分离项目
前言
笔者最近正在写一个前后端分离项目,涉及的技术栈比较传统,就是SpringBoot+Vue3。
整个项目需要部署到一个新的Linux服务器,鉴于配置各种环境和依赖大概率要碰一鼻子的灰,我使用了Docker进行环境的部署。
这个过程可以顺带复习一下很久没用的Docker命令,经过一番折腾和踩坑后,我决定写一篇博客记录一下整个部署过程以及一些比较坑的点。
安装Docker
笔者采用的云服务器进行部署,系统为Ubuntu22,该系统镜像并未预装Docker,因此需要手动安装。
安装docker
- 首先,更新系统的软件包列表:
sudo apt update
- 安装必要的软件包,以允许apt使用HTTPS:
sudo apt install apt-transport-https ca-certificates curl software-properties-common
- 添加Docker的官方GPG密钥:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
- 设置稳定的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
- 更新软件包列表:
sudo apt update
- 安装Docker Engine:
sudo apt install docker-ce docker-ce-cli containerd.io
- 添加当前用户到docker用户组,以避免每次运行docker命令都需要sudo:
sudo usermod -aG docker $USER
- 重新登录或运行以下命令以使用户组更改生效:
newgrp docker
安装Docker Compose
网上的Docker Compose安装教程都要从GitHub上获取最新的二进制文件,但是由于笔者的服务器在国内,这一步根本跑不起来。在这种情况下,可以采用apt的方式进行安装。
sudo apt install docker-compose
现在,你已经成功在Ubuntu服务器上安装了Docker和Docker Compose。你可以通过运行docker --version
和docker-compose --version
来验证安装。
准备Jar包和Dockerfile
- 使用IDEA对Java程序进行打包。
这一步需要配置好maven的打包插件
spring-boot-maven-plugin
,否则启动后会报出找不到主类的错误。
- 编写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 保姆级文档
下面演示大致流程:
- 打包为dist目录
pnpm build
- 常用的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脚本的办法。
- 用FTP上传sql脚本。
myapp.sql
。 - 将sql放到容器内。执行:
docker cp myapp.sql myapp-mysql:/home
- 进入容器运行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密码,但是进不去。
解决方法;
- 在配置my.cnf中新增:
[mysqld]
skip-grant-tables
- 重启MySQL容器
docker restart myapp-mysql
- 跳过密码进入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
- 删除第一步添加的配置,重启MySQL容器。
- 再次进入MySQL容器就正常了。
部署Java容器的日志提示:找不到DataSource
- 检查生产环境和开发环境有没有配置对。
- 在application-prod.yml中检查是否开启了多数据源,如果不需要就切换为单数据源。
笔者就是在这里卡住了很久,因为在开发过程中我使用了p6spy并开启了MybatisPlus的多数据源,刚开始我没注意,并没有修改,导致容器一直无法运行。
随后我定位到此问题,在yml文件中改成了单数据源,奇怪的是容器还是依旧跑不起来,一直提示数据源出问题。一顿排查才知道,原来还需要注释掉pom.xml中的多数据源依赖,否则会报错!!
- 感谢你赐予我前进的力量