Files
NianAIGC/docs/DEPLOYMENT.md

315 lines
13 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.

# 智念AIGC平台部署说明
## 阿里云 ACK + RDS PostgreSQL(生产)
生产环境设置 `ZHINIAN_DATA_BACKEND=postgres`,并通过 Kubernetes Secret 注入
`DATABASE_URL`。优先使用 RDS 内网连接地址;ACK 节点/Pod 与 RDS 必须位于同一
或网络可达的 VPC,并在 RDS 白名单或安全组中仅放行实际工作负载网段。
启用 RDS SSL 后,下载实例对应的 CA,创建 `zhinian-rds-ca` Secret,并将其挂载到
`/etc/zhinian/rds/ca.pem`;同时设置 `DATABASE_SSL_MODE=verify-full` 和
`DATABASE_CA_CERT_PATH=/etc/zhinian/rds/ca.pem`。不要使用关闭证书校验的配置。
```bash
kubectl -n zhinian create secret generic zhinian-rds-ca \
--from-file=ca.pem=./path/to/downloaded-rds-ca.pem
kubectl apply -f deploy/ack/configmap.yaml
kubectl apply -f deploy/ack/secrets.example.yaml # 仅作模板;先替换全部占位值
kubectl apply -f deploy/ack/web.yaml -f deploy/ack/go-api.yaml \
-f deploy/ack/service.yaml -f deploy/ack/ingress.yaml
```
生产拓扑为 ADR-003 双工作负载:Next.js 只服务页面/静态资源/SSR(不持有任何
RDS/服务商凭据,仅共享会话密钥);Go 工作负载 `zhinian-go-api` 独占
`/api`、`/uploads`、`/generated-results`,内嵌 WorkerLoop(生产**不部署**
Node Worker,`worker.yaml` 已弃用保留)。Ingress 按路径分流:页面 → Web,
后端路径 → Go,`/api/internal/worker` → 无端点 deny Service。
Go 镜像构建:
```bash
docker build -f backend/Dockerfile -t REGISTRY/PROJECT/zhinian-go-api:TAG backend/
```
Go 首次启动时从 `zhinian-go-bootstrap` Secret 读取
`ZHINIAN_BOOTSTRAP_ADMIN_*`,仅当不存在任何超级管理员时创建一次。
数据库 schema 由部署负责人在发布前手工执行,**不部署迁移 Job Pod**(清单
`deploy/ack/migration-job.yaml` 已弃用保留):使用迁移角色账号依次执行
`database/migrations/0001_initial_schema.sql`、`0002_generation_lifecycle_fencing.sql`,
再执行应用角色授权语句(表权限 + 两个并发函数 `claim_generation_jobs` 与
`billing_post_wallet_entry` 的 EXECUTE 权限,参见
`scripts/migrate-postgres.mjs` 中的 `provisionApplicationRole`)。Web/Go 的数据库
账号应只具有应用运行权限。以后每次 schema 变更同样按版本化 SQL 文件手工执行,并在
变更后再滚动工作负载。
迁移 SQL 包含重复数据保护:遇到重复的历史 `usage_events.job_id` 会失败并要求人工
审计,不会自动删除计费/用量记录;清理后重新执行对应文件。
替换模板占位符后,可先运行 `npm run deploy:check` 做仓库内静态契约检查;真正发布前仍需
使用目标 ACK 集群的 `kubectl apply --dry-run=server` 验证 CRD/准入策略和 Ingress 行为。
ACK 中的 Secret/ConfigMap 才是配置事实来源。不要在 `/settings` 页面修改生产密钥:该
页面写入容器内 `.env.local`,Pod 重建会丢失,也不会自动更新 Kubernetes Secret。应修改
Secret/ConfigMap 后触发 Deployment 滚动更新。
模板默认 Web 为 1 副本,因为仅启用 PostgreSQL 并不会共享上传/生成文件。确认这些文件
已使用 OSS 或其他共享对象存储后,才可水平扩容;本地 `.runtime` 数据或文件不能在副本
之间共享。Worker 只持有内部 token 并调用集群内 `zhinian-web` Service,不持有
`DATABASE_URL`。Ingress 通过更长的 `/api/internal/worker` Prefix 把公网请求路由到
没有 Endpoint 的 `zhinian-public-deny` Service,因此不会把内部接口转发给 Web;也可以
在 API Gateway/WAF 再配置等价拒绝规则。不要默认添加依赖 Terway 或特定 ACK 托管组件
的 webhook/白名单注解。
探针约定:`/api/health` 是不查询数据库的进程存活检查;`/api/ready` 会确认 PostgreSQL
关键表存在,并验证应用角色具备任务表读写和两个业务函数的执行权限,失败返回 503。
启动探针避免迁移/冷启动期间过早重启。Worker 每次内部 tick 默认 120 秒超时,避免网络
半开连接让轮询进程永久卡住;可用 `ZHINIAN_WORKER_REQUEST_TIMEOUT_MS` 调整。
连接预算按 `Web 副本数 × DATABASE_POOL_MAX` 计算,并为迁移、管理连接和故障切换
预留余量。滚动更新默认可能短暂同时存在旧、新 Pod;模板将 `maxSurge` 设为 1,容量
预算至少覆盖 `(replicas + 1) × pool max`,否则应降低 pool 或使用 `maxSurge: 0`。
当前 Docker runner 默认以 Node Alpine 镜像的 root 用户运行,模板没有虚构一个未经
镜像验证的 UID。生产加固应在镜像中创建固定非 root 用户、修正 `/app/.runtime` 权限,
验证写入与启动后,再把 Pod 设置为 `runAsNonRoot: true`。
关键数据库变量:
| 变量 | 说明 |
| --- | --- |
| `ZHINIAN_DATA_BACKEND` | 生产固定为 `postgres`;配置错误不会降级到本地 JSON |
| `DATABASE_URL` | PostgreSQL URI,仅存 Secret;不要写入镜像、ConfigMap 或日志 |
| `DATABASE_APP_ROLE` | 应用角色名;手工执行授权语句时使用,与 Web/Go 的 RDS 用户名一致 |
| `DATABASE_SSL_MODE` | `disable` 或 `verify-full`;RDS SSL 生产建议 `verify-full` |
| `DATABASE_CA_CERT_PATH` | 已挂载 CA 文件路径 |
| `DATABASE_POOL_MAX` | 单个 Web Pod 最大连接数 |
| `DATABASE_CONNECTION_TIMEOUT_MS` | 建连超时;模板为 5000 ms |
| `DATABASE_IDLE_TIMEOUT_MS` | 空闲连接回收时间 |
| `DATABASE_STATEMENT_TIMEOUT_MS` | SQL 语句超时 |
旧 `NEXT_PUBLIC_SUPABASE_URL`、`NEXT_PUBLIC_SUPABASE_ANON_KEY` 和
`SUPABASE_SERVICE_ROLE_KEY` 已废弃,直连 PostgreSQL 路径不会读取它们。
本文面向运维部署。推荐使用 Docker Compose,同一套编排会启动 Web 服务和任务 Worker。
## 服务器要求
- Linux 服务器
- Docker
- Docker Compose v2(`docker compose`)或旧版 `docker-compose`
- 可访问外网供应商接口:火山 Visual、EvoLink、Seedance、OSS
## 一键部署
```bash
git clone <仓库地址>
cd NianAIGC
bash scripts/deploy.sh
```
脚本会自动:
- 从 `.env.example` 创建 `.env.local`(如果不存在)
- 创建 `.runtime/data`、`.runtime/uploads`、`.runtime/generated-results`
- 创建 `.runtime/logs`
- 构建镜像
- 启动 `zhinian-aigc` Web 服务
- 启动 `zhinian-worker` 任务 Worker
- 输出容器状态
默认访问:
```text
http://服务器IP:3000
```
## 必填生产配置
部署前编辑 `.env.local`:
```env
APP_PORT=3000
PORT=3000
HOSTNAME=0.0.0.0
NEXT_PUBLIC_APP_URL=https://你的域名
ZHINIAN_AUTH_REQUIRED=auto
ZHINIAN_AUTH_SESSION_SECRET=请替换为强随机会话密钥
ZHINIAN_DATA_BACKEND=postgres
DATABASE_URL=postgresql://应用账号:密码@RDS内网地址:5432/数据库名
DATABASE_SSL_MODE=verify-full
DATABASE_CA_CERT_PATH=/etc/zhinian/rds/ca.pem
ZHINIAN_API_KEYS=partner-a:请替换为强随机key
ZHINIAN_INTERNAL_WORKER_TOKEN=请替换为强随机token
ZHINIAN_WEBHOOK_SECRET=请替换为webhook签名密钥
```
`ZHINIAN_API_KEYS` 冒号前是开放 API 账号 ID。不同账号 ID 的任务、素材和幂等键会写入不同 owner 分区,例如 `api:partner-a`。
真实生成能力按需配置:
```env
IMAGE_GENERATE_ENGINE=evolink
EVOLINK_API_KEY=
VOLCENGINE_ACCESS_KEY_ID=
VOLCENGINE_SECRET_ACCESS_KEY=
SEEDANCE_API_KEY=
ALI_OSS_ENDPOINT=
ALI_OSS_BUCKET=
ALI_OSS_ACCESS_KEY_ID=
ALI_OSS_ACCESS_KEY_SECRET=
ALI_OSS_PUBLIC_BASE_URL=
```
如果不配置真实供应商密钥,mock 配置会保留本地验收能力,但生产对接应配置真实密钥。
平台账号部署说明:
```text
npm run bootstrap:admin -- --phone 13800138000 --password '请替换为强密码' --name '平台超级管理员'
```
生产部署前先手工执行版本化 SQL 完成建库建表与授权(见上文"手工建库建表"步骤),然后
通过环境变量初始化超级管理员:Go 后端首次启动时读取
`ZHINIAN_BOOTSTRAP_ADMIN_PHONE` / `ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD` /
`ZHINIAN_BOOTSTRAP_ADMIN_NAME`(密码至少 8 位,名称缺省为"平台超级管理员"),仅在
不存在任何超级管理员时创建一次,无需单独脚本。旧的 `npm run bootstrap:admin` 脚本
仅为本地/Next 开发保留。授权只向应用角色显式授予当前业务表 DML 和两个数据库函数
EXECUTE;不会授予未来对象的默认权限、`schema_migrations` 或 DDL 权限。新增表/函数时
必须随对应版本 SQL 显式更新授权。旧账号导入脚本 `npm run migrate:accounts` 仅为历史
遗留工具保留,新部署无需使用。
## 旧版组织账号接口(已停用)
如需启用 `/accounts` 后台账号管理,按组织能力接口文档配置:
```env
ZHINIAN_ORG_API_BASE_URL=https://<gateway-domain>/hotelStaff
ZHINIAN_ORG_API_TOKEN=
ZHINIAN_STAFF_API_BASE_URL=https://<gateway-domain>/hotelStaff
ZHINIAN_STAFF_API_TOKEN=
ZHINIAN_ORG_TENANT_ID=
ZHINIAN_ORG_ID=
ZHINIAN_ORG_MEMBER_LIST_PATH=
```
平台后端会代理调用组织、部门、角色、成员和企业端用户接口;成员分页列表默认通过 `hotelStaff` 的 `/adminOrganization/organizationMember/organizationMemberList` 对外代理,只有直连基础组织服务内部 `/organizationMember/organizationMemberList` 时才需要 `from: Y`。如统一服务路径不同,可配置 `ZHINIAN_ORG_MEMBER_LIST_PATH` 覆盖。创建账号调用 `/adminOrganization/organizationMember/addOrganizationMemberAndCreatePlatformUser`,重置密码调用 `/adminPcUser/resetPlatformUserPassword`。这些接口默认转发当前登录管理员账号的 `access_token`,企业端用户接口会校验该 token 是否具备管理员角色 `1`;`ZHINIAN_ORG_API_TOKEN` / `ZHINIAN_STAFF_API_TOKEN` 只作为无登录 token 时的服务端兜底。
## 常用运维命令
```bash
docker compose ps
docker compose logs -f zhinian-aigc
docker compose logs -f zhinian-worker
docker compose restart
docker compose down
```
Web 后台可在登录后访问:
```text
https://你的域名/logs
https://你的域名/settings
https://你的域名/accounts
https://你的域名/usage
https://你的域名/billing
```
日志默认写入 `.runtime/logs/server-events.jsonl`,用于查看 API 500 错误、Worker 任务异常、错误栈和请求路径。可通过环境变量 `ZHINIAN_LOG_DIR` 调整目录,通过 `ZHINIAN_LOG_MAX_BYTES` 调整单文件轮转大小。
更新部署:
```bash
git pull
bash scripts/deploy.sh
```
健康检查:
```bash
curl -f http://127.0.0.1:${APP_PORT:-3000}/api/health
```
OpenAPI:
```bash
curl http://127.0.0.1:${APP_PORT:-3000}/api/v1/openapi.json
```
## 数据持久化
Docker Compose 会挂载:
```text
./.runtime:/app/.runtime
```
本地 JSON 数据层、上传文件和生成结果都会放在 `.runtime/` 下。`local` 仅适合单实例开发;如临时使用,必须备份该目录。
服务端日志也会放在 `.runtime/logs/` 下,建议和运行时数据一起备份或接入服务器日志采集。
生产 PostgreSQL 发布前必须按顺序手工执行版本化 SQL 文件(0001 再 0002)与角色授权;不部署 `zhinian-db-migrate` Job。执行完成后再滚动工作负载。首次打开 `/billing` 或提交真实任务时,系统会自动导入内置标准成本目录;平台参数档案会同步,已有倍率会保留,超级管理员只维护上浮倍率。
建议备份:
```bash
tar -czf zhinian-runtime-$(date +%Y%m%d%H%M%S).tar.gz .runtime
```
## 服务组成
- `zhinian-aigc`:Next.js Web/API 服务,默认容器端口 `3000`
- `zhinian-worker`:后台任务 Worker,负责提交供应商任务、轮询结果、导入资产和触发 Webhook
注意:只启动 Web 服务时,任务会停留在 `queued` 或 `running`,必须同时运行 Worker。
## 反向代理建议
Nginx 示例:
```nginx
server {
listen 80;
server_name your-domain.com;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
配置反向代理后,将 `.env.local` 里的 `NEXT_PUBLIC_APP_URL` 设置成公网 HTTPS 地址。
## 验收清单
部署后执行:
```bash
curl -f https://你的域名/api/health
curl -H "Authorization: Bearer <API_KEY>" https://你的域名/api/v1/capabilities
curl https://你的域名/api/v1/openapi.json
```
确认:
- 未登录访问 Web 页面会跳转到 `/auth/login`
- 普通用户登录后看到创作、账号和计费;账号页可修改自己的密码,任务详情、输入要素和结果下载均在创作页右侧任务模块完成,点击页头账号 ID 可查看自己的快捷周期用量
- 从管理员入口登录且命中管理员账号或专用角色白名单后,可访问 `/logs`、`/settings`、`/usage`,并在 `/accounts` 额外维护组织和成员;超级管理员还可配置 `/billing` 计费规则和组织余额
- `/usage` 可按日期、组织、账号、功能和服务商筛选,Mock 与开放 API 任务不计入
- `/billing` 可查看组织余额、成员消耗和账务流水;管理员通过“余额与上账”直接记入组织额度
- `/api/health` 返回 `ok: true`
- `/logs` 可查看后台错误日志
- `/api/v1/capabilities` 使用 API Key 可访问
- `zhinian-worker` 日志持续输出 `claimed=...`
- OSS、EvoLink、火山、Seedance 密钥按业务需要配置完成