diff --git a/docs/architecture/运维/assets/02-部署链时序.svg b/docs/architecture/运维/assets/02-部署链时序.svg new file mode 100644 index 00000000..3cbe8899 --- /dev/null +++ b/docs/architecture/运维/assets/02-部署链时序.svg @@ -0,0 +1,100 @@ + + + + + + + + 图 2 · 部署链:push → pull → 重建 → 验门 + 内网无自建 CI,人工经标准序在 mini-desktop 完成。实线=同步动作;灰虚线=回执/拉取。橙框=隔离验证安全变体(新 JAR 先在 :48090 验全,live :48080 全程不动)。 + + + + lili-mac(开发机·ARM) + 出 JAR 架构中立 · 不进调度 + + Aliyun Gitea(中转) + :2222 push · :3000 匿名读 + + mini-desktop(权威验收·x86) + 构建 + 隔离验证实例 :48090 + + live 后端 :48080 + 在跑的服务 · 验全才切 + + + + + + + + + + ① git push(默认远程 :2222) + 坑:scp/rsync 源码树被分类器拦 → 必须走 Gitea 中转;同仓 push 串行(撞 ref 锁) + + + + ② HTTP :3000 匿名 clone / pull(只读端口,非 SSH) + + + + ③ git reset --hard origin/dev/2.0.0 +   mvn clean install → 出 fat JAR + + + + ④ 字节码实证:unzip -p 核 .class 含本次改动 + 防「构建源 ≠ 运行的 JAR」翻车 + + + + 隔离验证安全变体(高风险 / 整机变更走这条) + + + + ⑤ 新 JAR 起隔离实例 :48090 + live :48080 全程不动 + + + + ⑥ 在 :48090 验全:health 200 · 四模板就绪 · Flyway up-to-date · 冒烟门 12 项 + + + + ⑦ 验全过 → 停旧实例释放端口 → 切 :48080 + + + + ⑧ 任一步失败 → /tmp 备份 JAR 把 :48080 恢复回去(5 分钟内) + + + + ⑨ 切后复验:冒烟门 deploy/smoke-test.sh(详见图 4) + + + + 红线:API 全绿 ≠ UI 通 —— + 编排器旁路会掩盖 UI 缺陷;用户可见波次收口前,必须另在 mini-desktop 经 CDP 做一次真浏览器 UI 走查 + + + + 目标态蓝图(现实未建·勿当现状): + 开发团队版文档那套「push 触发 CI → 自动构建镜像 → 滚动更新」与现实系统性脱节(文档自带审计横幅);现行是人工标准序,无 docker-compose、无自建 CI 流水线。 + + + 同步动作 / 命令 + 回执 / 拉取(灰虚线) + 橙框=隔离验证安全变体 + 可靠 + 安全 + 成本 + 隔离切换/备份回滚=可靠 · 分类器/串行=安全 · 内网物理机人工部署=成本 + + 映射:运维/README.md §1.2 | 状态:现 | 设计变动须同步本图 + diff --git a/docs/architecture/运维/assets/04-冒烟门12项.svg b/docs/architecture/运维/assets/04-冒烟门12项.svg new file mode 100644 index 00000000..4b3371ad --- /dev/null +++ b/docs/architecture/运维/assets/04-冒烟门12项.svg @@ -0,0 +1,114 @@ + + + + + + + 图 4 · 冒烟门 12(+1)项 + deploy/smoke-test.sh:部署后秒级就绪检查(以读为主、可反复跑),逐项 PASS/FAIL,末尾给退出码(0=全过 / 1=有 FAIL),可直接挂部署脚本当卡口。不是深度 e2e。 + + + + 基础设施 / 鉴权(2 项) + + 1 · GET /actuator/health(200) + + 2 · GET /app-api/passport/me(token 有效) + + + + 链路① 创作→生成→预览(2 项) + + 3 · GET aigc/template/list(四模板≥4) + + 4 · POST studio/draft(建草稿拿 gameId) + 唯一写:status 0 草稿,不发布、无下游副作用 + + + + 链路② 发布→审核→游戏流(3 项) + + 5 · GET feed/stream(内容池非空) + + 6 · GET feed/zones(分区结构) + + 7 · GET project/my(创作者项目区) + + + + 链路③ 试玩→互动→分享(1 项) + + 8 · GET runtime/package/{versionId}(试玩取包) + + 坑:feed item 的 packageUrl 恒为 null —— 取包走 runtime/package,别拿它当入口 + + + + 链路④ 广告→收益→钱包(3 项) + + 9 · GET ad/slot/list-enabled(广告位) + + 10 · GET trade/account/mine(钱包) + + 11 · GET trade/income/page(收益流水) + + + + 链路⑤ 数据回路(1 项) + + 12 · feed 首条携带 qualityScore + 结构断言:feed 排序由质量分驱动 = 回路已回灌 + 遥测 → 质量分 → feed 重排,闭环结构性可见 + + + + --deep(+1 项 · 按需) + + 13 · POST studio/generate(生成入队) + 走真实写路径:触发生成入队 + 遥测落库 + 默认不跑;只验入队 200/code=0,不等生成完成 + + + + 总判 + 退出码(可挂 CI) + + 全过 → SMOKE_PASS · exit 0 + + 有 FAIL → SMOKE_FAIL · exit 1 + 职责边界:就绪检查 ≠ 深度 e2e(深度 e2e 由 agent-loop 编排器批跑,不重叠) + + + + + + + 链路③汇入判定 + + + + 调用约定 + BASE=http://100.64.0.7:48080(默认 staging 后端) · TOKEN=test1(种子创作者测试态 token) · ORIGIN=http://100.64.0.7:4173(验 CORS 同源放行) + 内网地址 / 测试 token 可入仓(内网非密);真实密钥严禁写入脚本。鉴权头:Authorization: Bearer $TOKEN · tenant-id: $TENANT。 + + + + 与观测体系衔接(设计已出·待接线): + 冒烟门是「部署那一刻的就绪点检」,观测体系是「部署之后的常态监控」——两者互补不重叠。 + 衔接点:冒烟门 12 项结果可打成指标推 Prometheus,让「每次部署的健康度」也进可观测历史,而非只在终端闪一下。详见图 6 / 图 7。 + + + 图例: + 五链路就绪项 + --deep 写路径(按需) + 已踩坑 / 红线 + 可靠 + 安全 + 逐项就绪 + 退出码可卡口=可靠 · 鉴权/CORS 结构验证=安全 + + 映射:运维/README.md §1.3 · deploy/smoke-test.sh(逐项以脚本为准·分组为讲解性归组) | 状态:现 | 设计变动须同步本图 + diff --git a/docs/architecture/运维/assets/06-观测体系.svg b/docs/architecture/运维/assets/06-观测体系.svg new file mode 100644 index 00000000..6def427f --- /dev/null +++ b/docs/architecture/运维/assets/06-观测体系.svg @@ -0,0 +1,153 @@ + + + + + + + + 图 6 · 观测体系:OTel → Grafana / 夜莺 + 采集 → 管道 → 存储 → 看板/告警 一条链。创始人 2026-06-21 定栈:OTel 统一采集 + Prometheus + Grafana(看)+ 夜莺(告警)。除标「已在」的件外,整链均为待接线。 + + + + 状态约定: + 实心块 = 已在(地基已就位);虚线框 + 「待接线」= 设计已出、代码未接,绝非现行已建。本图整体状态 = 接。 + + + + 采集侧(被观测的进程) + + + + huijing 后端单体 + + 已在:SAA 节点 observation(每节点一条 Micrometer span) + + 待接:OTel Java Agent(自动 span:HTTP/JDBC/Redis/出网) + monitor starter 未进 aigc 部署单元 → 要先引 actuator/micrometer + + 待埋:生成业务指标 gen_task/duration/queue/gate/cost + 5 指标当前代码零命中,需补 MeterRegistry 注册 + + + + game-studio / game-admin 前端 + 待接:OTel Web SDK(首屏 / JS 错误 / 前端 trace)· 第三期 + 待接线 + + + + 主机 / 中间件 + 待接:node-exporter · MySQL/Redis exporter + CPU / 内存 / 磁盘 / GC,对应 OOM、磁盘涨满 + 待接线 + 区分:业务遥测(谁玩了哪款)走现有 /app-api,不动、不重复造 + + + + 管道:OTel Collector + OTLP 统一入口 + 收 trace / metrics / log + 批处理 / 脱敏 / 采样 / 路由 + 脱敏在此集中做(手机号/token) + 待接线 + + + + 存储后端(各管一类) + + Prometheus(指标时序) + + trace 后端 Tempo/Jaeger + 倾向 Tempo · 选型待定 + + 日志后端 Loki 等 + 倾向 Loki · 选型待定 + + + + 看板与告警 + + + Grafana —— 看(read-only 的眼睛) + 统一看板:指标 / trace / 日志三联下钻 + admin 一键进的「监控大盘」即 Grafana + SLO 可用性 + Error Budget 燃尽图(详见图 5) + 从一条慢请求点进它的 trace、再点进日志 + 待接线 + + + 夜莺 Nightingale —— 叫(会响的告警) + 告警规则引擎 + 值班通知分发 + 把 P0–P3 升级链落成真规则 + 真通道 + 从 Prometheus 拉指标算规则,口径与 Grafana 一致 + 补「告警通道一根没接」这个最大空 + 待接线 + + + 关键告警三类(创始人点名) + 可用性 P0 · 错误率(5xx>2%) P1 · 生成失败率(<70%) P1 + + + + 通知通道 + MVP 先接:飞书 / 钉钉群机器人 webhook + (零成本、当天可接,先全打通) + P0 电话 / 短信:随支付/短信真实化补 + webhook 密钥进《内网凭据与端点.md》 + + + + game-admin 观测入口 + 控制台「观测」菜单:观测大图 + 跳转 + SSO 免登进盘(详见图 7) + 现状 = 零,整套新建 + 权限守门,不裸暴露 Grafana/Prometheus + + + + + OTLP + + + metrics/trace/log 分路 + + + Prometheus 取数 + + + webhook + + + 嵌入 + SSO 免登 + + + + 唯一已就位的观测信号(不要当成「全都接好了」): + aigc-server 已引 spring-ai-alibaba-starter-graph-observation;SAA 裸图每个节点发 spring.ai.alibaba.graph.node.<id> 的 Micrometer observation(带 trace + 耗时,失败反映在指标里)。 + 缺的只是:① 一个把它收走的后端;② 一个非 NOOP 的 ObservationRegistry(要 actuator/micrometer 装配在席)。其余采集件、存储、看板、告警、admin 入口全部待接线。 + + + + 旁路铁律: + 埋点开关默认关、经 profile 显式开;agent 异步批量上报、Collector 不可达时本地丢弃。 + 硬验收:停掉 Collector,生成主链路与各 API 行为不变、冒烟门仍全绿——观测栈本身挂掉,绝不影响被观测的服务。 + + + 图例: + 已在(地基就位) + 待接线(设计已出·未接码) + 数据流(OTLP/scrape) + 可观测 + 安全 + 可靠 + 成本 + 三件齐全=可观测 · Collector 脱敏=安全 · 旁路降级=可靠 · 缓存命中哨兵=成本 + + 映射:运维/观测体系.md(+ README.md §4) | 状态:接(设计已出·待接线) | 设计变动须同步本图 + diff --git a/docs/architecture/运维/assets/08-k3s迁移.svg b/docs/architecture/运维/assets/08-k3s迁移.svg new file mode 100644 index 00000000..8a6b237b --- /dev/null +++ b/docs/architecture/运维/assets/08-k3s迁移.svg @@ -0,0 +1,161 @@ + + + + + + + + 图 8 · k3s 单节点迁移(收窄 + 缓做) + 创始人 2026-06-22 定:范围收窄(单节点 k3s + staging 应用 + 观测栈,库不进集群,多节点/高可用/prod 推迟)、时机缓做(排在 W-G1 之后)。整张图是目标态,不是现行已建。 + + + + 状态约定: + 紫色虚线框 + 「缓做」= 目标态、本阶段不做(排 W-G1 之后);实心块 = 今天就在跑、迁移期全程保留的兜底路。本图整体状态 = 缓。 + + + + mini-desktop · 100.64.0.7 · x86 —— k3s 单节点(control-plane + worker 同机) + 缓做 + 选 k3s(单二进制 / 自带 containerd / 内置 local-path);装时 --disable traefik --disable servicelb;service-node-port-range=4000-50000 + + + + k3s 集群(ns: huijing-staging / observability) + + + 后端单体 Deployment(取代裸 java -jar) + image · -Xmx2g · requests 2Gi/limits 2.5Gi(Burstable) · 探针 + hostNetwork:true → 直占宿主 48080 + 连 127.0.0.1 库 + + + game-studio Deployment + 静态产物 nginx 镜像 · NodePort 4173 + + game-admin Deployment + NodePort 4174 + + observability namespace(观测栈 workload): + + OTel Collector + + Grafana + + 夜莺 Nightingale + 观测埋点/看板设计见图 6;k3s 就绪前可先裸 docker-compose 过渡,对应用侧埋点零差异(都是 OTLP) + requests/limits + QoS 给观测栈设内存上限,别让 Prometheus 时序库吃垮同机 live 后端 + + + + 隔离 MySQL / Redis + :13306 / :16379 + 裸容器 · 只 bind 127.0.0.1 + 不进集群(第一阶段保留) + 有状态迁移是另一量级风险 + + + + 镜像进集群 + ctr import 导本地 tar + (第一阶段不引 registry) + + + + 逃生口(实心·迁移全程保留,不退役):裸 JAR + /tmp 备份 + 任一阶段、任一时刻 kubectl 出问题 → kubectl delete 集群 workload → start-app.sh + /tmp 备份 JAR 把 :48080 拉回 → 回到今天形态,5 分钟内。 + 单节点 k3s 节点挂了集群全没(不买高可用);新路没稳定前,旁边始终留一条已验证的老路。集群版稳跑一个完整迭代 + 一次无事故发版,才考虑退役裸 JAR。 + + + + mini-infra · 100.64.0.8 + 共享基建 · 不进集群 · 不装 kubelet + free 仅约 569Mi · 8 个常驻容器 + + new-api · PostgreSQL · MinIO · Gitea · Redis + 集群经 ExternalName Service 走 Tailscale IP 访问 + 与裸 JAR 用 100.64.0.8:3000 一致 · baseUrl 不带 /v1 + 凭据仍读《内网凭据与端点.md》 + + + + 6c6g · 100.64.0.9 + 无人值守 · 不当节点、不跑 workload + 当 kubectl 控制端(kubeconfig 指 mini-desktop) + 声明式部署 / rollout / 状态巡检 cron 落这 + 符合「禁跑重活」铁律:只发 kubectl 命令 + + + + lili-mac · ARM + 开发 / 本地 dev / 快构建 · 会休眠不进调度 + 镜像必须 mini-desktop x86 出 + Mac 的 ARM 镜像不同构 = 半假门(staging-ops 铁律延伸) + + + + ExternalName → Tailscale IP + + kubectl 远程驱动 + + + + 迁移路:四阶段,每阶段可独立验收、可回滚(live :48080 全程或可瞬时恢复) + + + 阶段 0 · 容器化(零 k8s 风险·可先行) + 后端单体出镜像(Dockerfile 已存在) + JDK21 运行 / Java 17 编译,保留不降 + 门:镜像内 JAR 字节码实证 + docker run 跑冒烟 + 是上云前提,无论范围/时机都该做 + + + 阶段 1 · 装 k3s(空集群·live 仍裸跑) + mini-desktop 单节点 + 门:Ready + 底噪 ≤1.5G + 装完仍 ≥6G 余量 + 装完掉到 6G 以下 → 单节点方案不成立,降级 + 回滚:k3s-uninstall.sh 一键卸,回纯裸跑 + + + 阶段 2 · 后端上集群(流量切换在此) + 隔离期 Pod 用 server.port=48092 起(避撞 live) + 门:隔离 48092 冒烟全绿 → 切 48080 → 复验 + UI 走查 + 回滚:delete + /tmp 备份 JAR 拉起 :48080 + 风险最高一步;裸 JAR 路径始终在手边 + + + 阶段 3 · 前端 + 观测栈上集群 + studio/admin 静态产物 nginx 镜像(--mode staging) + 观测栈进 observability ns · admin 补观测入口 + 门:UI 走查 + 看板出图 + 至少一条告警自测 + 观测栈是旁路,挂了不咬主链路;delete 即可 + + + + + + + + + 本阶段上 k8s 买到的: + 声明式部署 + rollout undo 一键回滚 + requests/limits 共存保护 + 观测栈编排。 + 买不到的: + 高可用(单节点节点挂了集群全没)——要等第二台有余量的 x86 机器 + 多节点。别误以为「上了 k8s 就更稳」。 + 部署链 git push → Gitea → mini-desktop 拉取不变;冒烟门接口零改动(仍打 :48080);构建门红线一条不丢,字节码实证从「核 JAR」升级成「核镜像里的 JAR」。 + + + 图例: + 实心=今天就在跑·全程保留 + 紫虚线=缓做目标态(W-G1 后) + 跨机访问 / 控制 + 伸缩 + 可靠 + 成本 + 声明式/平滑长多节点=伸缩 · 共存保护/一键回滚=可靠 · 不硬凑第二机=成本 + + 映射:运维/k8s迁移.md(+ README.md §4) | 状态:缓(收窄 + 缓做·排 W-G1 之后) | 设计变动须同步本图 + diff --git a/docs/architecture/运维/运维图说.md b/docs/architecture/运维/运维图说.md new file mode 100644 index 00000000..12cdea57 --- /dev/null +++ b/docs/architecture/运维/运维图说.md @@ -0,0 +1,184 @@ +--- +date: 2026-06-22 +topic: 运维域图说——把「部署在哪、怎么变成在跑的服务、靠什么持续看见它」画成一套图 + 讲解(架构图集金样板·运维域首件) +status: 现行 + 待接(观测)+ 缓做(k3s)三态并存 · 防漂移门记源档 commit hash +映射源档: + - docs/architecture/运维/README.md @ 15b707fd + - docs/architecture/运维/观测体系.md @ 3e71715a + - docs/architecture/运维/k8s迁移.md @ 15b707fd +--- + +# 运维域图说 + +> **这是什么**:绘境AI 运维域的「看图入口」。运维域回答三件事——这套系统**部署在哪些机器上**、代码**怎么从一行 commit 变成在运行的服务**、靠**什么确认它还活着并且活得够好**。这份图说把这三件事,连同两件「设计已出但还没接上」的事(线上观测体系、k3s 迁移),用一套图加配套讲解画清楚。复杂、跨机的用手绘 SVG 大图,简单、线性的用 Mermaid,已有的图一律单源引用、不复制。 +> +> **给谁看**:负责部署与值守的工程师、做发版的人、排查线上故障的人,以及关心可用性与运维成本的创始人。判断「这套运维方案是不是想要的那个」看这份图就够建立全局轮廓,逐字节的命令仍去 `deploy/` 脚本与 `.agents/skills/staging-ops.md`。 + +--- + +## 0. 阅读约定与同步纪律 + +- **映射的设计档**:本图说不另立设计,只把运维域三份 canonical 设计档画出来——部署链与四机分工出自 [`运维/README.md`](README.md),线上观测体系出自 [`观测体系.md`](观测体系.md),k3s 迁移出自 [`k8s迁移.md`](k8s迁移.md)。frontmatter 里记了这三份的当前 commit hash,作为**防漂移门**:源档一旦变更、hash 对不上,这份图说与对应 SVG 即标「待复核」,由收口脚本比对。 +- **同步纪律**:**设计一变动,本图说与对应 SVG 必须同步更新**,每张 SVG 脚注注明映射源档与状态。已并入 wave 收口清单。 +- **三种状态贯穿全图,必须看清**:运维域的特殊之处是它同时承载「现行已建」「设计已出待接线」「收窄后缓做」三类内容,绝不能把后两类画成已建。图 6(观测)整体是**接**(待接线)、图 8(k3s)整体是**缓**(缓做、排在 W-G1 之后),其余六张是**现**。每张图内部还会用实心块 / 虚线框 + 「待接线」「缓做」小标把单个元素的状态再标一层。 +- **产物形式**:SVG 入仓(GitHub 与编辑器直接渲染矢量),PNG 在 mini-desktop 转(6c6g 禁 chrome、无转换器)。本图说只出 SVG。 + +## 1. 全图通用图例 + +**生产维度徽章**(用小色块 chip + 白字标在每张图相关的维度上,运维域主要关心其中四个): + +- **可靠**(`#16a34a`):隔离验证安全变体、备份 JAR 一键回滚、冒烟门退出码可卡口、观测旁路降级、k8s 共存保护与 rollout undo。 +- **可观测**(`#0ea5e9`):OTel 统一采集、Grafana 三联下钻、夜莺告警必达——这是图 6 的主轴。 +- **安全**(`#dc2626`):push 走 Gitea 中转(scp/rsync 被分类器拦)、Collector 集中脱敏、admin 观测入口权限守门不裸暴露后端。 +- **成本**(`#16a34a`):内网物理机 + 人工部署而非重 CI、不硬凑第二台机器上多节点、缓存命中率哨兵——都是 < ¥5,000/月成本盒子的取舍。 + +(另两个维度 **数据** `#475569`、**伸缩** `#7c3aed` 在 k3s 图上点到:声明式编排与平滑长成多节点 = 伸缩。) + +**复用 / 边界三线语义**: + +- **实线**(`#334155`):现成直接用、同步动作或命令。 +- **虚线**(`#64748b`,`stroke-dasharray="6 4"`):自建 / 解耦 / 回执 / 待接线 / 缓做的目标态。 +- **双线 / 加粗框**:跨域共享的公共件或需要强调的边界。 + +> 运维域图说里,虚线还额外承担一层**状态语义**:凡是「待接线」(观测)或「缓做」(k3s)的元素,一律用虚线框 + 文字小标画出,与「现行已建」的实心块在视觉上一眼区分开。这是本域最该守的纪律点——观测体系和 k3s 迁移都是「设计已出、代码未接」,绝不能因为画得好看就让人误以为已经建好了。 + +--- + +## 2. 图集 + +### 图 1 · 四机分工拓扑〔引·现〕 + +> 单源引用,不复制:四机分工拓扑的 Mermaid 已在 **[运维/README.md §1.1](README.md#11-跑在哪里四台机器各司其职)** 内嵌,是该图的唯一来源。这里只链接、不抄一份进来——同一张图存两处、改一处漏一处,正是这份图集要消灭的漂移面。 + +这张图回答「服务跑在哪里」。绘境AI 在内网阶段刻意不上云托管、不上 Kubernetes,而是用一组通过 Tailscale 连起来的物理机,按角色硬分工:**lili-mac** 是创作者本地工作站,跑开发主会话与快构建,但它会休眠合盖,所以不进 Tailscale 调度、也不承担任何「门」;**mini-desktop** 是权威验收机,staging 全栈、重型构建、浏览器端到端测试都在这里,且与生产同构;**mini-infra** 是共享基建机,Gitea / PostgreSQL / Redis / MinIO / new-api 都在这台,它绝不跑项目 app 或重型构建;**6c6g** 是常驻无人值守机,跑常开的编排、定时任务、git 操作,但禁跑重活(重型前端构建会 OOM)。 + +读这张图要抓住一条最硬的边界:**Mac 是 ARM 架构,产出的 JAR 与前端产物架构中立可用,但 Docker 镜像与冒烟门必须在 x86 的 mini-desktop 出**,否则镜像架构与生产不同构,等于半假的门。这条边界在后面图 8(k3s 迁移)里会再次出现并延伸——到了 k8s 时代,「进集群的镜像必须 mini-desktop x86 出」依然成立。这张图没有现行/远期的灰度,它就是当前真相;唯一的「设计缝」其实在别处——README §1.1 自己点明 mini-infra 上跑的关系型库是 PostgreSQL,而 mini-desktop 上是隔离的 staging MySQL,两者不是一回事,端点口径以凭据 SoT《内网凭据与端点.md》为准,别混。 + +### 图 2 · 部署链:push → pull → 重建 → 验门〔SVG新·时序·现〕 + +![图 2 部署链 push→pull→重建→验门](assets/02-部署链时序.svg) + +这张时序图回答「代码怎么变成在跑的服务」。它要先破除一个误解:开发团队版文档里那套「push 触发 CI、自动构建镜像、滚动更新」是**目标态蓝图,与现实系统性脱节**(那份文档顶部自带审计横幅说明这点)。现行的部署没有 docker-compose、也没有自建 CI 流水线,而是**人工经标准序在 mini-desktop 上完成**——图里把它画成四条泳道之间的一串动作,并在底部用一个虚线框单独把这套蓝图标成「现实未建·勿当现状」,免得有人照着蓝图去找不存在的 CI。 + +核心三步在图上从左到右展开。**第一步同步代码**:代码经 `git push` 推到 Aliyun Gitea(默认远程,SSH `:2222`),mini-desktop 再从同一个 Gitea 以匿名 HTTP `:3000` 拉取(注意是 HTTP 只读端口、不是 SSH)。这里画出了两条已经踩过的坑:直接 `scp / rsync` 源码树会被分类器拦下,所以必须走 Gitea 中转;大资产 push 在途时再发 push 会撞 Gitea 的 ref 锁,所以同仓 push 要串行。**第二步重建**:在 mini-desktop 上 `git reset --hard` 到目标分支、`mvn clean install` 出新的 fat JAR,收口前**以字节码实证**新 JAR 确实含本次改动(曾因「构建源 ≠ 运行的 JAR」翻过车,图上单画一格强调)。**第三步验门**:重启后验 health 200、四模板就绪、Flyway up-to-date,再跑冒烟门(即图 4)。 + +整张图最该让人看出的,是中间那个橙色虚线框——**隔离验证安全变体**。高风险或整机变更不直接动 live,而是先用新 JAR 在隔离端口 `:48090` 起一个实例验全,**live 的 `:48080` 全程不动**,验全过才切;任一步失败就用 `/tmp` 里的备份 JAR 把 `:48080` 恢复回去。这套「验全才切、失败即回」的安全变体是运维域可靠性的支点,后面图 8 把它原样平移到了 k8s 的隔离端口切换。图底还有一条红线值得记住:**API 全绿 ≠ UI 通**,编排器旁路会掩盖 UI 缺陷,用户可见波次收口前必须另做一次真浏览器 UI 走查。 + +### 图 3 · 隔离验证安全变体〔Mer新·流程·现〕 + +这张图把图 2 里那个橙框单独放大,讲清「为什么验全才切、失败怎么退」。它是一条状态/分支流程:新 JAR 不直接顶替 live,而是先在隔离端口 `:48090` 起一个实例,跑完整验证(health、四模板、Flyway、冒烟门 12 项);只有**全部通过**才停旧实例、释放端口、把新实例切到 `:48080`;**任一步失败,立即用 `/tmp` 备份 JAR 把 `:48080` 恢复**,回到改动前的形态,全程 live 不受影响。这条流程让一次高风险发版的下行风险被牢牢兜住——最坏情况也只是「白验一次、live 没动」,而不是「切了一半、服务挂了、退不回去」。 + +```mermaid +flowchart TB + START["新 JAR 已构建
(字节码实证含本次改动)"] --> ISO["在隔离端口 :48090
起一个新实例"] + LIVE["live 后端 :48080
全程不动 · 继续服务"]:::live + ISO --> V{"在 :48090 验全?
health 200 · 四模板就绪
Flyway up-to-date · 冒烟门 12 项"} + V -- "全过" --> SWITCH["停旧实例 → 释放端口
把新实例切到 :48080"] + V -- "任一失败" --> ROLLBACK["/tmp 备份 JAR
把 :48080 恢复回去
(5 分钟内 · live 本就没动)"]:::danger + SWITCH --> REVERIFY["切后复验冒烟门
+ 真 UI 走查"] + SWITCH -.->|稳定观察一两天后| CLEAN["再删 /tmp 备份 JAR"] + ROLLBACK --> BACK["回到改动前形态
排查后重来"] + + classDef live fill:#eff6ff,stroke:#2563eb,stroke-width:2px; + classDef danger fill:#fee2e2,stroke:#dc2626,stroke-width:2px; +``` + +这张图没有现行/远期边界,它就是现行高风险发版的标准动作;它和图 2 的关系是「图 2 给全链路、图 3 把其中最关键的安全机制放大」。要注意的设计缝只有一个:这套变体是**人工经标准序**执行的,不是声明式的——这正是图 8(k3s)想用 `kubectl rollout undo` 改进的地方,但在 k8s 落地前、乃至落地后的过渡期,这条人工兜底路始终保留。 + +### 图 4 · 冒烟门 12(+1)项〔SVG新·流程·现〕 + +![图 4 冒烟门 12+1 项](assets/04-冒烟门12项.svg) + +这张图回答「靠什么确认它健康」。每次 staging 部署之后跑一遍 `deploy/smoke-test.sh`——它是「部署后是否健康」的**就绪检查**(秒级、以读为主、可反复跑),逐项 PASS / FAIL,末尾给总判与退出码(0 = 全过,1 = 有 FAIL),所以能直接挂到部署脚本里当卡口。图上把 12 个默认项按「基础设施 / 鉴权 + 五条端到端闭环链路」分组画出来,让人一眼看出冒烟门验的不是零散接口,而是**五条业务链的关键 API 是否都在岗**:① 创作→生成→预览、② 发布→审核→游戏流、③ 试玩→互动→分享、④ 广告→收益→钱包、⑤ 数据回路(遥测→质量分→feed 重排)。 + +读这张图要抓三个细节。其一,**12 项里唯一的写操作**是建一条无副作用的草稿(`POST /app-api/studio/draft`,status 0、不发布、无下游副作用),用于验证创作入口 + 鉴权 + gameId 分配在岗;其余全是读。其二,`--deep` 才加的第 13 项(图上用橙色虚线框单独标「按需」)走真实写路径,触发一次生成入队 + 遥测落库,默认不跑、只验入队 `200/code=0`,不等生成完成。其三,图上用红框钉死了一个反复出现的坑:**feed 列表项的 `packageUrl` 字段恒为 null**,真实取游戏包要走 `GET /app-api/runtime/package/{versionId}`,别拿 `packageUrl` 当取包入口。 + +这张图的「设计缝」画在右下角那个虚线框里:冒烟门是「部署那一刻的就绪点检」,它**不是**深度 e2e(深度 e2e 由 agent-loop 编排器批跑,职责不重叠),也**还没有**常态监控来补它的另一半——这正好衔接到图 6 的观测体系。衔接点写在图上:冒烟门那 12 项的结果可以打成指标推给 Prometheus,让「每次部署的健康度」也进可观测历史,而不是只在部署当时的终端里闪一下。 + +### 图 5 · 可用性 SLO / Error Budget〔Mer新·讲解·现〕 + +这张图回答「健康到什么程度才算达标」。运维域的硬指标是**服务可用性 ≥ 99.5%**,与之配套的是 MVP 基础设施成本控制在 **< ¥5,000/月**——这两个数字是运维域所有取舍(不上云托管、单体而非微服务、人工部署而非重 CI)的约束来源。图把可用性目标拆成三档 SLO(来自 `security-and-reliability.md` §5.1:游戏流 99.5%/月 ≈ error budget 3.6 小时、AI 生成 99%/月 ≈ 7.2 小时、支付 99.9%/月 ≈ 43 分钟),并点明这些目标怎么落进成本盒子。 + +```mermaid +flowchart TB + GOAL["可用性目标 ≥ 99.5%(MVP)
+ 基础设施成本 ¥5,000/月以内"]:::goal + subgraph SLO["三档 SLO + Error Budget(security-and-reliability §5.1)"] + direction LR + S1["游戏流
99.5% / 月
容错预算 ≈ 3.6 小时"] + S2["AI 生成
99% / 月
容错预算 ≈ 7.2 小时"] + S3["支付
99.9% / 月
容错预算 ≈ 43 分钟"] + end + subgraph COST["成本盒子怎么撑住目标"] + direction LR + C1["内网物理机
不上云托管"] + C2["单体
而非微服务"] + C3["人工部署
而非重 CI"] + end + GOAL --> SLO + GOAL --> COST + SLO -.->|"现状:只有一个目标数字,
没有东西在持续度量它"| GAP["缺口:error budget 无人消耗与记录
→ 由观测体系落地(图 6)
用 health 成功率算实际可用性 + burn rate 看板"]:::gap + + classDef goal fill:#f0fdf4,stroke:#16a34a,stroke-width:2px; + classDef gap fill:#fff7ed,stroke:#d97706,stroke-width:2px,stroke-dasharray:5 4; +``` + +这张图最该让人看出的是那条虚线指向的**缺口**:≥99.5% 现在是一个**目标数字**,但没有任何东西在持续度量它、没有 error budget 在被消耗和记录。换句话说,达标线画出来了,举证手段还没建。这个缺口的归宿就是图 6 的观测体系——用 health 探测的成功率算游戏流 API 的实际可用性,对着三档 SLO 做 burn rate 看板和告警,让「达没达标」从一句承诺变成一张有数据、有容错预算余量的看板。所以图 5 和图 6 是一对:图 5 立目标、点缺口,图 6 给落地手段。 + +### 图 6 · 观测体系:OTel → Grafana / 夜莺〔SVG新·架构·接(设计已出·待接线)〕 + +![图 6 观测体系 OTel→Grafana/夜莺](assets/06-观测体系.svg) + +这张图回答「线上靠什么持续观测」——也是本域**最该验状态纪律**的一张。它整体是**接**(待接线):创始人 2026-06-21 定了栈(OTel 统一采集 + Prometheus + Grafana 主看板 + 夜莺主告警),正式设计稿已出,但**代码大半没接、没后端**。所以图上做了一件最要紧的事:把「现在已经有什么」和「还得新建什么」用实心块 / 虚线框严格分开,绝不让人误以为观测已经建好。 + +图按「采集 → 管道 → 存储 → 看板/告警」一条链画,逐段标状态。**唯一已就位的实心块**在图正下方高亮:aigc-server 已引入 `spring-ai-alibaba-starter-graph-observation`,SAA 裸图每个节点已发 `spring.ai.alibaba.graph.node.` 的 Micrometer observation(带 trace 和耗时,失败反映在指标里)——这是后端唯一一段已经在以标准产出、且依赖已就位的观测信号。但图上同样标清楚:它缺一个把它收走的后端、缺一个非 NOOP 的 `ObservationRegistry`(要 actuator/micrometer 装配在席)。**其余全是虚线框 + 「待接线」**:OTel Java Agent(自动 span,monitor starter 当前根本没进 aigc 部署单元)、五个生成业务指标(`gen_task_total` / `gen_duration_seconds` / `gen_queue_depth` / `gen_gate_fail_total` / `llm_cost`,目前在代码里**全部零命中**,要补 MeterRegistry 注册才有数据,其中成本指标还额外依赖一个尚未落地的 new-api `logs.quota` 采集薄片)、OTel Collector、Prometheus / trace 后端 / 日志后端(trace 倾向 Tempo、日志倾向 Loki,但都标「选型待定」)、Grafana、夜莺、通知通道、admin 观测入口。 + +读这张图要抓住三层设计意图。其一,**为什么 Collector 居中而不是各组件直连后端**:让每个进程直接推各自后端会把地址、协议、脱敏逻辑硬编码进每个应用,上 k8s 或换后端就得改一圈;中间放一个 Collector 当统一入口,脱敏(手机号/token)、批处理、采样、路由全在这一层集中做,换后端只改 Collector 导出配置、应用零改动——这正是「先单体可跑、后平滑上 k8s」能成立的技术支点。其二,**Grafana 和夜莺不是冗余而是分工**:Grafana 负责「你主动去看时看得清」(三联下钻),夜莺负责「你没在看时它把你叫来」(把 P0–P3 升级链变成真规则真通道,补上「告警通道一根没接」这个最大的空);创始人点名的三类关键告警都在图上有家——可用性走 P0、错误率(5xx>2%)走 P1、生成失败率(<70%)走 P1。其三,图底那条红线是观测体系的底线:**埋点开关默认关、经 profile 显式开,agent 异步批量上报、Collector 不可达时本地丢弃**,硬验收是「停掉 Collector,主链路与各 API 行为不变、冒烟门仍全绿」——观测栈本身挂掉,绝不能影响被观测的服务。 + +除图上画出的「trace/日志后端选型、admin 嵌入方式与 SSO(详见图 7)」这几条待核外,源档《观测体系.md》§6 还留了两条待创始人拍板的口径,读图时一并记住、别当已定:一是**观测数据保留期与成本**——trace 与 metrics 存多久直接吃 mini-desktop 的磁盘,要对一下账「预算里留的那笔监控冗余够不够这套栈的存储」;二是**生成成功率告警阈值**——现在 P1 写的是「成功率 < 70%」(已经很糟该紧急处理的线),而验收线是 ≥80%,是否要在 70% 之外再加一条 80% 的趋势预警,让成功率从 80% 往下掉时就先有提醒、而不是等到 70% 才报,也待拍。 + +### 图 7 · admin 观测入口〔Mer新·流程·接(待接线)〕 + +这张图把图 6 右侧「admin 观测入口」那一块放大,讲清运营怎么从后台一键进监控大盘。它整体也是**接**——而且这块的现状是**零**,要整套新建:`game-admin/src` 下没有任何观测相关的路由、菜单或组件,后端也没有任何给 admin 用的观测查询接口。所以「控制台一键进盘」不是接一根线,而是要补齐前端菜单/路由/组件、后端管理员专属查询接口、嵌入安全处理三件,缺一不可。图用虚线把这三件标成「待接线」,并画出 admin 进盘的链路与那个必须解决的体验问题:**别让运营进个监控还要再登一次 Grafana**。 + +```mermaid +flowchart LR + OPS["运营
(system_users 已登录 admin)"] --> ADMIN["game-admin 控制台
『观测』菜单(待建)"]:::todo + ADMIN -->|"观测大图:嵌入只读大盘"| EMBED["Grafana 嵌入面板
可用性/错误率/生成成功率"]:::todo + ADMIN -->|"深挖跳转"| PROXY["admin 后端 auth-proxy
透传 system_users 身份头(待建)"]:::todo + PROXY -->|"可信头免登(SSO)"| GRAF["Grafana 完整大盘
trace/metrics/log 三联下钻"] + ADMIN -.->|"告警概览 API"| N9E["夜莺告警列表
最近告警 / 值班状态"] + EMBED --> GRAF + + classDef todo fill:#fff7ed,stroke:#d97706,stroke-width:1.5px,stroke-dasharray:5 4; +``` + +这张图要让人看出两处尚未拍板的设计缝,都还**待核**,不能当成已定。其一,**嵌入方式**:iframe 嵌 Grafana 不是写个 `