记账项目部署
将项目前端、后端、数据库这三个服务打包的镜像,push 到 docker hub,再利用 docker compose 统一拉取新版本镜像进行服务端部署
后端
- 流程
容器共需两个基础镜像,一个用来构建(golang:1.21-alpine3.20 AS builder
),一个用来运行项目(alpine:latest
)
构建时先复制依赖文件,安装依赖,之后再将源码复制到容器进行构建
构建产物及环境变量文件复制到运行时的镜像中,启动项目即可
- 区别环境变量
在构建运行时镜像配置中,添加容器环境变量;启动项目之前将环境变量文件复制到运行时容器中
# ...
FROM alpine:latest
ENV GIN_ENV prod
ENV GIN_MODE release
# ...
WORKDIR /app
COPY --from=builder /app/main /app/main
COPY --from=builder /app/.env.prod /app/.env.prod
# ...
然后代码中通过 GIN_ENV
区别加载的文件,注意是运行时才会区别环境而不是构建时
// 获取环境变量 GIN_ENV,默认值为 "dev"
env := os.Getenv("GIN_ENV")
if env == "" {
env = "dev"
}
// 确定要加载的 .env 文件
envFile := filepath.Join(basepath, "..", ".env."+env)
// 加载环境文件
err := godotenv.Load(envFile)
- 数据库连接要配置容器名而不是 localhost 或 ip
# env
DB_DSN=username:password@tcp(mysql-container-name:3306)/db_name?charset=utf8mb4&parseTime=True&loc=Local
- 记得配置 go 代理,加速依赖包
FROM golang:1.21-alpine3.20 AS builder
WORKDIR /app
ENV GOPROXY=https://goproxy.cn,direct
# ...
- docker build 报错
failed commit on ref "layer-sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1": "layer-sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1" failed size validation: 0 != 32: failed precondition
解决方法:使用 docker pull
手动拉取镜像
- 先同步数据库,再启动项目
CMD ["sh", "-c", "./main db migrate:create && ./main server"]
- 镜像构建脚本
#!/bin/bash
# push.sh
VERSION=$1
if [ -z "$VERSION" ]; then
echo "Error: No version specified."
exit 1
fi
docker build -t account-app-backend:$VERSION .
docker tag account-app-backend:$VERSION gsemir/account-app-backend:$VERSION
docker push gsemir/account-app-backend:$VERSION
先构建镜像,再打标签,最后 push 到 dockerhub。在执行脚本时必须传递版本号 sh push.sh 0.1.0
前端
- 流程
与后端差不多,需要两个基础镜像,一个用来构建(node:20-alpine3.20 AS build
),一个用来运行(nginx:alpine
)
构建时同样先复制 package.json 与 package-lock.json 到镜像,安装依赖,之后再打包
启动时使用 nginx 开启服务即可
- 跨域
使用 nignx 反向代理浏览器发出的 api 接口请求
location /api/ {
proxy_pass http://account-app-backend:8080/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
此时前端的 axios 的 baseURL
要使用本地的 origin,即 window.location.origin
,不能写死
写死的话 nginx 根本不会匹配到对应的 url 并转发,只能靠后端改 CORS 响应头
- Vite 多环境配置
根目录定义 .env.development
和 .env.production
等多环境环境变量文件
页面中使用 import.meta.env.XXX_XXX
来访问环境变量
注意 Vite 会在构建时替换对应的环境变量的值,因此在 build 项目前要声明 NODE_ENV 环境变量
- NODE_ENV 与依赖
这里还存在一个问题,就是如果设置了 NODE_ENV=production
环境变量,npm 在安装依赖时会忽略掉 devDependencies
但是通常情况下,构建阶段应包含所有开发依赖。所以这里我们把设置 NODE_ENV 的步骤放到安装依赖之后、打包项目之前
npm ci
替换 npm install
npm ci 会严格安装 lock
文件安装依赖,忽略对 package.json 中版本范围的解析等过程,且安装依赖前会删除现有的 node_modules 目录
总的来说,npm ci
更适合 CICD 的流程中,更严格、更快、更干净地安装依赖
数据库
数据库使用官方的 mysql:latest
镜像,使用 docker run 命令直接启动也可以
docker run -d \
--name account-app-mysql \
--network network1 \
-e MYSQL_ROOT_PASSWORD=xxx \
-e MYSQL_DATABASE=account_app_db \
-e MYSQL_USER=xxx \
-e MYSQL_PASSWORD=xxx \
-p 3307:3306 \
-v mysql-data:/var/lib/mysql \
mysql:latest
但是后面会集成到 docker compose 中
docker-compose
Docker compose 的配置实际上就是整合多个容器服务的 docker run 命令
- network
注意配置网络环境,保证三个服务在同一网络环境下
services:
frontend:
image: gsemir/account-app-frontend:0.1.3
networks:
- account-app-network
backend:
image: gsemir/account-app-backend:0.1.0
networks:
- account-app-network
db:
image: mysql:latest
networks:
- account-app-network
networks:
account-app-network:
driver: bridge
- 端口
注意端口映射,ports: 70:80
表示宿主环境的(外部)的 70 端口对应容器内部环境的 80 端口,即服务启动(暴露)的端口
- 环境变量与持久化
会自动读取根目录中的 .env 文件中定义的数据库连接所需的环境变量
services:
db:
image: mysql:latest
container_name: account-app-mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data: