topfans/docs/superpowers/specs/2026-06-08-docker-to-k8s-migration-design.md
zheng020 4db796f407 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
2026-06-08 17:25:17 +08:00

29 KiB

Docker → Kubernetes 迁移 设计文档

  • 日期: 2026-06-08
  • 涉及模块: docker/ 全部产物、新增 k8s/ 目录、未来四个新服务
  • 现状: docker-compose.prod.yml 在单台 VM (101.132.250.62) 上跑 10 个 Go 服务 + Postgres + Redis
  • 目标: 分两阶段把服务搬到 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 未来设计),本阶段不实现

本设计文档主要覆盖第一阶段,但保留第二阶段的设计草图作为远期参考。


一、背景与动机

1.1 业务背景

TopFans 是一个"明星粉丝平台"。不同明星的粉丝量差异极大:

  • 头部明星: 单明星可占整平台 50%+ 流量
  • 腰部明星: 几个明星可共享资源池
  • 尾部明星: 大量冷启动明星,合并部署降低成本

当前阶段的核心诉求是"合并部署降本" — 用 K8s 替代单 VM,利用云的弹性降低基础设施成本。不做分组隔离,所有明星共享一套基础设施。

1.2 当前架构瓶颈

docker-compose.prod.yml 是单机部署,本质限制:

能力 Docker Compose Kubernetes
多机部署
自动扩缩容 (HPA/KEDA)
滚动升级 手动
故障自愈 手动
资源按需分配 (固定 VM) (Pod request/limit)
新增业务线 改 compose helm install

1.3 即将到来的新服务

代码或 PRD 中已提及、未来需要独立部署的全新系统(非复用现有服务,各自独立代码库、独立 DB,只通过 API 互相调用):

服务 作用 关系
admin 后台管理平台(运营、客服) 独立系统,通过 API 调用 userservice / 其他服务
review 审核工作流(UGC 内容审核) 独立系统,通过 API 调用 assetservice 读取待审内容,审核结果回写自己的 DB
ai-image-gen AI 图片生成(镭射卡) 独立系统,gateway 改为通过 API 调用它(原 MiniMax 调用逻辑迁过去)
ai-chat AI 对话(粉丝互动) 独立系统,前端通过 API 调用它(从 aichatservice 拆出来)

关键澄清: 这四个服务与现有 userservice / assetservice / aichatservice没有代码级复用关系,也没有共享 DB schema。它们是完全独立的新服务,通过 HTTP/gRPC API 互相调用。K8s 迁移任务只负责为它们预留 namespace 位置和部署模板,不涉及实现。


二、候选方案 (Phase 1 范围)

第一阶段的核心问题是:要不要迁移到 K8s?如果要,采用什么部署结构? 给出三个候选:

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             (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. 完全对齐第一阶段目标: 合并部署,利用 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. ⚠️ 一次性迁移工作量: 镜像推到 ACR、域名切换、数据迁移(Docker → RDS)
  2. ⚠️ 没有按明星隔离: 头部明星的爆款活动仍可能影响尾部(可接受,这就是 Phase 1 的取舍)
  3. ⚠️ DB 走外部托管: 一次性数据迁移风险(可用阿里云 DTS 平滑迁移)
  4. ⚠️ 新服务只在 K8s 占位: 本阶段不实现,只是预留

适用场景: 业务初期/中期、流量逐渐增长、需要脱离单 VM 限制、为未来扩展留空间。完全匹配第一阶段目标

结论: 第一阶段强烈推荐


2.3 方案 C:从第一天就做按组隔离的 K8s

结构: 直接上"namespace per group",把方案 B 的 Phase 1 + Phase 2 合并到一次完成。

优点:

  1. 一步到位,不需要后续重构

缺点:

  1. 过度工程: 当前业务量不需要,合并部署完全够用
  2. 迁移复杂度高: 一次要把"VM → K8s"和"合并 → 隔离"两个变更叠在一起,排查问题难
  3. 每组 ResourceQuota / 跨 ns Dubbo / Ingress Host 路由 — 都是当前用不到的能力,徒增维护成本
  4. 违反"YAGNI"原则: 没遇到的问题不要预先解决

结论: 不推荐。会拖慢第一阶段交付。


三、方案对比总结表 (第一阶段)

维度 A 不迁移 B 单 namespace 合并 C 第一天就分组
迁移成本 0 中等
是否解决"降本"诉求
应用代码改动 0 0 少量(可能要改 Dubbo URL)
故障自愈
自动扩缩容
后续按组隔离成本 低(helm values 调整) 已经是
复杂度 最低 适中
与第一阶段目标匹配 (但过度)
推荐度 强烈推荐

四、推荐方案 (B) 详细设计 — 第一阶段:合并部署

4.1 集群拓扑 (Phase 1)

K8s 集群 (单 namespace,所有服务合并)
│
└── 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,Dubbo tri:// 用 K8s 短 DNS 名:

# 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
DUBBO_ACTIVITY_SERVICE_URL: tri://activityservice:20004
DUBBO_TASK_SERVICE_URL: tri://taskservice:20006
DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20005
DUBBO_AI_CHAT_SERVICE_URL: tri://aichatservice:20008
LASER_COMPOSITOR_URL: http://lasercompositor:7002

应用代码零改动 — 与原 docker-compose.prod.yml 几乎完全一致,只是服务名沿用,无需改任何代码。

4.2.2 Postgres / Redis 走外部托管

K8s 内只创建 ExternalName Service 占位:

# k8s/helm/topfans/templates/external-db/postgres-external.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
  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 / 备份 / 主从切换(全部交给云服务)
  • 应用代码不变,继续用 DB_HOST=postgresREDIS_HOST=redis 这种短名
  • 后续压力大了,云上一键升配即可

数据迁移: 从 VM 上的 Postgres 迁到阿里云 RDS,推荐用阿里云 DTS 做平滑迁移,详见第六章。

4.2.3 镜像构建与推送策略

两种选择:

  • 方式 1 (推荐): CI 构建镜像推送阿里云 ACR,Helm 通过 image.tag 引用
  • 方式 2: 维持当前 deploy.sh 的"本地 build → SSH 推到服务器"流程,服务器装 containerd/nerdctl

推荐方式 1,原因:

  • 镜像版本化管理 (:v1.0.0)
  • 利用镜像分发,build 一次,所有 K8s 节点共享
  • rollback 简单: helm rollback topfans 1

Dockerfile.services 保持不变(已是多阶段构建 OK),CI 脚本里改 tag 即可。

4.2.4 健康检查

改用 K8s livenessProbe / readinessProbe 显式配置,写在 Helm chart 里的 Deployment 中,而不是依赖 Dockerfile 的 HEALTHCHECK。

顺带说一下:原 docker/Dockerfile.services 有 HEALTHCHECK 端口错配 bug(参见 §4.2.5 旧内容,galleryservice 等用 21001 实际监听 20001)。K8s 不依赖 Dockerfile HEALTHCHECK,所以这次不修这个 bug,保持向后兼容,留待单独 issue 处理。

4.2.5 Secrets 管理

# k8s/helm/topfans/templates/secrets/db-credentials.yaml (示例)
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: topfans
type: Opaque
stringData:
  DB_PASSWORD: <占位>
  REDIS_PASSWORD: <占位>
  JWT_SECRET: <占位>
  OSS_ACCESS_KEY_ID: <占位>
  OSS_ACCESS_KEY_SECRET: <占位>
  DIFY_API_KEY: <占位>
  MINIMAX_API_KEY: <占位>
  QWEN_API_KEY: <占位>

安全约束:

  • 模板里只放占位符,真值不入 git
  • values-prod.yaml 加入 .gitignore,只提交 values-prod.example.yaml(空值)
  • 真值由 CI/CD 注入,或后续接 External Secrets Operator / 阿里云 KMS

4.2.6 HPA (水平自动扩缩)

为高负载服务启用 HPA,典型配置:

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
users JWT 携带 group_id
galleries / assets / stars Dubbo attachment 透传
评论/点赞/收藏 同上

第一阶段不实现,第二阶段另起专门的设计文档


五、目录结构 (Phase 1)

k8s/                                                (新目录,根目录同级)
├── README.md                                       运维使用手册
├── helm/
│   └── topfans/                                    单个 chart,部署整个 topfans
│       ├── Chart.yaml
│       ├── values.yaml                             默认值(占位 + 文档)
│       ├── values-prod.example.yaml                生产覆盖模板(空值,真值不入 git)
│       └── templates/
│           ├── _helpers.tpl
│           ├── 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 占位
│
└── .gitignore                                      忽略 values-prod.yaml 等含真值文件

原则:

  • docker/ 目录保留,继续支撑本地开发 (docker-compose.local.yml)
  • k8s/ 是新增的部署维度,与 docker/ 并存
  • 单个 Helm chart(topfans/)覆盖整个 Phase 1,第二阶段再考虑拆 chart
  • 未来四个新服务不在本次实现,只留 .gitkeep 占位
  • 镜像构建继续走 docker/Dockerfile.services (多阶段),不重复造轮子

六、迁移计划 (Phase 1)

Step 1: K8s 集群与基础设施

  • 选型:阿里云 ACK 测试集群(推荐,或自建 k3s 备选)
  • 部署 nginx-ingress + cert-manager(HTTPS)
  • 创建 topfans namespace
  • 创建阿里云 RDS(PostgreSQL)与 ElastiCache(Redis)实例
  • 用阿里云 DTS 把 VM 上的 Postgres 数据迁到 RDS
  • 验证: RDS / Redis 端到端连通

Step 2: 编写 Helm Chart

  • 初始化 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: CI 镜像构建

  • 选 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 中所有 setval 同步(从手工迁移数据开始时,见 CLAUDE.md 规范)

Step 6: 后续优化 (Phase 1 内可做)

  • 接 Prometheus + Grafana(可选)
  • 接 Loki 日志收集(可选)
  • 接阿里云监控告警(可选)
  • 验证 HPA 触发条件是否符合预期

Step 7: 第二阶段 (未来,不在本任务)

  • 拆 namespace (per-group)
  • 加 ResourceQuota
  • 应用层多租户改造 (group_id 字段)
  • 接 ArgoCD / Flux 做 GitOps
  • 接 External Secrets Operator
  • 多集群 / 多 region 容灾

七、风险与待定项 (Phase 1)

风险/待定 影响 缓解
VM 上的 docker-compose 在生产跑,切换期间需双轨 资源消耗(临时) 切完即停 VM
Postgres 走外部 RDS,数据迁移 数据一致性 用阿里云 DTS 做实时同步,切换时切流量
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 / 自定义指标) 资源效率 Phase 1 启用基础 HPA,稳定后按需调
多集群 / 多 region 容灾 灾备 Phase 1 不做,Phase 2/3 规划

八、用户已确认的关键决策 (Phase 1)

  1. 分两阶段:第一阶段合并部署降本,第二阶段按组隔离(第二阶段不在本任务)
  2. 配置格式: Helm Chart
  3. 数据库: 外部托管 (RDS/ElastiCache)
  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 字段添加与透传)
  • 按明星组的 namespace 拆分(留给第二阶段)
  • ResourceQuota / LimitRange(留给第二阶段)
  • Service Mesh (Istio / Linkerd)
  • 灾备多集群

十、Spec Review Refinements (Phase 1 相关)

本文档经 spec-document-reviewer 审查通过 (Status: Approved)。原始 review 在两阶段化之前完成,部分细化项已与 Phase 1 范围对齐,新版本不重新跑 review。Phase 1 范围内需要执行时关注的细化项:

10.1 镜像仓库路径决策 (Phase 1 必选)

values.yamlimage.repository 必须明确指向一个 registry:

  • 选项 A (推荐): 阿里云 ACR,image.repository: registry.cn-shanghai.aliyuncs.com/topfans/<service>,CI 构建推送
  • 选项 B: 自建 Harbor,image.repository: harbor.example.com/topfans/<service>

Phase 1 实施时需选其一,并写明 deploy.sh 的去留:

  • 选 A: 改 deploy.sh 为 CI 触发器(或废弃),服务器只拉不构建
  • 选 B: 保留 deploy.sh 构建逻辑,但目标改为推 Harbor 而非本地

10.2 Secret 落盘策略 (Phase 1 必做)

Secret 模板含明文密钥会污染 git。Phase 1 实施时需:

  • values-prod.yaml 加入 .gitignore,只提交 values-prod.example.yaml(空值)
  • 真值由 CI/CD 注入,或用 kubeseal / sops 加密后提交
  • 最低要求: 真值不进 git

10.3 PostgreSQL 序列同步 (CLAUDE.md 强制规范)

CLAUDE.md 明确指出,任何手动 INSERT 指定 id 必须同步 setval(...)。Phase 1 切换流量必须:

  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. 通过后才切流量

该流程是 Phase 1 切换的硬性 blocker

10.4 HEALTHCHECK 修复范围 (Phase 1 不修)

K8s 用 Helm chart 里的 livenessProbe / readinessProbe,不再依赖 Dockerfile HEALTHCHECK。原 docker/Dockerfile.services 中有端口错配 bug(如 galleryservice 监听 20001 但 HEALTHCHECK 用 21001),Phase 1 不修,保持向后兼容,留待单独 issue 处理。

10.5 不再相关的项 (已 out of Phase 1 范围)

以下细化项在原 spec 中存在,但 Phase 1 不实现,移到第十一章作 Phase 2 远期参考:

  • group_id 字段前置条件 → 移到 §11
  • 跨 namespace Dubbo 调用模式 → 移到 §11
  • ResourceQuota / LimitRange 模板 → 移到 §11

十一、Phase 2 未来设计 (仅作远期参考,不实现)

本节是 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-<group-name>: 每组 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 名:

# 组内 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 加资源限制,防吵市占率:

# 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):

# 未来 review 服务的环境变量 (在 topfans-shared namespace)
# 方式 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: 用配置中心或服务发现动态获取 (推荐)

应用层需支持 group 维度路由。具体实现属于应用层设计,不在 K8s 范围。

11.4.4 应用层多租户改造 (数据层)

在所有业务表加 group_id 字段,JWT 携带 group_id,Dubbo attachment 透传:

加 group_id 透传方式
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 即可。