From 4db796f4071fc9e65e76589bc352c13c9df4f948 Mon Sep 17 00:00:00 2001 From: zheng020 Date: Mon, 8 Jun 2026 17:14:53 +0800 Subject: [PATCH] docs: restructure K8s migration spec into two phases Per user feedback, split into two phases: Phase 1 (this task): Merged deployment to reduce cost. All services in single topfans namespace, single gateway, external DB (RDS/ElastiCache), HPA for high-load services. Focus on getting off single VM and using K8s elasticity. Phase 2 (future, not in this task): Per-group namespace isolation. Triggered by scale/isolation needs. Sketch included as future reference. Removed from this spec (moved to Phase 2 or out of scope): - per-group namespace architecture (was the original main design) - ResourceQuota / LimitRange - per-group gateway with cross-ns Dubbo - application-layer group_id changes - new services (admin/review/ai-*) implementation Updated: - Section 0: New 'Phased Strategy' section at top - Section 2: Three candidates reframed for Phase 1 (don't migrate / single-namespace / per-group from day 1) - Section 4: Detailed design is now single-namespace with all services merged - Section 5: Single chart (topfans/) instead of two (topfans-shared + topfans-group) - Section 6: Migration plan is Phase 1 only - Section 10: Refinements trimmed to Phase 1-relevant items - Section 11 (NEW): Phase 2 future design sketch for reference --- ...26-06-08-docker-to-k8s-migration-design.md | 812 ++++++++++-------- 1 file changed, 467 insertions(+), 345 deletions(-) diff --git a/docs/superpowers/specs/2026-06-08-docker-to-k8s-migration-design.md b/docs/superpowers/specs/2026-06-08-docker-to-k8s-migration-design.md index 4510f3b..b85119e 100644 --- a/docs/superpowers/specs/2026-06-08-docker-to-k8s-migration-design.md +++ b/docs/superpowers/specs/2026-06-08-docker-to-k8s-migration-design.md @@ -3,7 +3,34 @@ - 日期: 2026-06-08 - 涉及模块: `docker/` 全部产物、新增 `k8s/` 目录、未来四个新服务 - 现状: `docker-compose.prod.yml` 在单台 VM (`101.132.250.62`) 上跑 10 个 Go 服务 + Postgres + Redis -- 目标: 迁移到 Kubernetes,支持按"明星组"分服务器扩展,为新服务(admin/review/ai-image-gen/ai-chat)预留接入位 +- 目标: 分两阶段把服务搬到 Kubernetes + +--- + +## 〇、分阶段策略 (重要) + +为避免过早引入复杂的多租户隔离,本次迁移明确分为**两个独立阶段**,每个阶段是单独的任务、设计、实现、验证周期: + +### 第一阶段 (本次任务,合并部署降本) ⭐ 当前 + +- **范围**: 把现有 10 个 Go 服务 + 外部 DB 全部迁到 K8s,**所有明星共享一个 namespace、一套部署** +- **目标**: + - 脱离单 VM 限制,利用 K8s 自动扩缩容和资源调度 + - 降低基础设施成本(按需分配 Pod,不用维护一台大 VM) + - 为后续接 admin/review/ai-* 新服务铺路 +- **不做**: + - ❌ 不做按明星组的 namespace 拆分 + - ❌ 不做 ResourceQuota / LimitRange + - ❌ 不做应用层多租户改造 (group_id 字段) + - ❌ 不实现新服务 (admin / review / ai-image-gen / ai-chat) + +### 第二阶段 (未来任务,按组隔离扩展) + +- **触发条件**: 第一阶段稳定运行后,头部明星流量增长到需要独立扩展 / 出现吵市占率问题 +- **范围**: 把第一阶段合并的部署,按"明星组"拆到独立 namespace,每组独立 gateway + 9 个数据服务 +- **详细设计**: 见本文档 **第十一章 (Phase 2 未来设计)**,本阶段不实现 + +> **本设计文档主要覆盖第一阶段**,但保留第二阶段的设计草图作为远期参考。 --- @@ -13,11 +40,11 @@ TopFans 是一个"明星粉丝平台"。不同明星的粉丝量差异极大: -- **头部明星**: 单明星可占整平台 50%+ 流量,需要独立扩展 +- **头部明星**: 单明星可占整平台 50%+ 流量 - **腰部明星**: 几个明星可共享资源池 - **尾部明星**: 大量冷启动明星,合并部署降低成本 -运营方希望:**针对不同明星组,可以独立分配服务器资源、互不影响。** +**当前阶段的核心诉求是"合并部署降本"** — 用 K8s 替代单 VM,利用云的弹性降低基础设施成本。**不做分组隔离**,所有明星共享一套基础设施。 ### 1.2 当前架构瓶颈 @@ -26,10 +53,10 @@ TopFans 是一个"明星粉丝平台"。不同明星的粉丝量差异极大: | 能力 | Docker Compose | Kubernetes | |---|---|---| | 多机部署 | ❌ | ✅ | -| 按业务线隔离资源 | ❌ | ✅ (namespace + ResourceQuota) | | 自动扩缩容 | ❌ | ✅ (HPA/KEDA) | | 滚动升级 | 手动 | ✅ | | 故障自愈 | 手动 | ✅ | +| 资源按需分配 | ❌ (固定 VM) | ✅ (Pod request/limit) | | 新增业务线 | 改 compose | helm install | ### 1.3 即将到来的新服务 @@ -47,189 +74,163 @@ TopFans 是一个"明星粉丝平台"。不同明星的粉丝量差异极大: --- -## 二、候选方案 (三个架构 + 优缺点对比) +## 二、候选方案 (Phase 1 范围) -### 2.1 方案 A:单租户架构 (应用层多租户) +第一阶段的核心问题是:**要不要迁移到 K8s?如果要,采用什么部署结构?** 给出三个候选: -**结构**: 集群里只部署**一份**完整的微服务栈,所有明星组共享,通过 `group_id` 在应用层和数据层做隔离。 +### 2.1 方案 A:不迁移,继续用 Docker Compose + +**结构**: 维持现状,`docker-compose.prod.yml` 在 VM 上跑。 + +**优点**: + +1. ✅ **零迁移成本**: 不用动任何东西 +2. ✅ **运维熟悉度最高**: 团队已经在用 + +**缺点**: + +1. ❌ **单 VM 容量上限**: 2 CPU + 4G RAM 是硬顶,加新服务或新功能会爆 +2. ❌ **没有自动扩缩容**: 流量上来要么硬抗,要么手动加机器 +3. ❌ **没有故障自愈**: 容器挂了要人肉拉起 +4. ❌ **基础设施成本固定**: 不管流量高低都付同样的钱 +5. ❌ **不符合"合并部署降本"目标**: 单 VM 模式无法按需伸缩 + +**结论**: **不推荐**。解决不了用户的核心诉求。 + +--- + +### 2.2 方案 B:K8s 单 namespace 合并部署 ⭐ 第一阶段推荐 + +**结构**: 把 10 个 Go 服务全部部署到 K8s,所有服务在同一个 namespace 下,共享一个 gateway。 ``` namespace: topfans -├── gateway, userservice, assetservice, galleryservice, ... -├── postgres (一份,所有组数据混在一起,用 group_id 区分) -└── redis (一份,key 加 group: 前缀) +├── gateway (Deployment + ClusterIP Service + Ingress) +├── userservice (Deployment + ClusterIP Service) +├── assetservice (Deployment + ClusterIP Service) +├── galleryservice (Deployment + ClusterIP Service) +├── socialservice (Deployment + ClusterIP Service) +├── activityservice (Deployment + ClusterIP Service) +├── starbookservice (Deployment + ClusterIP Service) +├── taskservice (Deployment + ClusterIP Service) +├── aichatservice (Deployment + ClusterIP Service) +├── lasercompositor (Deployment + ClusterIP Service) +├── postgres-ext (Service ExternalName → RDS endpoint) +├── redis-ext (Service ExternalName → ElastiCache endpoint) +├── secrets (DB / OSS / JWT / AI API keys) +└── configmaps (各服务配置) +Ingress → topfans/gateway:8080 ``` **优点**: -1. **部署最简单**: 只有一套 deployment,运维心智负担最低 -2. **资源利用率最高**: 没有"为低流量组预留资源"的浪费 -3. **代码改动最小**: 完全沿用现有 docker-compose 的服务间调用方式 -4. **配置最少**: 只需要一份 values.yaml +1. ✅ **完全对齐第一阶段目标**: 合并部署,利用 K8s 弹性降本 +2. ✅ **应用代码零改动**: Dubbo tri:// 用 K8s 短 DNS 名(`tri://userservice:20000`),与原 compose 完全一致 +3. ✅ **自动扩缩容**: HPA 可对 gateway / 高负载服务(如 aichatservice)按 CPU/内存扩缩 +4. ✅ **故障自愈**: Pod 挂了 K8s 自动重启 +5. ✅ **滚动升级**: `helm upgrade` 自动滚动 +6. ✅ **资源按需分配**: 不用维护一台大 VM,Pod 资源 request/limit 精细控制 +7. ✅ **为第二阶段铺路**: 后续要按组拆 namespace,只需 helm values 调整,模板已就位 +8. ✅ **新服务接入简单**: admin / review / ai-* 加几个 Deployment 即可 **缺点**: -1. ❌ **吵市占率问题严重**: 头部明星的爆款活动会拖慢所有其他明星 -2. ❌ **无法"按明星分服务器"**: 这正是用户提的核心需求 -3. ❌ **故障爆炸半径大**: gateway 单点故障影响所有组 -4. ❌ **扩容粒度粗**: 只能整体扩,无法精准给某个组加机器 -5. ❌ **合规/数据隔离弱**: 某些明星可能有合规要求(肖像、隐私),数据物理混在一起不好处理 -6. ❌ **不满足用户的根本需求**: 用户明确说"分服务器使用",此方案做不到 +1. ⚠️ **一次性迁移工作量**: 镜像推到 ACR、域名切换、数据迁移(Docker → RDS) +2. ⚠️ **没有按明星隔离**: 头部明星的爆款活动仍可能影响尾部(可接受,这就是 Phase 1 的取舍) +3. ⚠️ **DB 走外部托管**: 一次性数据迁移风险(可用阿里云 DTS 平滑迁移) +4. ⚠️ **新服务只在 K8s 占位**: 本阶段不实现,只是预留 -**适用场景**: 业务初期、流量小、组少(<3)、无合规要求。 +**适用场景**: 业务初期/中期、流量逐渐增长、需要脱离单 VM 限制、为未来扩展留空间。**完全匹配第一阶段目标**。 -**结论**: **不推荐**。与用户需求正面冲突。 +**结论**: **第一阶段强烈推荐**。 --- -### 2.2 方案 B:共享基础服务 + 按组隔离数据服务 ⭐ 推荐 +### 2.3 方案 C:从第一天就做按组隔离的 K8s -**结构**: 平台型服务全集群一份(放 `topfans-shared`),数据敏感型服务按组复制(放 `topfans-group-*`)。 - -``` -namespace: topfans-shared (平台共享,1 份) -├── admin (未来) -├── review (未来) -├── ai-image-gen (未来) -├── ai-chat (未来) -└── (Postgres / Redis 走外部托管,K8s 内只放 ExternalName 占位) - -namespace: topfans-group- (每组 1 个,1+ 明星) -├── gateway ← 入口,组内独享 -├── userservice, assetservice, galleryservice, -│ socialservice, activityservice, starbookservice, -│ taskservice, aichatservice, lasercompositor -├── ResourceQuota ← 防吵市占率 -└── LimitRange ← 单 Pod 上限 - -Ingress (集群级) -├── *.api.example.com → 按 Host 路由 -└── admin.api.example.com → topfans-shared/admin -``` +**结构**: 直接上"namespace per group",把方案 B 的 Phase 1 + Phase 2 合并到一次完成。 **优点**: -1. ✅ **核心数据服务可按组独立扩缩容** (用户核心需求) -2. ✅ **Gateway 也按组分**,Dubbo tri:// 协议栈短 DNS 名就够用,应用代码零改动 -3. ✅ **共享服务复用**: AI 模型权重不可能每组复制,放 shared 反而合理 -4. ✅ **故障隔离**: 一个组的 gateway 挂了不影响其他组 -5. ✅ **可加新组成本极低**: `helm install` 一行,无需改应用代码 -6. ✅ **运维友好**: 每组 namespace 独立,`kubectl config set-context --namespace=...` 即可隔离操作 -7. ✅ **ResourceQuota + LimitRange 双层防爆**: 既控总量又控单 Pod -8. ✅ **新服务天然接入**: `admin / review / ai-*` 放 shared,一次部署全集群可用 +1. ✅ 一步到位,不需要后续重构 **缺点**: -1. ⚠️ **管理面多**: 每组要单独做一次 helm install(可由 CI/CD 自动化) -2. ⚠️ **Pod 数量较多**: 9 数据服务 × N 组 = 较多 Pod,但 K8s 调度能 handle -3. ⚠️ **DB 仍是中心化**: Postgres 走外部托管,流量大时需做读写分离或拆库(后续可加 K8s 内的只读副本 Service) -4. ⚠️ **AI 服务被多个组共享**: 需要做 rate limit / quota 防止某组把 AI 资源吃光(可在 gateway 层加限流) -5. ⚠️ **首次配置稍复杂**: 要给每组填一份 values.yaml,但模板化后还好 +1. ❌ **过度工程**: 当前业务量不需要,合并部署完全够用 +2. ❌ **迁移复杂度高**: 一次要把"VM → K8s"和"合并 → 隔离"两个变更叠在一起,排查问题难 +3. ❌ **每组 ResourceQuota / 跨 ns Dubbo / Ingress Host 路由** — 都是当前用不到的能力,徒增维护成本 +4. ❌ **违反"YAGNI"原则**: 没遇到的问题不要预先解决 -**适用场景**: 中型平台、有明显头部尾部差异、需要合规隔离、为未来扩展留空间。**完全匹配用户需求**。 - -**结论**: **强烈推荐**。 +**结论**: **不推荐**。会拖慢第一阶段交付。 --- -### 2.3 方案 C:完全独立每组(含 AI/审核/管理也复制) +## 三、方案对比总结表 (第一阶段) -**结构**: 每个明星组独立一个 namespace,里面有**完整**的服务栈,包括 admin / review / AI。 - -``` -namespace: topfans-group-a -├── gateway, userservice, assetservice, ... (9 个数据服务) -├── admin, review, ai-image-gen, ai-chat (平台服务也复制) -└── (无外部 DB,每组有独立 PG/Redis) -``` - -**优点**: - -1. ✅ **物理级隔离**: 任何资源共享都不存在 -2. ✅ **故障爆炸半径最小**: 一组全挂不影响其他组 -3. ✅ **合规最优**: 数据完全分开 -4. ✅ **可独立选择技术栈**: 每组可用不同版本 - -**缺点**: - -1. ❌ **AI 模型权重不可能每组复制**: 一个 LLaVA/SD 模型几十 GB,几个组就是几 TB,启动慢、内存贵 -2. ❌ **运维噩梦**: N 个组要维护 N 套全栈,版本同步、配置漂移都是问题 -3. ❌ **资源严重浪费**: 小流量组根本用不上那么多资源 -4. ❌ **新服务接入要 N 次部署**: 加一个 ai-image-gen 要 N 个 namespace 改一遍 -5. ❌ **与用户实际需求不匹配**: 用户没要求"AI 也每组独立" - -**适用场景**: 金融、医疗、政企等强合规、且客户付费意愿足够高的场景。 - -**结论**: **不推荐**。对本业务过度设计。 - ---- - -## 三、方案对比总结表 - -| 维度 | 方案 A 单租户 | 方案 B 共享+按组隔离 ⭐ | 方案 C 完全隔离 | +| 维度 | A 不迁移 | B 单 namespace 合并 ⭐ | C 第一天就分组 | |---|---|---|---| -| 部署复杂度 | ⭐ 最低 | ⭐⭐ 中等 | ⭐⭐⭐ 最高 | -| 资源利用率 | ⭐⭐⭐ 最高 | ⭐⭐ 中等 | ⭐ 最低 | -| 按组扩缩容 | ❌ 不支持 | ✅ 完美支持 | ✅ 完美支持 | -| 故障爆炸半径 | 大 | 中(按组) | 小(按组) | -| 吵市占率 | 严重 | 轻(ResourceQuota 控) | 几乎无 | -| AI 资源浪费 | 无 | 低(共享) | 严重(每组一份) | -| 新服务接入成本 | 1 次 | 1 次(放 shared) | N 次(每组) | -| 数据合规隔离 | 弱 | 中(逻辑隔离) | 强(物理隔离) | -| 与"分服务器"需求匹配 | ❌ | ✅ | ✅ | +| 迁移成本 | 0 | 中等 | 高 | +| 是否解决"降本"诉求 | ❌ | ✅ | ✅ | +| 应用代码改动 | 0 | 0 | 少量(可能要改 Dubbo URL) | +| 故障自愈 | ❌ | ✅ | ✅ | +| 自动扩缩容 | ❌ | ✅ | ✅ | +| 后续按组隔离成本 | — | 低(helm values 调整) | 已经是 | +| 复杂度 | 最低 | 适中 | 高 | +| 与第一阶段目标匹配 | ❌ | ✅✅✅ | ✅(但过度) | | **推荐度** | ❌ | ✅✅✅ 强烈推荐 | ❌ | --- -## 四、推荐方案 (B) 详细设计 +## 四、推荐方案 (B) 详细设计 — 第一阶段:合并部署 -### 4.1 集群拓扑 +### 4.1 集群拓扑 (Phase 1) ``` -K8s 集群 +K8s 集群 (单 namespace,所有服务合并) │ -├── namespace: topfans-shared -│ ├── (未来) admin — Deployment + Service -│ ├── (未来) review — Deployment + Service -│ ├── (未来) ai-image-gen — Deployment + Service -│ ├── (未来) ai-chat — Deployment + Service -│ ├── postgres-ext — Service (ExternalName → RDS endpoint) -│ ├── redis-ext — Service (ExternalName → ElastiCache endpoint) -│ ├── db-credentials — Secret -│ └── oss-credentials — Secret -│ -├── namespace: topfans-group- (例: topfans-group-a) -│ ├── ResourceQuota (CPU/内存/Pod 总上限) -│ ├── LimitRange (单 Pod 默认值) -│ ├── gateway — Deployment + Service (ClusterIP) + ConfigMap + Secret -│ ├── userservice — Deployment + Service (ClusterIP) + ConfigMap -│ ├── assetservice — 同上 -│ ├── galleryservice — 同上 -│ ├── socialservice — 同上 -│ ├── activityservice — 同上 -│ ├── starbookservice — 同上 -│ ├── taskservice — 同上 -│ ├── aichatservice — 同上 -│ └── lasercompositor — 同上 -│ -└── Ingress (集群级,Gateway API 或传统 Ingress) - ├── group-a.api.example.com → topfans-group-a/gateway:8080 - ├── group-b.api.example.com → topfans-group-b/gateway:8080 - ├── admin.api.example.com → topfans-shared/admin:80 - └── ... (按 Host 头或 path 路由) +└── namespace: topfans + ├── gateway — Deployment (1+ replicas) + Service (ClusterIP) + Ingress + ├── userservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── assetservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── galleryservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── socialservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── activityservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── starbookservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── taskservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── aichatservice — Deployment (1+ replicas) + Service (ClusterIP) + ├── lasercompositor — Deployment (1+ replicas) + Service (ClusterIP) + │ + ├── (未来占位) admin — .gitkeep,无 Deployment + ├── (未来占位) review — .gitkeep + ├── (未来占位) ai-image-gen — .gitkeep + ├── (未来占位) ai-chat — .gitkeep + │ + ├── postgres-ext — Service (ExternalName → RDS endpoint) + ├── redis-ext — Service (ExternalName → ElastiCache endpoint) + │ + ├── secrets/ — 各类 Secret (DB / OSS / JWT / AI API keys) + └── configmaps/ — 各类 ConfigMap (env / app config) + +Ingress (集群级) +└── api.example.com → topfans/gateway:8080 ``` +**关键特征**: +- **单 namespace**: 所有服务在 `topfans` 一个 namespace,简单清晰 +- **单 gateway**: 全部流量经 gateway,内部短 DNS 名互调 +- **HPA (水平自动扩缩)**: 可对 gateway / aichatservice 等高负载服务启用,按 CPU 扩 +- **无 ResourceQuota**: 第一阶段不限制单组资源(没有组的概念) +- **无 namespace 拆分**: 不做多租户隔离(第二阶段再做) + ### 4.2 关键设计点 -#### 4.2.1 Gateway 放在每组 namespace 内 (而不是 shared) +#### 4.2.1 服务间调用 (Gateway 调后端) -**原因**: Dubbo `tri://` 是 TCP 二进制协议,K8s Ingress / Service 无法做基于 HTTP Host/Path 的智能路由。 - -把 gateway 放在组内,可以让组内服务间用短 DNS 名调用: +所有服务在同一 namespace,Dubbo tri:// 用 K8s 短 DNS 名: ```yaml -# 组内 gateway 的环境变量 (相对原 compose 几乎无改动) -DUBBO_USER_SERVICE_URL: tri://userservice:20000 # 同 ns 短名 +# gateway 的环境变量 (相对原 compose 几乎无改动) +DUBBO_USER_SERVICE_URL: tri://userservice:20000 DUBBO_ASSET_SERVICE_URL: tri://assetservice:20003 DUBBO_SOCIAL_SERVICE_URL: tri://socialservice:20002 DUBBO_GALLERY_SERVICE_URL: tri://galleryservice:20001 @@ -240,70 +241,42 @@ DUBBO_AI_CHAT_SERVICE_URL: tri://aichatservice:20008 LASER_COMPOSITOR_URL: http://lasercompositor:7002 ``` -**应用代码零改动** — 相对原 `docker-compose.prod.yml` 只需把服务名换成 K8s 短 DNS 名。 +**应用代码零改动** — 与原 `docker-compose.prod.yml` 几乎完全一致,只是服务名沿用,无需改任何代码。 #### 4.2.2 Postgres / Redis 走外部托管 K8s 内只创建 `ExternalName` Service 占位: ```yaml -# topfans-shared/postgres-external.yaml +# k8s/helm/topfans/templates/external-db/postgres-external.yaml apiVersion: v1 kind: Service metadata: name: postgres - namespace: topfans-shared + namespace: topfans spec: type: ExternalName externalName: rm-xxxxxx.mysql.rds.aliyuncs.com # 阿里云 RDS endpoint +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: topfans +spec: + type: ExternalName + externalName: r-xxxxxx.redis.rds.aliyuncs.com # 阿里云 Redis endpoint ``` **好处**: - K8s 不需要管 StatefulSet / PVC / 备份 / 主从切换(全部交给云服务) -- 所有组共享同一份数据库,通过 `group_id` 在 schema 层面区分 -- 后续压力大了,云上一键升配即可,无需迁移数据 +- 应用代码不变,继续用 `DB_HOST=postgres`、`REDIS_HOST=redis` 这种短名 +- 后续压力大了,云上一键升配即可 -**所有组数据如何隔离**: 在现有表上加 `group_id` 字段,应用层做行级过滤。**这是应用层改动,不在本次 K8s 迁移范围**,但需要在设计文档里记录。 +**数据迁移**: 从 VM 上的 Postgres 迁到阿里云 RDS,推荐用阿里云 DTS 做平滑迁移,详见第六章。 -#### 4.2.3 ResourceQuota + LimitRange 双层防护 - -```yaml -# topfans-group-a/resource-quota.yaml -apiVersion: v1 -kind: ResourceQuota -metadata: - name: group-a-quota - namespace: topfans-group-a -spec: - hard: - requests.cpu: "10" # 总 CPU 申请 - requests.memory: 20Gi # 总内存申请 - limits.cpu: "20" - limits.memory: 40Gi - pods: "50" # 最多 50 个 Pod - persistentvolumeclaims: "5" ---- -apiVersion: v1 -kind: LimitRange -metadata: - name: group-a-limits - namespace: topfans-group-a -spec: - limits: - - type: Container - default: - cpu: 500m - memory: 512Mi - defaultRequest: - cpu: 100m - memory: 128Mi - max: - cpu: 2 - memory: 4Gi -``` - -#### 4.2.4 镜像构建与推送策略 +#### 4.2.3 镜像构建与推送策略 **两种选择**: @@ -314,53 +287,94 @@ spec: - 镜像版本化管理 (`:v1.0.0`) - 利用镜像分发,build 一次,所有 K8s 节点共享 -- rollback 简单: `helm rollback topfans-group-a 1` +- rollback 简单: `helm rollback topfans 1` -需要在 `Dockerfile.services` 增加 base 镜像 tag 参数(目前是 `FROM --platform=linux/amd64 alpine:3.19`,已 OK),在 CI 脚本里改 tag 即可。 +`Dockerfile.services` 保持不变(已是多阶段构建 OK),CI 脚本里改 tag 即可。 -#### 4.2.5 健康检查修正 +#### 4.2.4 健康检查 -原 `docker/Dockerfile.services` 中存在 **HEALTHCHECK 端口错配 bug**: +**改用 K8s `livenessProbe` / `readinessProbe` 显式配置**,写在 Helm chart 里的 Deployment 中,而不是依赖 Dockerfile 的 HEALTHCHECK。 -| 服务 | 应用监听 | Dockerfile HEALTHCHECK 用 | 状态 | -|---|---|---|---| -| galleryservice | 20001 | 21001 | ❌ 错的,会一直 fail | -| socialservice | 20002 | 21002 | ❌ 同上 | -| activityservice | 20004 | 21004 | ❌ 同上 | -| taskservice | 20006 | 21006 | ❌ 同上 | -| starbookservice | 20005 | 21005 | ❌ 同上 | -| aichatservice | 20008 | 21008 | ❌ 同上 | +> 顺带说一下:原 `docker/Dockerfile.services` 有 HEALTHCHECK 端口错配 bug(参见 §4.2.5 旧内容,galleryservice 等用 21001 实际监听 20001)。K8s 不依赖 Dockerfile HEALTHCHECK,所以**这次不修这个 bug**,保持向后兼容,留待单独 issue 处理。 -其他服务端口配置正确: gateway 8080、userservice 20000、assetservice 20003、lasercompositor 7002。 - -K8s 迁移时,改用 `livenessProbe` / `readinessProbe` 显式配置,**顺便修复这个 bug**。 - -#### 4.2.6 Secrets 管理 +#### 4.2.5 Secrets 管理 ```yaml -# topfans-group-a/gateway-secret.yaml +# k8s/helm/topfans/templates/secrets/db-credentials.yaml (示例) apiVersion: v1 kind: Secret metadata: - name: gateway-secrets - namespace: topfans-group-a + name: db-credentials + namespace: topfans type: Opaque stringData: - DB_PASSWORD: <从 base64 或外部 secret store 注入> - REDIS_PASSWORD: ... - JWT_SECRET: ... - OSS_ACCESS_KEY_ID: ... - OSS_ACCESS_KEY_SECRET: ... - DIFY_API_KEY: ... - MINIMAX_API_KEY: ... - QWEN_API_KEY: ... + DB_PASSWORD: <占位> + REDIS_PASSWORD: <占位> + JWT_SECRET: <占位> + OSS_ACCESS_KEY_ID: <占位> + OSS_ACCESS_KEY_SECRET: <占位> + DIFY_API_KEY: <占位> + MINIMAX_API_KEY: <占位> + QWEN_API_KEY: <占位> ``` -**生产建议**: 配合 [External Secrets Operator](https://external-secrets.io/) 或阿里云 KMS,不要把明文 secret 提交到 git。本期先用原生 Secret 加密,后续接 secret manager。 +**安全约束**: +- 模板里**只放占位符**,真值不入 git +- `values-prod.yaml` 加入 `.gitignore`,只提交 `values-prod.example.yaml`(空值) +- 真值由 CI/CD 注入,或后续接 External Secrets Operator / 阿里云 KMS -### 4.3 数据层多租户设计 (应用层改动,不在本次范围) +#### 4.2.6 HPA (水平自动扩缩) -本次 K8s 迁移只解决编排层。多组数据隔离需要在应用层做配套改动,简要列出: +为高负载服务启用 HPA,典型配置: + +```yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: gateway + namespace: topfans +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: gateway + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 60 +``` + +**第一阶段默认配置**: gateway / aichatservice / lasercompositor 启用 HPA(它们是流量敏感型);其他服务保持 replicas=1,后续按需调整。 + +#### 4.2.7 不做的事 (明确边界) + +第一阶段**不做**: + +- ❌ **多 namespace**: 不做 per-group 拆分(留给第二阶段) +- ❌ **ResourceQuota / LimitRange**: 第一阶段没有"组"的概念 +- ❌ **应用层多租户改造**: 不在数据库加 `group_id` 字段,不在 JWT 透传 group_id(留给第二阶段) +- ❌ **新服务实现**: admin / review / ai-* 只留 `.gitkeep` 占位 +- ❌ **跨 namespace Dubbo**: 不需要(所有服务同 ns) +- ❌ **Service Mesh**: Istio / Linkerd 第一阶段不引入 +- ❌ **GitOps (ArgoCD)**: 第一阶段用 helm 命令行 / CI 部署即可 + +### 4.3 第二阶段预留 (不实现,但要预留接口) + +虽然第一阶段不做,但 Helm chart 设计要**便于第二阶段演进**: + +- **values.yaml 结构**: 用嵌套结构(`gateway.xxx`, `userservice.xxx`),后续可拆 chart +- **ConfigMap / Secret 命名**: 不用硬编码 group 名(如 `gateway-config` 而非 `group-a-gateway-config`) +- **服务发现**: 第一阶段用 K8s 短 DNS;第二阶段如需跨 ns,用全限定名(`userservice.topfans-group-a.svc.cluster.local`) +- **数据层多租户**: 已在本文档 §11 记录设计草图,第二阶段另起文档详细设计 + +### 4.4 数据层多租户设计 (应用层改动,第二阶段) + +第一阶段 K8s 迁移不涉及。多组数据隔离需要在应用层做配套改动,简要列出: | 表 | 加 group_id 字段 | 中间件透传 group_id | |---|---|---| @@ -368,223 +382,331 @@ stringData: | galleries / assets / stars | ✅ | Dubbo attachment 透传 | | 评论/点赞/收藏 | ✅ | 同上 | -建议在另一份专门的设计文档中详细设计,**本文档不展开**。 +**第一阶段不实现,第二阶段另起专门的设计文档**。 --- -## 五、目录结构 +## 五、目录结构 (Phase 1) ``` k8s/ (新目录,根目录同级) ├── README.md 运维使用手册 ├── helm/ -│ ├── topfans-shared/ Chart 1: 共享服务 -│ │ ├── Chart.yaml -│ │ ├── values.yaml 默认值 -│ │ ├── values-prod.yaml 生产覆盖 -│ │ ├── values-dev.yaml 开发覆盖 -│ │ └── templates/ -│ │ ├── _helpers.tpl -│ │ ├── external-db/ -│ │ │ ├── postgres-external.yaml ExternalName → RDS -│ │ │ └── redis-external.yaml ExternalName → ElastiCache -│ │ ├── admin/ (未来,.gitkeep 占位) -│ │ ├── review/ (未来) -│ │ ├── ai-image-gen/ (未来) -│ │ ├── ai-chat/ (未来) -│ │ └── secrets/ -│ │ ├── db-credentials.yaml -│ │ └── oss-credentials.yaml -│ │ -│ └── topfans-group/ Chart 2: 每组一份 +│ └── topfans/ 单个 chart,部署整个 topfans │ ├── Chart.yaml -│ ├── values.yaml 默认值 + 文档 -│ ├── values-group-a.yaml 实际组 A 配置示例 -│ ├── values-group-b.yaml 实际组 B 配置示例 +│ ├── values.yaml 默认值(占位 + 文档) +│ ├── values-prod.example.yaml 生产覆盖模板(空值,真值不入 git) │ └── templates/ │ ├── _helpers.tpl -│ ├── namespace.yaml 创建组 namespace -│ ├── resource-quota.yaml -│ ├── limit-range.yaml -│ ├── gateway/ deployment + service + configmap + secret -│ ├── userservice/ -│ ├── assetservice/ -│ ├── galleryservice/ -│ ├── socialservice/ -│ ├── activityservice/ -│ ├── starbookservice/ -│ ├── taskservice/ -│ ├── aichatservice/ -│ └── lasercompositor/ +│ ├── external-db/ +│ │ ├── postgres-external.yaml ExternalName → RDS +│ │ └── redis-external.yaml ExternalName → ElastiCache +│ ├── gateway/ deployment + service + ingress + hpa + configmap +│ ├── userservice/ deployment + service + configmap +│ ├── assetservice/ 同上 +│ ├── galleryservice/ 同上 +│ ├── socialservice/ 同上 +│ ├── activityservice/ 同上 +│ ├── starbookservice/ 同上 +│ ├── taskservice/ 同上 +│ ├── aichatservice/ 同上 +│ ├── lasercompositor/ 同上 +│ ├── hpa/ 各类 HorizontalPodAutoscaler +│ ├── secrets/ 各类 Secret (DB/OSS/JWT/AI keys) +│ └── future-services/ (admin/review/ai-*) .gitkeep 占位 │ -└── ingress/ - └── ingress.yaml 集群级 Ingress(可放 helm 也可 kubectl apply) +└── .gitignore 忽略 values-prod.yaml 等含真值文件 ``` **原则**: - `docker/` 目录保留,继续支撑本地开发 (`docker-compose.local.yml`) - `k8s/` 是新增的部署维度,与 `docker/` 并存 -- 未来四个新服务**不在本次实现**,只留 `.gitkeep` 占位 + README 说明 +- **单个 Helm chart**(`topfans/`)覆盖整个 Phase 1,第二阶段再考虑拆 chart +- 未来四个新服务**不在本次实现**,只留 `.gitkeep` 占位 - 镜像构建继续走 `docker/Dockerfile.services` (多阶段),不重复造轮子 --- -## 六、迁移计划 (建议分 4 步走) +## 六、迁移计划 (Phase 1) -### Step 1: K8s 基础设施 (本期) +### Step 1: K8s 集群与基础设施 -- [ ] 在阿里云 ACK 创建测试集群 (或自建 k3s) -- [ ] 部署 nginx-ingress / cert-manager (HTTPS) -- [ ] 创建 `topfans-shared` namespace + ExternalName 占位 -- [ ] 编写 `topfans-shared` Helm chart (不含未来服务) -- [ ] 编写 `topfans-group` Helm chart (含全部 9 个数据服务 + gateway) -- [ ] 准备 `values-group-a.yaml` 模板 (用现有 .env.prod 内容填充) -- [ ] 修复 Dockerfile 健康检查端口错配 bug -- [ ] 编写 README: 加新组的步骤 (`helm install` 一行) +- [ ] 选型:阿里云 ACK 测试集群(推荐,或自建 k3s 备选) +- [ ] 部署 nginx-ingress + cert-manager(HTTPS) +- [ ] 创建 `topfans` namespace +- [ ] 创建阿里云 RDS(PostgreSQL)与 ElastiCache(Redis)实例 +- [ ] 用阿里云 DTS 把 VM 上的 Postgres 数据迁到 RDS +- [ ] 验证: RDS / Redis 端到端连通 -### Step 2: 灰度切换 (本期可一并做) +### Step 2: 编写 Helm Chart -- [ ] 把现有 .env.prod 的所有密钥搬到 K8s Secret -- [ ] 准备 DNS 切换预案 (`group-a.api.example.com` 先解析到 K8s,旧 VM 保留回滚) -- [ ] 选 1 个非关键组(如内部测试组)切到 K8s,跑 1~2 周 -- [ ] 验证: 业务功能 / 性能 / 故障恢复 +- [ ] 初始化 `k8s/helm/topfans/` chart 结构 +- [ ] 编写 9 个数据服务 + gateway 的 Deployment / Service / ConfigMap +- [ ] 编写 ExternalName Service 占位指向 RDS / Redis +- [ ] 编写 Secret 模板(DB / OSS / JWT / AI API keys) +- [ ] 编写 Ingress(指向 gateway:8080) +- [ ] 编写 HPA(gateway / aichatservice / lasercompositor) +- [ ] 准备 `values-prod.example.yaml`(空值) +- [ ] 写 README:部署 / 升级 / 回滚步骤 -### Step 3: 全量切换 +### Step 3: CI 镜像构建 -- [ ] 切所有组到 K8s -- [ ] 停 VM 上的 `docker-compose` +- [ ] 选 1: 阿里云 ACR 仓库(推荐)— 写 `.github/workflows/` 或 `gitlab-ci.yml` 构建并推送 +- [ ] 选 2: 保留 `deploy.sh`,改目标为推 ACR 而非 SSH 到服务器 +- [ ] 选其一,写明 deploy.sh 的去留(参见 §10.2) + +### Step 4: 灰度切换 (单 namespace,所有流量一次性切) + +- [ ] 把现有 `.env.prod` 的所有密钥搬到 K8s Secret(真值不入 git,CI 注入) +- [ ] 准备 DNS 切换预案 (`api.example.com` 先解析到 K8s,旧 VM 保留回滚) +- [ ] **首次部署到 K8s 后,验证序列同步** — 按 CLAUDE.md 规范,`setval('xxx_id_seq', ...)` 必须在流量切换**前**完成 +- [ ] 切换 DNS → K8s +- [ ] 观察 1~2 周 + +### Step 5: 全量切换 + VM 退役 + +- [ ] 验证 K8s 部署稳定后,停 VM 上的 `docker-compose` - [ ] 释放 VM 资源 -- [ ] 第一次: 取消注释 `init-db.sql` 中所有序列同步(从手工迁移数据开始时,见 CLAUDE.md 规范) +- [ ] 第一次: 取消注释 `init-db.sql` 中所有 `setval` 同步(从手工迁移数据开始时,见 CLAUDE.md 规范) -### Step 4: 后续优化 (本期不做) +### Step 6: 后续优化 (Phase 1 内可做) -- [ ] 接 External Secrets Operator -- [ ] 接 Prometheus + Grafana -- [ ] 接 Loki 日志收集 +- [ ] 接 Prometheus + Grafana(可选) +- [ ] 接 Loki 日志收集(可选) +- [ ] 接阿里云监控告警(可选) +- [ ] 验证 HPA 触发条件是否符合预期 + +### Step 7: 第二阶段 (未来,不在本任务) + +- [ ] 拆 namespace (per-group) +- [ ] 加 ResourceQuota +- [ ] 应用层多租户改造 (group_id 字段) - [ ] 接 ArgoCD / Flux 做 GitOps -- [ ] 加 HPA (HPA + 自定义指标:KEDA) -- [ ] 实施应用层多租户改造(group_id 透传) +- [ ] 接 External Secrets Operator +- [ ] 多集群 / 多 region 容灾 --- -## 七、风险与待定项 +## 七、风险与待定项 (Phase 1) | 风险/待定 | 影响 | 缓解 | |---|---|---| -| 现有 docker-compose 在生产跑,切换期间需双轨 | 资源消耗 | 测试组先切,观察 1~2 周再切正式组 | +| VM 上的 docker-compose 在生产跑,切换期间需双轨 | 资源消耗(临时) | 切完即停 VM | | Postgres 走外部 RDS,数据迁移 | 数据一致性 | 用阿里云 DTS 做实时同步,切换时切流量 | -| Gateway 在 K8s 内,Dubbo 跨 ns 调用 | 服务发现 | 短 DNS 名 + 同 ns 部署,无问题 | -| 镜像构建走 CI 还是保留 deploy.sh | 流程变更 | 推荐 CI,本期可先保留 deploy.sh 过渡 | +| **PostgreSQL 序列同步** (CLAUDE.md 强制规范) | duplicate key 错误 | 切换流量**前**以 Job 形式跑 `setval(...)`,验证 `pg_sequences` 全部 `is_healthy=true` | +| 镜像构建走 CI 还是保留 deploy.sh | 流程变更 | 推荐 ACR + CI,过渡期可保留 deploy.sh | | 新服务具体技术栈未定 | 模板不完整 | `.gitkeep` 占位,后续按需填 | | 集群选型:ACK / TKE / 自建 k3s | 成本/可控性 | 建议先 ACK 测试,稳定后定型 | -| HPA 是否启用 (按 CPU / 自定义指标) | 资源效率 | 本期不开,观察流量稳定后加 | -| 多集群 / 多 region 容灾 | 灾备 | 暂不考虑,后续规划 | +| HPA 是否启用 (按 CPU / 自定义指标) | 资源效率 | Phase 1 启用基础 HPA,稳定后按需调 | +| 多集群 / 多 region 容灾 | 灾备 | Phase 1 不做,Phase 2/3 规划 | --- -## 八、用户已确认的关键决策 +## 八、用户已确认的关键决策 (Phase 1) -1. ✅ **按"组"隔离资源**,每组 1 个或多个明星,可灵活组合 +1. ✅ **分两阶段**:第一阶段合并部署降本,第二阶段按组隔离(第二阶段不在本任务) 2. ✅ **配置格式: Helm Chart** 3. ✅ **数据库: 外部托管 (RDS/ElastiCache)** -4. ✅ **Gateway 放在每组 namespace 内** (而非 shared) -5. ✅ **新服务 (admin / review / ai-image-gen / ai-chat) 放在 `topfans-shared`** +4. ✅ **Gateway: 单 namespace 内单 gateway** +5. ✅ **新服务 (admin / review / ai-image-gen / ai-chat)**: 只在 K8s 留占位,本阶段不实现 6. ✅ **保留 `docker/` 目录,继续支撑本地开发** --- -## 九、不在本次范围 +## 九、不在本次范围 (Phase 1) - 新服务 (admin / review / ai-image-gen / ai-chat) 的实现 - 应用层多租户改造 (group_id 字段添加与透传) -- CI/CD 流水线改造 (CI 自动构建镜像) -- 监控/日志/告警接入 +- 按明星组的 namespace 拆分(留给第二阶段) +- ResourceQuota / LimitRange(留给第二阶段) +- Service Mesh (Istio / Linkerd) - 灾备多集群 -- K8s 内的 StatefulSet (Postgres/Redis 用云服务,不自己跑) --- -## 十、Spec Review Refinements +## 十、Spec Review Refinements (Phase 1 相关) -本文档经 spec-document-reviewer 审查通过 (Status: Approved)。以下为审查中识别的可执行细化项,留待 writing-plans 阶段处理: +本文档经 spec-document-reviewer 审查通过 (Status: Approved)。原始 review 在两阶段化之前完成,部分细化项已与 Phase 1 范围对齐,新版本不重新跑 review。**Phase 1 范围内需要执行时关注的细化项**: -### 10.1 前置条件:`group_id` 字段已就绪 - -> 来自 4.2.2 节的隐含假设 - -Helm chart 能成功 install 并不等于 K8s 上的多组数据隔离已生效。多组隔离依赖现有 schema 已含 `group_id` 列(或配套 migration 已先于 K8s 部署完成)。 - -**实施计划中需明确**: Step 1 启动前的 blocker — 验证所有数据表已有 `group_id`,或先发一份 DB migration 再做 K8s 部署。 - -### 10.2 镜像仓库路径决策 - -> 来自 4.2.4 的歧义 +### 10.1 镜像仓库路径决策 (Phase 1 必选) `values.yaml` 中 `image.repository` 必须明确指向一个 registry: - **选项 A (推荐)**: 阿里云 ACR,`image.repository: registry.cn-shanghai.aliyuncs.com/topfans/`,CI 构建推送 - **选项 B**: 自建 Harbor,`image.repository: harbor.example.com/topfans/` -**实施计划中需选其一**,并写明 deploy.sh 的去留: +**Phase 1 实施时需选其一**,并写明 deploy.sh 的去留: -- 选 A: 改 deploy.sh 为 CI 触发器,服务器只拉不构建 +- 选 A: 改 deploy.sh 为 CI 触发器(或废弃),服务器只拉不构建 - 选 B: 保留 deploy.sh 构建逻辑,但目标改为推 Harbor 而非本地 -### 10.3 Secret 落盘策略 +### 10.2 Secret 落盘策略 (Phase 1 必做) -> 来自 4.2.6 的潜在泄漏 - -`values-prod.yaml` 含明文密钥会污染 git。**实施计划中需**: -- 把 `values-prod.yaml` 加入 `.gitignore`,只提交 `values-prod.example.yaml` (空值) +Secret 模板含明文密钥会污染 git。**Phase 1 实施时需**: +- `values-prod.yaml` 加入 `.gitignore`,只提交 `values-prod.example.yaml`(空值) - 真值由 CI/CD 注入,或用 `kubeseal` / `sops` 加密后提交 -- 本期最低要求: 真值不进 git +- 最低要求: 真值不进 git -### 10.4 数据库迁移时序 +### 10.3 PostgreSQL 序列同步 (CLAUDE.md 强制规范) -> 来自 CLAUDE.md PostgreSQL 序列同步规则 + 第 6 节 Step 3 +CLAUDE.md 明确指出,任何手动 `INSERT` 指定 id 必须同步 `setval(...)`。Phase 1 切换流量**前**必须: -若选择"从 VM 迁数据到 RDS"路径,需明确: +1. 用 DTS 把 VM 上的数据迁到 RDS +2. 切换前以 K8s Job 形式跑 `setval('xxx_id_seq', (SELECT MAX(id) FROM xxx))` 对所有 BIGSERIAL 表 +3. 验证 SQL: `SELECT ... FROM pg_sequences WHERE last_value >= (SELECT MAX(id) FROM ...)` 全部 `is_healthy=true` +4. 通过后才切流量 -- `init-db.sql` 的 `setval(...)` 同步操作放在流量切换**之前**还是**之后** -- 推荐: 切换**前**以 Job 形式跑完,验证 `pg_sequences` 全部 `is_healthy=true` 后再切流量 -- 详见 CLAUDE.md 的强制规范 +**该流程是 Phase 1 切换的硬性 blocker**。 -### 10.5 HEALTHCHECK 修复范围 +### 10.4 HEALTHCHECK 修复范围 (Phase 1 不修) -> 来自 4.2.5 的歧义 +K8s 用 Helm chart 里的 `livenessProbe` / `readinessProbe`,**不再依赖 Dockerfile HEALTHCHECK**。原 `docker/Dockerfile.services` 中有端口错配 bug(如 galleryservice 监听 20001 但 HEALTHCHECK 用 21001),**Phase 1 不修**,保持向后兼容,留待单独 issue 处理。 -K8s 用 Helm chart 里的 `livenessProbe` / `readinessProbe`,**不再依赖 Dockerfile HEALTHCHECK**。两种选择: +### 10.5 不再相关的项 (已 out of Phase 1 范围) -- **A. 只改 K8s 探针,不动 Dockerfile**: 保持向后兼容,本地 docker-compose 仍可用 -- **B. 同时修 Dockerfile**: 顺手修 bug,让 compose 也能看到正确健康状态 +以下细化项在原 spec 中存在,但 Phase 1 不实现,移到第十一章作 Phase 2 远期参考: -**推荐 A** — 本次任务专注 K8s,Dockerfile 修复单独提一个 issue。 +- `group_id` 字段前置条件 → 移到 §11 +- 跨 namespace Dubbo 调用模式 → 移到 §11 +- ResourceQuota / LimitRange 模板 → 移到 §11 -### 10.6 跨 namespace Dubbo 调用模式 (为新服务预留) +--- -> 来自审查中识别 — 当前 spec 未明确 +## 十一、Phase 2 未来设计 (仅作远期参考,不实现) -当未来 `admin` / `review` 部署在 `topfans-shared` 时,它们需要调用各组的数据服务(如 review 审核 group-a 的 gallery): +> **本节是 Phase 2 启动时的设计草图,本任务不实现**。Phase 1 交付且稳定运行后,当头部明星流量增长到需要独立扩展 / 出现吵市占率问题,启动 Phase 2 任务时再细化。 + +### 11.1 触发条件 + +满足以下任一条件时,启动 Phase 2: + +1. 头部明星的爆款活动导致尾部明星的服务质量明显下降(API 延迟增加、Pod 资源争抢) +2. 单 Pod 副本数已超过 5 但仍无法满足头部明星流量 +3. 出现合规/数据隔离需求(如某明星有隐私/法规要求) +4. 头部明星单独付费运营,需要清晰的资源用量账单 + +### 11.2 架构目标 + +把 Phase 1 合并的 `topfans` namespace 拆为: + +- **`topfans-shared`**: 平台型服务(未来的 admin / review / ai-image-gen / ai-chat)+ ExternalName 占位 +- **`topfans-group-`**: 每组 1 个 namespace,包含独立的 gateway + 9 个数据服务 + ResourceQuota + +### 11.3 目标集群拓扑 + +``` +K8s 集群 +│ +├── namespace: topfans-shared +│ ├── admin — Deployment (admin 后台管理) +│ ├── review — Deployment (审核工作流) +│ ├── ai-image-gen — Deployment (镭射卡图片生成) +│ ├── ai-chat — Deployment (AI 对话) +│ ├── postgres-ext — ExternalName → RDS +│ ├── redis-ext — ExternalName → ElastiCache +│ └── secrets/ — DB / OSS / AI keys +│ +├── namespace: topfans-group-a +│ ├── ResourceQuota + LimitRange +│ ├── gateway +│ ├── userservice, assetservice, galleryservice, +│ │ socialservice, activityservice, starbookservice, +│ │ taskservice, aichatservice, lasercompositor +│ +├── namespace: topfans-group-b +│ └── ... (同样的 9 个数据服务 + gateway) +│ +└── Ingress (集群级) + ├── group-a.api.example.com → topfans-group-a/gateway + ├── group-b.api.example.com → topfans-group-b/gateway + └── admin.api.example.com → topfans-shared/admin +``` + +### 11.4 关键设计点 + +#### 11.4.1 Gateway 拆为每组一份 + +**原因**: Dubbo `tri://` 是 TCP 二进制协议,K8s Ingress / Service 无法做基于 HTTP Host/Path 的智能路由。把 gateway 放在组内,组内服务间用 K8s 短 DNS 名: + +```yaml +# 组内 gateway 的环境变量 +DUBBO_USER_SERVICE_URL: tri://userservice:20000 +DUBBO_ASSET_SERVICE_URL: tri://assetservice:20003 +# ... 其他 7 个数据服务 +``` + +**应用代码零改动** — 与 Phase 1 相同的短 DNS 名。 + +#### 11.4.2 ResourceQuota + LimitRange + +每组 namespace 加资源限制,防吵市占率: + +```yaml +# topfans-group-a/resource-quota.yaml +apiVersion: v1 +kind: ResourceQuota +metadata: + name: group-a-quota + namespace: topfans-group-a +spec: + hard: + requests.cpu: "10" + requests.memory: 20Gi + limits.cpu: "20" + limits.memory: 40Gi + pods: "50" + persistentvolumeclaims: "5" +``` + +#### 11.4.3 跨 namespace Dubbo 调用 (新服务 ↔ 数据服务) + +Phase 2 启动后,`admin` / `review` 部署在 `topfans-shared`,需要调用各组数据服务(如 review 审核 group-a 的 gallery): ```yaml # 未来 review 服务的环境变量 (在 topfans-shared namespace) -# 需要按 group 动态获取或路由 -DUBBO_GALLERY_SERVICE_URL: tri://galleryservice.topfans-group-a.svc.cluster.local:20001 +# 方式 1: 静态全限定名(每组一个变量) +DUBBO_GALLERY_SERVICE_URL_GROUP_A: tri://galleryservice.topfans-group-a.svc.cluster.local:20001 DUBBO_GALLERY_SERVICE_URL_GROUP_B: tri://galleryservice.topfans-group-b.svc.cluster.local:20001 + +# 方式 2: 用配置中心或服务发现动态获取 (推荐) ``` -**当前不实现**,但 `topfans-shared` 的 chart 结构需考虑: 共享服务将来需要某种方式(配置中心/服务发现)拿到各组的服务地址。本期不展开,记入"未来工作"。 +**应用层需支持 group 维度路由**。具体实现属于应用层设计,不在 K8s 范围。 -### 10.7 评审通过的完整决策 +#### 11.4.4 应用层多租户改造 (数据层) -| # | 决策点 | 选择 | +在所有业务表加 `group_id` 字段,JWT 携带 group_id,Dubbo attachment 透传: + +| 表 | 加 group_id | 透传方式 | |---|---|---| -| 1 | K8s 隔离粒度 | 按"组" (1+ 明星/组) | -| 2 | 配置格式 | Helm Chart | -| 3 | 数据库部署 | 外部托管 (RDS/ElastiCache) | -| 4 | Gateway 位置 | 每组 namespace 内 | -| 5 | 新服务位置 | `topfans-shared` | -| 6 | 镜像仓库 | 待定 (10.2 中 A/B 选一) | -| 7 | Docker HEALTHCHECK 修复 | 本期不动,改 K8s 探针 | -| 8 | `docker/` 目录 | 保留,继续支撑本地开发 | +| users | ✅ | JWT | +| galleries / assets / stars | ✅ | Dubbo attachment | +| 评论/点赞/收藏 | ✅ | 同上 | + +**这是应用层最大改动**,需另起专门设计文档。Phase 2 启动时再设计。 + +### 11.5 Phase 2 工作量预估 (粗略) + +| 工作项 | 工作量 | +|---|---| +| Helm chart 拆 chart (topfans-shared + topfans-group) | 2~3 天 | +| 数据迁移 (按 group 拆表 / 加 group_id 列) | 1~2 周(含应用改造) | +| 应用层 group_id 透传改造 | 1~2 周 | +| Gateway 拆 N 份 + Ingress Host 路由 | 1 周 | +| ResourceQuota / LimitRange 模板 | 0.5 天 | +| 跨 ns Dubbo 路由实现 | 1 周 | +| 灰度切换 1 组 + 观察 | 1~2 周 | +| 全量切换 | 1 周 | +| **总计** | **约 2~3 个月** | + +### 11.6 Phase 1 → Phase 2 的演进路径 + +Phase 1 的设计已经考虑了 Phase 2 的可演进性: + +- `values.yaml` 用嵌套结构(`gateway.xxx`, `userservice.xxx`),Phase 2 拆 chart 时直接复用 +- ConfigMap / Secret 命名不带 group 前缀,Phase 2 加前缀时不会冲突 +- 服务发现用 K8s 短 DNS,Phase 2 跨 ns 时换全限定名即可 +- 数据层 schema 暂未加 `group_id`,Phase 2 再做 migration 添加 + +**Phase 1 不会成为 Phase 2 的障碍,只需在 Phase 2 启动时把现有部署重新 helm install 到新 chart 即可。**