Make deployment truth and remaining graduation gates explicit
Some checks failed
verify / verify (push) Has been cancelled

Document the runnable Docker workflow, expected one-shot container exits, server-controlled Bailian namespace switch, current earned progress, and the security and evaluation work that still blocks private-data or graduation release claims.

Constraint: Current local Bailian credentials are rejected by the configured workspace

Rejected: Mark the project complete from synthetic metrics | would overstate provider, OCR, authorization, and blind-evaluation readiness

Confidence: high

Scope-risk: narrow

Directive: Keep progress evidence task-based and distinguish synthetic engineering validation from real geological efficacy

Tested: make verify-design; Markdown links; secret scan; diff checks

Not-tested: Final thesis review and live provider authorization
This commit is contained in:
2026-07-13 05:58:39 +08:00
parent ecdb10c37a
commit 63279cf213
7 changed files with 214 additions and 109 deletions

View File

@@ -3,8 +3,8 @@
| 项目 | 设计值 |
|---|---|
| 部署方式 | Docker Compose 单机 |
| 长期服务 | 当前 `web``gateway``api``model-gateway``db`;后续加入 `worker` |
| 一次性服务 | `migrate` |
| 长期服务 | `web``gateway``api``model-gateway``worker-local``worker-model``db` |
| 一次性服务 | `migrate``upload-init`;成功后正常 `Exited (0)` |
| 对外端口 | 仅 Nginx/Web |
| 密钥 | Docker Secret / Secret Manager |
| 数据库 | PostgreSQL 17 + pgvector 0.8.x实现时固定 digest |
@@ -17,7 +17,10 @@
docker compose up -d --build
```
这条命令当前完成数据库健康等待、一次性迁移、`model-gateway`、API、入口 Gateway 和 Web 启动Worker 落地后也遵循同一依赖链。它不等于“所有数据和模型凭证自动生成”首次部署仍必须完成密钥轮换、Secret 创建和三模型能力探测。
这条命令当前完成数据库健康等待、一次性迁移、上传卷初始化、两个最小权限 Worker、
`model-gateway`、API、入口 Gateway 和 Web 启动。`migrate``upload-init` 成功后显示
`Exited (0)` 是预期终态,不是服务暂停。它不等于“所有数据和模型凭证自动生成”,首次部署
仍必须创建本地 Secret真实模式还必须完成密钥轮换和三模型能力探测。
架构定位是适合毕设、演示和单机内部使用的生产式单节点,不宣称跨主机高可用。互联网生产环境应替换托管数据库、对象存储和集中密钥管理。
@@ -29,10 +32,12 @@ docker compose up -d --build
|---|---|---|---|
| `db` | 固定版本 pgvector 镜像 | 元数据、向量、任务、会话、评测 | 仅内部网络 |
| `migrate` | 与后端同镜像 | 运行 Alembic一次成功退出 | 无 |
| `upload-init` | 与后端同镜像 | 无网络初始化上传卷属主,一次成功退出 | 无网络 |
| `api` | `backend/Dockerfile` | FastAPI、检索、问答、管理 API | 仅 internal data/model 网络 |
| `gateway` | 与后端同镜像 | 固定上游、请求边界、脱敏错误和流式转发 | 仅 internal ingress/data 网络 |
| `model-gateway` | 与后端同镜像 | 唯一读取百炼 Key代理 Embedding/Rerank/Chat | 仅 internal model + egress 网络,无宿主机端口 |
| `worker` | 同一后端镜像 | 解析、向量化、评测后台任务 | 无 |
| `worker-local` | 同一后端镜像 | 本地文件验证、解析、切分;有上传卷,无模型网络/Token | 仅 internal data 网络 |
| `worker-model` | 同一后端镜像 | 只处理已审批文本的向量化;无上传卷 | 仅 internal data/model 网络 |
| `web` | `frontend/Dockerfile` | React 静态资源、Nginx 同源入口和 SSE 代理 | `127.0.0.1:8000`;生产部署 HTTPS |
| `ocr-worker` | 可选 profile | PaddleOCR 重型任务 | 无 |
| `prometheus/grafana` | 可选 profile | 本地观测 | 默认不对公网 |
@@ -121,6 +126,20 @@ services:
networks: [data]
restart: "no"
upload-init:
build: ./backend
command: ["python", "-m", "app.tools.init_upload_storage"]
environment:
UPLOAD_ROOT: /data/uploads
user: "0:0"
network_mode: none
volumes:
- uploads_data:/data/uploads
read_only: true
cap_drop: [ALL]
cap_add: [CHOWN, FOWNER, DAC_OVERRIDE]
restart: "no"
model-gateway:
build: ./backend
command: ["uvicorn", "app.model_gateway:app", "--host", "0.0.0.0", "--port", "8000"]
@@ -143,6 +162,8 @@ services:
depends_on:
migrate:
condition: service_completed_successfully
upload-init:
condition: service_completed_successfully
model-gateway:
condition: service_healthy
environment:
@@ -151,7 +172,7 @@ services:
- postgres_app_password
- model_gateway_api_token
volumes:
- uploads:/data/uploads
- uploads_data:/data/uploads
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=2)"]
interval: 10s
@@ -170,22 +191,40 @@ services:
networks: [ingress, data]
restart: unless-stopped
worker:
worker-local:
build: ./backend
command: ["python", "-m", "app.workers.main"]
command: ["python", "-m", "app.worker"]
depends_on:
migrate:
condition: service_completed_successfully
upload-init:
condition: service_completed_successfully
environment:
<<: *runtime-config
WORKER_CAPABILITIES: document_parse
secrets:
- postgres_app_password
volumes:
- uploads_data:/data/uploads
networks: [data]
restart: unless-stopped
worker-model:
build: ./backend
command: ["python", "-m", "app.worker"]
depends_on:
migrate:
condition: service_completed_successfully
model-gateway:
condition: service_healthy
environment:
<<: [*runtime-config, *model-client-config]
MODEL_GATEWAY_TOKEN_FILE: /run/secrets/model_gateway_worker_token
MODEL_GATEWAY_CALLER: worker
WORKER_CAPABILITIES: ${WORKER_CAPABILITIES:-document_parse,embedding,rerank,evaluation}
WORKER_CAPABILITIES: embedding
secrets:
- postgres_app_password
- model_gateway_worker_token
volumes:
- uploads:/data/uploads
networks: [data, model]
restart: unless-stopped
@@ -231,7 +270,7 @@ services:
volumes:
postgres_data:
uploads:
uploads_data:
networks:
edge:
@@ -265,7 +304,17 @@ secrets:
实际实现固定数据库镜像 digest。`migrate` 只有一个实例执行;成功应用到 head 后退出码为 0随后在 `docker compose ps -a` 显示 `Exited (0)` 是一次性任务的正常终态不是服务暂停。API 和 Worker 不在启动时并发自动迁移。首次初始化脚本只在 `db` 容器内使用 bootstrap 凭据创建 `vector` 扩展、无超级用户权限的 migrator/app 登录角色和专属 schemamigrator 拥有 schema/DDLapp 仅获运行期所需的 DML/sequence 权限及默认权限API/Worker 永远不挂载 bootstrap 或 migrator Secret。最后一个初始化脚本只有在全部 SQL 事务和授权成功后,才在 `PGDATA` 内用同文件系统 rename 原子写入 `.rag-bootstrap-complete`healthcheck 同时检查 `pg_isready` 与该哨兵,防止临时初始化 server 提前放行迁移。Compose 的 `.env` 仅做变量插值,不会自动注入容器;后端从 `*_FILE` 读取 Secret不把明文密码放进 `.env``DATABASE_URL`
网络分为五`web` 位于普通 `edge` 和 internal `ingress`;入口 `gateway` 位于 `ingress + data`API 位于 internal `data + model`;数据库/迁移位于 internal `data`;只有 `model-gateway` 位于 internal `model + egress``provider-smoke` 只持 API 内部 token真实 `seed-demo` 只持数据库 Secret 和 Worker 内部 token二者都不挂载百炼 Key、不加入 `egress`。未来 Worker 沿用相同边界。`model-gateway` 不连接数据库、不挂载上传卷、不发布端口。入口隔离见 [ADR-0004](adr/0004-secretless-web-ingress.md),模型出口隔离见 [ADR-0005](adr/0005-isolate-model-egress.md)。必须注意:普通 `edge` bridge 为 Web 提供宿主机端口发布时,也给 Web 留有技术上的默认公网出口;生产仍需主机防火墙或出口代理只允许 `model-gateway` 到百炼域名。
网络分为五`web` 位于普通 `edge` 和 internal `ingress`;入口 `gateway` 位于
`ingress + data`API 位于 internal `data + model`;数据库、迁移和 `worker-local` 位于
internal `data``worker-model` 位于 internal `data + model`;只有 `model-gateway` 位于
internal `model + egress``worker-local` 挂载上传卷但没有模型 Token/网络,`worker-model`
内部 Worker Token 但不挂载上传卷;两者的信任边界见
[ADR-0007](adr/0007-split-local-and-model-workers.md)。`provider-smoke` 和真实 `seed-demo` 都不
挂载百炼 Key、不加入 `egress``model-gateway` 不连接数据库、不挂载上传卷、不发布端口。
入口隔离见 [ADR-0004](adr/0004-secretless-web-ingress.md),模型出口隔离见
[ADR-0005](adr/0005-isolate-model-egress.md)。必须注意:普通 `edge` bridge 为 Web 提供宿主机
端口发布时,也给 Web 留有技术上的默认公网出口;生产仍需主机防火墙或出口代理只允许
`model-gateway` 到百炼域名。
### 2.3 后台任务为何不用 Redis
@@ -331,7 +380,11 @@ def read_secret(file_env: str, value_env: str) -> str:
生产只允许文件/Secret Manager本地值环境变量仅作为临时兼容。`bailian_api_key` 只挂载到 `model-gateway`。API 与 Worker 分别挂载 `model_gateway_api_token``model_gateway_worker_token`;调用同时使用 Bearer token 和 `X-RAG-Caller`,服务端以常量时间比较并拒绝 token/身份不匹配。应用启动后不打印 Settings 的 Secret 字段,异常对象不得包含请求头。
Secret 或模型端点配置在运行中发生变化时,`model-gateway` 会原子清空旧 provider 与内部 token并进入 `restart_required`;旧、新 token 都不能在旧进程继续调用。Docker 的 `restart: unless-stopped` 不会仅因 unhealthy 自动重启,因此轮换操作必须协调执行 `docker compose restart model-gateway api`Worker 落地后同时重启 Worker待 readiness 恢复后再运行 provider smoke。禁止只替换文件而让旧进程长期携带旧凭据。
Secret 或模型端点配置在运行中发生变化时,`model-gateway` 会原子清空旧 provider 与内部
token并进入 `restart_required`;旧、新 token 都不能在旧进程继续调用。Docker 的
`restart: unless-stopped` 不会仅因 unhealthy 自动重启,因此轮换操作必须协调执行
`docker compose restart model-gateway api worker-model`,待 readiness 恢复后再运行 provider
smoke。禁止只替换文件而让旧进程长期携带旧凭据。
### 3.3 Git 防泄漏