feat(acceptance): 建立同构验收与弹性容量体系
实现本地三节点 K3s 同构环境、脱敏生产快照、Gemini 图片和多参考图视频协议模拟、统一验收报告及故障注入。\n\n新增 Worker 容量控制器、资源与连接预算、任务恢复保护,并将生产验收拆分为 validation 执行和人工 CAS 放量。\n\n验证包括 Go 全量测试、PostgreSQL HTTP 集成测试、go vet、OpenAPI、ShellCheck、前端检查、迁移及发布脚本测试。
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# 本地三节点同构验收
|
||||
|
||||
## 链路边界
|
||||
|
||||
本地验收不使用旧 `simulation` 请求模式。压测端发送正式 Gemini `generateContent` 和正式
|
||||
多参考图视频请求,完整经过:
|
||||
|
||||
```text
|
||||
双 TLS 入口 → API 鉴权与 validation 门禁 → 生产候选路由
|
||||
→ Base64 解码及媒体物化 → PostgreSQL/River → Worker/租约
|
||||
→ 真实 Gemini/Volces 客户端 → 协议模拟上游
|
||||
→ 结果持久化 → 独立钱包结算 → 独立回调收集器 → 客户端响应
|
||||
```
|
||||
|
||||
只有候选已经确定后的 Base URL 和凭据被验收 Run 替换。模拟上游实现真实协议、延迟、
|
||||
异步轮询及示例媒体返回;API、Worker、候选选择、账务和恢复代码不走测试旁路。
|
||||
|
||||
## 资源和工具
|
||||
|
||||
本地集群名固定为 `easyai-acceptance-local`,包含三个 k3d/K3s server:
|
||||
|
||||
| 节点 | Docker 上限 | 调度职责 |
|
||||
|---|---:|---|
|
||||
| server-0 / ningbo | 4 CPU / 8 GiB | API、Worker、PostgreSQL |
|
||||
| server-1 / hongkong | 4 CPU / 8 GiB | API、Worker、PostgreSQL |
|
||||
| server-2 / los-angeles | 2 CPU / 4 GiB | 控制面和 etcd,`NoSchedule` |
|
||||
|
||||
Docker Desktop 必须配置至少 24 GiB 内存;脚本只检查,不修改 Docker 设置。k3d、K3s 和
|
||||
CNPG 版本及 SHA-256 位于
|
||||
`deploy/kubernetes/local-acceptance/dependencies.lock`。高并发阶段使用宿主原生架构镜像,
|
||||
最后才导入 release manifest 中精确的 `linux/amd64@sha256` 制品做启动、迁移和媒体冒烟。
|
||||
|
||||
```bash
|
||||
scripts/acceptance/local-cluster.sh install-tools
|
||||
scripts/acceptance/local-cluster.sh preflight
|
||||
```
|
||||
|
||||
失败的 `up` 默认保留集群。只有下面的显式命令会销毁:
|
||||
|
||||
```bash
|
||||
scripts/acceptance/local-cluster.sh down --confirm
|
||||
```
|
||||
|
||||
## 脱敏生产快照
|
||||
|
||||
先使用只有 `SELECT` 权限的数据库角色导出 `acceptance-snapshot/v1`:
|
||||
|
||||
```bash
|
||||
AI_GATEWAY_ACCEPTANCE_SNAPSHOT_DATABASE_URL='<只读连接>' \
|
||||
scripts/acceptance/export-production-snapshot.sh \
|
||||
--release-sha <当前线上完整SHA> \
|
||||
--output .local-secrets/acceptance/production-snapshot.json
|
||||
```
|
||||
|
||||
快照只包含一个 Gemini 图片候选和一个 `omni_video.max_images >= 9` 视频候选所需的能力、
|
||||
协议、路由、限流、价格、重试和运行策略。导出器拒绝密码、Secret、Token、认证、代理和
|
||||
连接串字段,文件权限固定为 `0600`,并记录配置哈希和快照 SHA-256。
|
||||
|
||||
本地导入必须先匹配 `acceptance_local_cluster_id` 数据库标记,否则拒绝写入。集群内创建
|
||||
32 个隔离身份/API Key、独立钱包和专属 Run;Key 与 Run Token 只保存在 `0600` 临时文件,
|
||||
不进入 Pod Spec、报告或日志。
|
||||
|
||||
## 创建与验收
|
||||
|
||||
源码必须已提交且工作区干净,确保本地镜像和报告中的完整 Git SHA 可复现:
|
||||
|
||||
```bash
|
||||
scripts/acceptance/local-cluster.sh up \
|
||||
--snapshot .local-secrets/acceptance/production-snapshot.json
|
||||
|
||||
scripts/acceptance/run-local-acceptance.sh quick
|
||||
|
||||
scripts/acceptance/run-local-acceptance.sh full \
|
||||
--release-manifest dist/releases/<完整SHA>.json
|
||||
```
|
||||
|
||||
`full` 依次执行:
|
||||
|
||||
1. Gemini、3/6/9 图视频、账务和回调快速验收。
|
||||
2. 固定 `1+1` Worker 的 P24、P28、P32,每档完整运行三次。
|
||||
3. 1000×256 KiB、128×2 MiB、32×8 MiB Gemini 与 1200/96 视频负载。
|
||||
4. 40±20 ms/0.5% 丢包、上游断链 10 秒、Worker—数据库断链 30 秒。
|
||||
5. 确实持有远程任务的 Worker 强杀,以及容量控制器 Leader 切换。
|
||||
6. `1+1 → 1+2/2+2 → drain → 1+1` 弹性验证。
|
||||
7. 80% 两小时混合流量和 120% 十分钟主动限流。
|
||||
8. 精确 amd64 release 镜像的迁移、启动与媒体冒烟。
|
||||
|
||||
网络故障只能作用于带 `easyai.io/environment=local-acceptance` 标签的代理 Pod:
|
||||
|
||||
```bash
|
||||
scripts/acceptance/network-fault.sh baseline
|
||||
scripts/acceptance/network-fault.sh weak-link
|
||||
scripts/acceptance/network-fault.sh upstream-outage
|
||||
scripts/acceptance/network-fault.sh database-outage hongkong
|
||||
scripts/acceptance/network-fault.sh reset
|
||||
```
|
||||
|
||||
统一报告为 `acceptance-report/v1`,位于
|
||||
`dist/acceptance/local/<run-id>/acceptance-report.json`。本地吞吐只用于筛选候选档位;
|
||||
生产 certified profile 必须在线上模拟验收中重新确定。
|
||||
|
||||
## 不能被本地替代的门禁
|
||||
|
||||
本地环境不能认证真实 WireGuard 六向链路、生产磁盘、跨地域 CNPG 同步、供应商配额或
|
||||
生产节点混部资源。线上 `--execute` 会重新导出生产候选配置;配置哈希与本地快照不一致时
|
||||
立即停止,必须基于新快照重跑本地阻断验收。
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
验收脚本是显式生产写操作,会暂停新正式任务、调整 Worker 容量并在恢复场景删除一个
|
||||
Worker Pod。它不会自动发布新版本;只能针对已经发布、完整 SHA 和 digest 固定的 manifest
|
||||
执行。
|
||||
执行。进入线上前必须先通过[本地三节点同构验收](local-isomorphic-acceptance.md)。
|
||||
|
||||
## 隔离与流量门禁
|
||||
|
||||
@@ -79,8 +79,9 @@ AI_GATEWAY_WORKER_REPLICAS_NINGBO
|
||||
AI_GATEWAY_WORKER_REPLICAS_HONGKONG
|
||||
```
|
||||
|
||||
`AI_GATEWAY_WORKER_REPLICAS_*` 是发布工具配置,不会传入容器。当前验收固定宁波、香港各
|
||||
一个 2 GiB Worker,不创建额外 Pod:
|
||||
`AI_GATEWAY_WORKER_REPLICAS_*` 是发布工具配置,不会传入容器。容量档位先固定宁波、香港
|
||||
各一个 2 GiB Worker,再按实时内存、CPU 和 PostgreSQL 预算验证 `1+2`、条件允许时的
|
||||
`2+2`,以及安全 drain 回到 `1+1`:
|
||||
|
||||
| 档位 | 单实例执行槽 | 数据库池 | 媒体并发 | 全局执行槽 |
|
||||
|---|---:|---:|---:|---:|
|
||||
@@ -120,7 +121,8 @@ easyai-ai-gateway-cluster-release capacity
|
||||
|
||||
```bash
|
||||
scripts/cluster/run-production-acceptance.sh \
|
||||
--execute dist/releases/<完整SHA>.json
|
||||
--execute dist/releases/<完整SHA>.json \
|
||||
--local-report dist/acceptance/local/<本地run-id>/acceptance-report.json
|
||||
```
|
||||
|
||||
脚本依次执行:
|
||||
@@ -133,11 +135,25 @@ scripts/cluster/run-production-acceptance.sh \
|
||||
6. 执行两条真实小流量请求。
|
||||
7. 校验任务、账务、回调、队列、连接池、RSS、节点内存、数据库同步、六向 WireGuard 和
|
||||
公网健康。
|
||||
8. 重新校验 SHA/digest/revision 后切回 `live`。
|
||||
8. 完成 Run 并输出 `acceptance-report/v1`,保持 `validation`,等待人工确认。
|
||||
|
||||
报告保存在 `dist/acceptance/<run-id>/`,包含每阶段吞吐、p50/p95/p99、队列和资源 CSV、
|
||||
协议模拟器统计及最终摘要。报告不包含图片原文、Base64、Token、密码或连接串。
|
||||
|
||||
确认整体报告后单独执行人工开闸:
|
||||
|
||||
```bash
|
||||
scripts/cluster/run-production-acceptance.sh \
|
||||
--promote dist/releases/<完整SHA>.json \
|
||||
--run-id <线上run-id>
|
||||
```
|
||||
|
||||
`--promote` 会重新读取 Run、流量 revision、release SHA、镜像 digest、配置哈希及暂存容量
|
||||
配置,任一 CAS 不一致即拒绝。成功切到 `live` 后同一命令继续运行有限期监控:前 2 小时
|
||||
每 10 秒采样,之后每 60 秒采样至 24 小时,不恢复已经停用的周期性巡检。队列、RSS、
|
||||
连接池、同步复制、租约、重复结算、WireGuard 或公网健康触发硬门禁时,监控通过 CAS
|
||||
自动切回 `validation`,并恢复 Run 开始前保存的精确容量快照;不会自动回滚数据库。
|
||||
|
||||
## 通过和失败处理
|
||||
|
||||
通过要求包括:任务全部成功、Gemini 输出可解码且哈希/字节数正确、视频参考图校验全部
|
||||
@@ -145,6 +161,7 @@ scripts/cluster/run-production-acceptance.sh \
|
||||
超过当前档位、无 OOM/重启、Pod RSS 小于 1.5 GiB、节点内存低于 80%、PostgreSQL 连接
|
||||
低于 75%、连接池不连续满载、双副本同步、六向链路和公网健康通过。
|
||||
|
||||
失败 Run 会记录原因、恢复上一稳定容量并保持 `validation`。排除问题后调用 Run 的
|
||||
失败 Run 会记录原因、恢复上一稳定容量并保持 `validation`。成功 Run 也保持
|
||||
`validation`,只有上述人工 `--promote` 才能开闸。排除失败问题后调用 Run 的
|
||||
`retry` 接口重新执行。只有人工决定放弃验收时,才使用带相同 CAS 字段的 `abort` 接口
|
||||
恢复 `live`;该操作不会把失败验收标记为通过。
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://easyai.local/schemas/acceptance-report-v1.schema.json",
|
||||
"title": "EasyAI acceptance-report/v1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schemaVersion", "runId", "release", "snapshot", "stages", "gates", "promotion", "createdAt", "secretSafe"],
|
||||
"properties": {
|
||||
"schemaVersion": {"const": "acceptance-report/v1"},
|
||||
"runId": {"type": "string", "minLength": 1},
|
||||
"release": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["sourceSha", "apiImageDigest", "workerImageDigest"],
|
||||
"properties": {
|
||||
"sourceSha": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"apiImageDigest": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"},
|
||||
"workerImageDigest": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["sourceReleaseSha", "configHash", "sha256"],
|
||||
"properties": {
|
||||
"sourceReleaseSha": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"configHash": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
|
||||
}
|
||||
},
|
||||
"stages": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"$ref": "#/$defs/stage"}
|
||||
},
|
||||
"certifiedProfile": {"type": ["object", "null"]},
|
||||
"gates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "passed"],
|
||||
"properties": {
|
||||
"id": {"type": "string", "minLength": 1},
|
||||
"passed": {"type": "boolean"},
|
||||
"detail": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"promotion": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["ready", "requiresManualConfirmation", "trafficMode"],
|
||||
"properties": {
|
||||
"ready": {"type": "boolean"},
|
||||
"requiresManualConfirmation": {"const": true},
|
||||
"trafficMode": {"enum": ["validation", "live", "not-applicable"]},
|
||||
"promotedAt": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
},
|
||||
"createdAt": {"type": "string", "format": "date-time"},
|
||||
"secretSafe": {"const": true}
|
||||
},
|
||||
"$defs": {
|
||||
"stage": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": ["status"],
|
||||
"properties": {
|
||||
"status": {"enum": ["passed", "failed", "not-run"]}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://easyai.local/schemas/acceptance-snapshot-v1.schema.json",
|
||||
"title": "EasyAI acceptance-snapshot/v1",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schemaVersion", "source", "candidates", "snapshotSha256", "secretSafe"],
|
||||
"properties": {
|
||||
"schemaVersion": {"const": "acceptance-snapshot/v1"},
|
||||
"source": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["releaseSha", "configHash", "createdAt"],
|
||||
"properties": {
|
||||
"releaseSha": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
|
||||
"configHash": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"createdAt": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
},
|
||||
"candidates": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["workload", "provider", "baseModel", "platform", "platformModel", "runtimePolicy", "pricingRules", "metadata"],
|
||||
"properties": {
|
||||
"workload": {"enum": ["gemini_image_edit", "multi_reference_video"]}
|
||||
}
|
||||
}
|
||||
},
|
||||
"snapshotSha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
|
||||
"secretSafe": {"const": true}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user