Files
easyai-ai-gateway/docs/runbooks/production-ci-cd.md
T
easyai 762d61c9cf docs(release): 补充仓库 Actions 停用验收
记录仓库级 has_actions=false 和 main 无保护规则的只读验收命令。
2026-07-22 15:17:11 +08:00

136 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 人工生产发布运行手册
## 原则
生产发布没有自动触发器。允许 Agent 直接提交和推送 `main`,但 Git 操作不会授权 publish 或 deploy
1. 用户明确要求发布镜像后,Agent 执行本地 publish。
2. Agent 报告 manifest、digest、组件和冒烟结果并停止。
3. 用户再次明确确认上线后,Agent 执行 deploy。
不得把两步放进同一个命令、Git hook、Gitea Action、Webhook、服务、Timer 或轮询脚本。
## 一次性停用旧自动化
源码仓已经删除 `.gitea/workflows` 中的全部工作流,仓库级 Actions 开关必须为关闭;`main` 分支保护和 required status 也必须保持关闭。可用下列只读命令验收:
```bash
tea api /repos/BCAI/easyai-ai-gateway | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>console.log(JSON.parse(s).has_actions))'
tea branches list --repo BCAI/easyai-ai-gateway --output yaml | sed -n '/^- name: main$/,+3p'
```
第一条预期输出 `false`;分支状态预期 `protected: "false"``user-can-push: "true"`
确认没有运行中任务后,在旧 Runner 主机执行:
```bash
systemctl disable --now easyai-gateway-ci-v2-runner.service
systemctl is-active easyai-gateway-ci-v2-runner.service
```
预期第二条命令返回 `inactive`。保留 `easyai-gateway-ci-v2-data` volume 观察一段时间,不立即删除。
生产部署仓不得保留 Tag/Main dispatcher、Webhook receiver 或 Timer。先只读列出相关单元,确认精确名称后逐个 `disable --now`,不得用模糊匹配批量停止其他 EasyAI 服务:
```bash
systemctl list-unit-files --type=service --type=timer | grep -Ei 'easyai.*(release|dispatch|deploy)'
```
## 安装固定生产助手
生产助手只解析 manifest、拉取 digest、备份/迁移、更新 Compose、探活和回滚;它不是常驻服务,也不读取源码 checkout。
把以下三个已审查文件复制到生产服务器:
- `deploy/manual/easyai-ai-gateway-release`
- `deploy/manual/easyai-ai-gateway-release.conf.example`
- `scripts/release-manifest.mjs`
在可信 checkout 中可运行一次安装器:
```bash
sudo ./deploy/manual/install-release-helper.sh
sudoedit /etc/easyai-ai-gateway-release.conf
sudo /usr/local/sbin/easyai-ai-gateway-release status
```
配置文件必须为 root 所有、`0600`。助手安装后不会创建或启用 service、timer、webhook 或 watcher。
默认生产资源:
- 部署目录:`/root/easyai-ai-gateway-deploy`
- 发布清单:`/root/easyai-ai-gateway-deploy/.releases`
- 数据库备份:`/var/backups/easyai-ai-gateway`
- 公网入口:`https://ai.51easyai.com`
## 阶段一:本地 publish
前置条件:Docker Desktop/Engine、Buildx、Compose v2、Node、Go、可用 Registry 登录和生产只读 SSH。源码必须无 tracked/untracked 改动,HEAD 必须属于最新获取的 `origin/main` 历史。
```bash
docker login --username=<your-aliyun-account> registry.cn-shanghai.aliyuncs.com
./scripts/publish-release-images.sh --components auto
```
`auto` 读取线上当前 manifest,与 HEAD 比较后选择:
- `api`API、Go workspace 或迁移变化;
- `web`Web、contracts、pnpm 或 Nginx 容器配置变化;
- `all`Dockerfile/Compose 等共享运行时变化;
- `none`:无运行时变化,不产生发布物。
首次发布或线上状态不可验证时只能显式使用:
```bash
./scripts/publish-release-images.sh --components all
```
此路径保守地把迁移标记为变化:生产助手会先备份数据库,再运行新 API 镜像内的幂等 migrator。
publish 会执行快速 Go 测试、迁移安全校验、`linux/amd64` 构建、临时完整栈迁移和 API simulation 冒烟。全部通过后才推送完整 SHA Tag,并生成带 SHA-256 内容完整性字段的固定 schema manifest
```text
dist/releases/<40 位 Git SHA>.json
```
成功输出必须包含 `production_changed=false`。此时 Agent 应报告结果并停止,不得继续上线。
## 阶段二:人工 deploy
收到用户再次确认后执行:
```bash
./scripts/deploy-production-release.sh dist/releases/<40 位 Git SHA>.json
```
客户端和生产助手都会确认当前生产 SHA 仍等于 manifest 的 `baseReleaseSha`。不相等说明 publish 之后生产发生变化,必须重新 publish,不能覆盖或使用强制参数。
助手随后验证:
- API/Web 均为完整 Registry digest
- 镜像为 `linux/amd64`
- 本次变化组件的 OCI revision 等于 release SHA
- 迁移变化时备份可被 `pg_restore -l` 读取;
- 内部 API health/readiness/OpenAPI
- Web 页面和 Web API 反代;
- 公网 health/readiness/OpenAPI。
失败时自动恢复上一组应用 digest 并重复探活。已经执行的 expand-only 数据库迁移不会自动恢复。
## 状态与回滚
```bash
./scripts/deploy-production-release.sh --status
./scripts/deploy-production-release.sh --rollback <服务器已有的历史 SHA>
```
回滚也需要用户明确指令。服务器只接受 `.releases/<SHA>.json` 中已有版本,拒绝远端任意 Tag、`latest` 或临时镜像。
## 故障排查
- publish 前失败:没有推送镜像,也没有可部署 manifest;修复本地环境后重试。
- 镜像已经部分推送但 manifest 未生成:SHA Tag 被视为占用,先审查 Registry 中的 digest,不得覆盖重推。
- deploy 报基线过期:重新读取线上状态并重新 publish。
- 迁移备份失败:生产不切换。
- 生产探活和自动应用回滚都失败:停止发布,保留日志、当前数据库和 release manifest,人工恢复服务;不得自动执行 `pg_restore`