176 lines
6.1 KiB
Markdown
176 lines
6.1 KiB
Markdown
# 部署手册(Docker · 蓝绿零停机)
|
||
|
||
## 0. 一图看懂
|
||
|
||
```
|
||
浏览器 ──▶ proxy(nginx:80,常驻,发布时从不重建)
|
||
├─ /api ──▶ backend_a 或 backend_b
|
||
├─ / ──▶ web_a 或 web_b
|
||
├─ 18090(仅容器网络)SSR 取数 → 当前活跃后端
|
||
├─ 127.0.0.1:18091 前端构建期取 SSG
|
||
└─ 127.0.0.1:18092 管理后台(SSH 隧道)
|
||
```
|
||
|
||
发布时只重建**空闲槽**,等它健康后改写切换文件并 `nginx -s reload`,在途请求由旧槽跑完。
|
||
镜像以 `RELEASE`(`时间戳-后端提交号`)打标签,**不可变**,历史版本不会被覆盖。
|
||
|
||
部署入口在本仓库(后端仓),脚本会自动把前端仓也拉到位。
|
||
|
||
---
|
||
|
||
## 1. 服务器准备(只做一次)
|
||
|
||
```bash
|
||
# Ubuntu 22.04 / 24.04,2 核 4G 起
|
||
curl -fsSL https://get.docker.com | sh
|
||
apt install -y docker-compose-plugin git
|
||
|
||
mkdir -p /opt/fashion && cd /opt/fashion
|
||
git clone <后端仓库> backend
|
||
git clone <前端仓库> test # 必须与 backend 同级
|
||
|
||
cd backend
|
||
cp .env.example .env && vi .env
|
||
chmod +x deploy.sh init-db.sh
|
||
```
|
||
|
||
私有仓库需要配好拉取凭据:SSH deploy key,或 HTTPS token + `git config credential.helper store`。
|
||
|
||
---
|
||
|
||
## 2. 首次部署(只做一次)
|
||
|
||
```bash
|
||
./init-db.sh /path/to/your.sql # 建库(传入与当前代码匹配的 SQL)
|
||
./deploy.sh all # 启动全部服务
|
||
curl http://127.0.0.1/api/health # {"status":"ok"}
|
||
```
|
||
|
||
`.env` 必填 6 项:
|
||
|
||
| 变量 | 说明 |
|
||
| --- | --- |
|
||
| `DB_PASSWORD` | 数据库口令,别沿用开发库的 |
|
||
| `HASHID_SECRET` | 公开 ID 混淆盐,**上线后不要改**(改了所有旧链接失效) |
|
||
| `JWT_SECRET` | 登录令牌密钥 |
|
||
| `INGEST_SECRET` | 爬虫上报密钥,须与爬虫端一致 |
|
||
| `S4_AK` / `S4_SK` | 对象存储密钥,缺了图片全 404 |
|
||
|
||
---
|
||
|
||
## 3. 日常发布
|
||
|
||
```bash
|
||
./deploy.sh # 拉 main + 只更新后端(默认)
|
||
./deploy.sh all # 拉 main + 前后端都更新
|
||
./deploy.sh web # 只更新前端
|
||
./deploy.sh all --no-pull # 不拉代码,用服务器现有代码构建
|
||
GIT_BRANCH=dev ./deploy.sh all # 临时指定分支(优先于 .env)
|
||
```
|
||
|
||
**拉代码**(`.env` 的 `GIT_BRANCH`,默认 `main`):
|
||
|
||
- 后端仓 `.` 和前端仓 `$WEB_DIR`(默认 `../test`)都会 `fetch` → 必要时 `checkout` → `pull --ff-only`
|
||
- **不能快进就报错退出**,不会硬覆盖(有本地改动时会提示 `reset --hard` 命令)
|
||
- 每次打印实际提交:`后端:main @ a1b2c3d fix: xxx`
|
||
|
||
**发布过程**:
|
||
|
||
| 步 | 动作 | 用户感受 |
|
||
| --- | --- | --- |
|
||
| 1 | 生成 `RELEASE`,拉代码 | 无感 |
|
||
| 2 | `docker compose build` 构建 `fashion-*:$RELEASE` | 无感(旧槽在跑) |
|
||
| 3 | 重建空闲槽 | 无感 |
|
||
| 4 | 轮询 health,未通过则**直接退出** | 旧槽继续服务 |
|
||
| 5 | 改切换文件 → `nginx -t` → `nginx -s reload` | 在途请求不丢 |
|
||
| 6 | 写 `.deploy/releases.log` | — |
|
||
|
||
---
|
||
|
||
## 4. 回滚
|
||
|
||
```bash
|
||
./deploy.sh --rollback
|
||
# 回滚 backend:b → a(该槽镜像 fashion-backend:20261018-2210-9f3e1a2)
|
||
```
|
||
|
||
不动代码、不重建容器,只改 nginx 指向 + reload,秒级生效。
|
||
前提是该槽容器还在(正常交替发布时,非活跃槽就是上一版)。
|
||
|
||
---
|
||
|
||
## 5. 版本与镜像管理
|
||
|
||
```bash
|
||
cat .deploy/releases.log # 发布历史
|
||
docker inspect -f '{{.Config.Image}}' $(docker compose ps -q backend_a) # 线上版本
|
||
./deploy.sh --prune # 清理旧镜像
|
||
KEEP_IMAGES=3 ./deploy.sh --prune # 只留 3 个
|
||
```
|
||
|
||
镜像标签形如 `fashion-backend:20261019-153012-a1b2c3d`(时间 + 提交号),不会被自动清理。
|
||
`latest` 只是手动执行 compose 命令的别名,`deploy.sh` 从不依赖它。
|
||
|
||
---
|
||
|
||
## 6. 必须记住的规则
|
||
|
||
1. **默认发布 `main`,日常开发在 `dev`** —— 直接 `./deploy.sh all` 会部署 main 上的旧代码。要么先 merge,要么 `GIT_BRANCH=dev ./deploy.sh all`。
|
||
2. **改表要向后兼容** —— 切换的几十秒里新旧代码同时在跑。加字段/加表可以;改类型、删字段要拆两次发布。
|
||
3. **`HASHID_SECRET` 上线后别动**。
|
||
4. **镜像会占磁盘**(前端约 250MB/版),每 5 版跑一次 `./deploy.sh --prune`。
|
||
|
||
---
|
||
|
||
## 7. 管理后台
|
||
|
||
```bash
|
||
ssh -L 18092:127.0.0.1:18092 user@server
|
||
# 本机浏览器打开 http://localhost:18092/admin
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 域名 / HTTPS
|
||
|
||
前端只监听 80,二选一:
|
||
|
||
- **宿主机 nginx / 宝塔**:`.env` 里 `HTTP_PORT=8080`,外层反代到 `127.0.0.1:8080`,证书在外层做
|
||
- **Caddy(自动证书)**:同 compose 加 caddy 容器,`reverse_proxy 127.0.0.1:80`
|
||
|
||
---
|
||
|
||
## 9. 备份
|
||
|
||
```bash
|
||
docker compose exec -T db pg_dump -U fashion fashion | gzip > backups/db-$(date +%F).sql.gz
|
||
```
|
||
|
||
每天 4 点自动备份并保留 7 天:
|
||
|
||
```
|
||
0 4 * * * cd /opt/fashion/backend && docker compose exec -T db pg_dump -U fashion fashion | gzip > backups/db-$(date +\%F).sql.gz && find backups -name 'db-*.sql.gz' -mtime +7 -delete
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 排障
|
||
|
||
```bash
|
||
docker compose ps # 各容器状态与健康
|
||
docker compose logs -f proxy # 切换失败先看这里
|
||
docker compose logs -f backend_a
|
||
docker compose logs --tail 100 web_b
|
||
```
|
||
|
||
| 现象 | 原因 |
|
||
| --- | --- |
|
||
| 全站接口 404 | `nginx/default.conf` 的 `proxy_pass` 带了 URI/斜杠(会剥掉 `/api` 前缀) |
|
||
| 详情页 500 | 前端 `BASE_API_SSR` 必须是 `http://proxy:18090`(容器里的 localhost 是它自己) |
|
||
| 图片全 404 | `S4_AK/S4_SK` 没填或填错 |
|
||
| 前端构建失败 | 构建期要连 18091 取 SSG:确认 `proxy` 在跑、`backend_a` healthy |
|
||
| 发布后仍是旧版本 | 分支不对(默认 `main`,日常在 `dev`);或 `docker compose logs proxy` 里 reload 报错 |
|
||
| 回滚提示"槽不可用" | 该槽被更早的发布重建过,回滚只能退一格 |
|
||
| 登录后跳回登录页 | `JWT_SECRET` 改过,旧 token 失效 |
|
||
| 磁盘涨得快 | `./deploy.sh --prune` |
|