topfans/docs/superpowers/specs/2026-06-08-docker-to-k8s-migration-design.md
zheng020 ebe4a622d8 docs: clarify 4 new services are independent new systems, not reuse
Per user feedback: admin/review/ai-image-gen/ai-chat are completely new independent systems with their own codebases and DBs, not reusing existing services. They only have API-level calls between each other and to existing services. Section 1.3 table and 'key clarification' paragraph updated. Other 'shared' references in the document refer to the platform-architecture sense (shared services for all groups), not code reuse, so no other changes needed.
2026-06-08 17:25:17 +08:00

24 KiB
Raw Blame History

Docker → Kubernetes 迁移 设计文档

  • 日期: 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)预留接入位

一、背景与动机

1.1 业务背景

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

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

运营方希望:针对不同明星组,可以独立分配服务器资源、互不影响。

1.2 当前架构瓶颈

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

能力 Docker Compose Kubernetes
多机部署
按业务线隔离资源 (namespace + ResourceQuota)
自动扩缩容 (HPA/KEDA)
滚动升级 手动
故障自愈 手动
新增业务线 改 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 位置和部署模板,不涉及实现。


二、候选方案 (三个架构 + 优缺点对比)

2.1 方案 A:单租户架构 (应用层多租户)

结构: 集群里只部署一份完整的微服务栈,所有明星组共享,通过 group_id 在应用层和数据层做隔离。

namespace: topfans
├── gateway, userservice, assetservice, galleryservice, ...
├── postgres (一份,所有组数据混在一起,用 group_id 区分)
└── redis (一份,key 加 group: 前缀)

优点:

  1. 部署最简单: 只有一套 deployment,运维心智负担最低
  2. 资源利用率最高: 没有"为低流量组预留资源"的浪费
  3. 代码改动最小: 完全沿用现有 docker-compose 的服务间调用方式
  4. 配置最少: 只需要一份 values.yaml

缺点:

  1. 吵市占率问题严重: 头部明星的爆款活动会拖慢所有其他明星
  2. 无法"按明星分服务器": 这正是用户提的核心需求
  3. 故障爆炸半径大: gateway 单点故障影响所有组
  4. 扩容粒度粗: 只能整体扩,无法精准给某个组加机器
  5. 合规/数据隔离弱: 某些明星可能有合规要求(肖像、隐私),数据物理混在一起不好处理
  6. 不满足用户的根本需求: 用户明确说"分服务器使用",此方案做不到

适用场景: 业务初期、流量小、组少(<3)、无合规要求。

结论: 不推荐。与用户需求正面冲突。


2.2 方案 B:共享基础服务 + 按组隔离数据服务 推荐

结构: 平台型服务全集群一份(放 topfans-shared),数据敏感型服务按组复制(放 topfans-group-*)。

namespace: topfans-shared                    (平台共享,1 份)
├── admin               (未来)
├── review              (未来)
├── ai-image-gen        (未来)
├── ai-chat             (未来)
└── (Postgres / Redis 走外部托管,K8s 内只放 ExternalName 占位)

namespace: topfans-group-<group-name>         (每组 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

优点:

  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. ⚠️ 管理面多: 每组要单独做一次 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,但模板化后还好

适用场景: 中型平台、有明显头部尾部差异、需要合规隔离、为未来扩展留空间。完全匹配用户需求

结论: 强烈推荐


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 完全隔离
部署复杂度 最低 中等 最高
资源利用率 最高 中等 最低
按组扩缩容 不支持 完美支持 完美支持
故障爆炸半径 中(按组) 小(按组)
吵市占率 严重 轻(ResourceQuota 控) 几乎无
AI 资源浪费 低(共享) 严重(每组一份)
新服务接入成本 1 次 1 次(放 shared) N 次(每组)
数据合规隔离 中(逻辑隔离) 强(物理隔离)
与"分服务器"需求匹配
推荐度 强烈推荐

四、推荐方案 (B) 详细设计

4.1 集群拓扑

K8s 集群
│
├── 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-<group-name>   (例: 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 路由)

4.2 关键设计点

4.2.1 Gateway 放在每组 namespace 内 (而不是 shared)

原因: Dubbo tri:// 是 TCP 二进制协议,K8s Ingress / Service 无法做基于 HTTP Host/Path 的智能路由。

把 gateway 放在组内,可以让组内服务间用短 DNS 名调用:

# 组内 gateway 的环境变量 (相对原 compose 几乎无改动)
DUBBO_USER_SERVICE_URL: tri://userservice:20000       # 同 ns 短名
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 只需把服务名换成 K8s 短 DNS 名。

4.2.2 Postgres / Redis 走外部托管

K8s 内只创建 ExternalName Service 占位:

# topfans-shared/postgres-external.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: topfans-shared
spec:
  type: ExternalName
  externalName: rm-xxxxxx.mysql.rds.aliyuncs.com  # 阿里云 RDS endpoint

好处:

  • K8s 不需要管 StatefulSet / PVC / 备份 / 主从切换(全部交给云服务)
  • 所有组共享同一份数据库,通过 group_id 在 schema 层面区分
  • 后续压力大了,云上一键升配即可,无需迁移数据

所有组数据如何隔离: 在现有表上加 group_id 字段,应用层做行级过滤。这是应用层改动,不在本次 K8s 迁移范围,但需要在设计文档里记录。

4.2.3 ResourceQuota + LimitRange 双层防护

# 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 镜像构建与推送策略

两种选择:

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

推荐方式 1,原因:

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

需要在 Dockerfile.services 增加 base 镜像 tag 参数(目前是 FROM --platform=linux/amd64 alpine:3.19,已 OK),在 CI 脚本里改 tag 即可。

4.2.5 健康检查修正

docker/Dockerfile.services 中存在 HEALTHCHECK 端口错配 bug:

服务 应用监听 Dockerfile HEALTHCHECK 用 状态
galleryservice 20001 21001 错的,会一直 fail
socialservice 20002 21002 同上
activityservice 20004 21004 同上
taskservice 20006 21006 同上
starbookservice 20005 21005 同上
aichatservice 20008 21008 同上

其他服务端口配置正确: gateway 8080、userservice 20000、assetservice 20003、lasercompositor 7002。

K8s 迁移时,改用 livenessProbe / readinessProbe 显式配置,顺便修复这个 bug

4.2.6 Secrets 管理

# topfans-group-a/gateway-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: gateway-secrets
  namespace: topfans-group-a
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: ...

生产建议: 配合 External Secrets Operator 或阿里云 KMS,不要把明文 secret 提交到 git。本期先用原生 Secret 加密,后续接 secret manager。

4.3 数据层多租户设计 (应用层改动,不在本次范围)

本次 K8s 迁移只解决编排层。多组数据隔离需要在应用层做配套改动,简要列出:

加 group_id 字段 中间件透传 group_id
users JWT 携带 group_id
galleries / assets / stars Dubbo attachment 透传
评论/点赞/收藏 同上

建议在另一份专门的设计文档中详细设计,本文档不展开


五、目录结构

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: 每组一份
│       ├── Chart.yaml
│       ├── values.yaml                             默认值 + 文档
│       ├── values-group-a.yaml                     实际组 A 配置示例
│       ├── values-group-b.yaml                     实际组 B 配置示例
│       └── 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/
│
└── ingress/
    └── ingress.yaml                                集群级 Ingress(可放 helm 也可 kubectl apply)

原则:

  • docker/ 目录保留,继续支撑本地开发 (docker-compose.local.yml)
  • k8s/ 是新增的部署维度,与 docker/ 并存
  • 未来四个新服务不在本次实现,只留 .gitkeep 占位 + README 说明
  • 镜像构建继续走 docker/Dockerfile.services (多阶段),不重复造轮子

六、迁移计划 (建议分 4 步走)

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 一行)

Step 2: 灰度切换 (本期可一并做)

  • 把现有 .env.prod 的所有密钥搬到 K8s Secret
  • 准备 DNS 切换预案 (group-a.api.example.com 先解析到 K8s,旧 VM 保留回滚)
  • 选 1 个非关键组(如内部测试组)切到 K8s,跑 1~2 周
  • 验证: 业务功能 / 性能 / 故障恢复

Step 3: 全量切换

  • 切所有组到 K8s
  • 停 VM 上的 docker-compose
  • 释放 VM 资源
  • 第一次: 取消注释 init-db.sql 中所有序列同步(从手工迁移数据开始时,见 CLAUDE.md 规范)

Step 4: 后续优化 (本期不做)

  • 接 External Secrets Operator
  • 接 Prometheus + Grafana
  • 接 Loki 日志收集
  • 接 ArgoCD / Flux 做 GitOps
  • 加 HPA (HPA + 自定义指标:KEDA)
  • 实施应用层多租户改造(group_id 透传)

七、风险与待定项

风险/待定 影响 缓解
现有 docker-compose 在生产跑,切换期间需双轨 资源消耗 测试组先切,观察 1~2 周再切正式组
Postgres 走外部 RDS,数据迁移 数据一致性 用阿里云 DTS 做实时同步,切换时切流量
Gateway 在 K8s 内,Dubbo 跨 ns 调用 服务发现 短 DNS 名 + 同 ns 部署,无问题
镜像构建走 CI 还是保留 deploy.sh 流程变更 推荐 CI,本期可先保留 deploy.sh 过渡
新服务具体技术栈未定 模板不完整 .gitkeep 占位,后续按需填
集群选型:ACK / TKE / 自建 k3s 成本/可控性 建议先 ACK 测试,稳定后定型
HPA 是否启用 (按 CPU / 自定义指标) 资源效率 本期不开,观察流量稳定后加
多集群 / 多 region 容灾 灾备 暂不考虑,后续规划

八、用户已确认的关键决策

  1. 按"组"隔离资源,每组 1 个或多个明星,可灵活组合
  2. 配置格式: Helm Chart
  3. 数据库: 外部托管 (RDS/ElastiCache)
  4. Gateway 放在每组 namespace 内 (而非 shared)
  5. 新服务 (admin / review / ai-image-gen / ai-chat) 放在 topfans-shared
  6. 保留 docker/ 目录,继续支撑本地开发

九、不在本次范围

  • 新服务 (admin / review / ai-image-gen / ai-chat) 的实现
  • 应用层多租户改造 (group_id 字段添加与透传)
  • CI/CD 流水线改造 (CI 自动构建镜像)
  • 监控/日志/告警接入
  • 灾备多集群
  • K8s 内的 StatefulSet (Postgres/Redis 用云服务,不自己跑)

十、Spec Review Refinements

本文档经 spec-document-reviewer 审查通过 (Status: Approved)。以下为审查中识别的可执行细化项,留待 writing-plans 阶段处理:

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 的歧义

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>

实施计划中需选其一,并写明 deploy.sh 的去留:

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

10.3 Secret 落盘策略

来自 4.2.6 的潜在泄漏

values-prod.yaml 含明文密钥会污染 git。实施计划中需:

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

10.4 数据库迁移时序

来自 CLAUDE.md PostgreSQL 序列同步规则 + 第 6 节 Step 3

若选择"从 VM 迁数据到 RDS"路径,需明确:

  • init-db.sqlsetval(...) 同步操作放在流量切换之前还是之后
  • 推荐: 切换以 Job 形式跑完,验证 pg_sequences 全部 is_healthy=true 后再切流量
  • 详见 CLAUDE.md 的强制规范

10.5 HEALTHCHECK 修复范围

来自 4.2.5 的歧义

K8s 用 Helm chart 里的 livenessProbe / readinessProbe,不再依赖 Dockerfile HEALTHCHECK。两种选择:

  • A. 只改 K8s 探针,不动 Dockerfile: 保持向后兼容,本地 docker-compose 仍可用
  • B. 同时修 Dockerfile: 顺手修 bug,让 compose 也能看到正确健康状态

推荐 A — 本次任务专注 K8s,Dockerfile 修复单独提一个 issue。

10.6 跨 namespace Dubbo 调用模式 (为新服务预留)

来自审查中识别 — 当前 spec 未明确

当未来 admin / review 部署在 topfans-shared 时,它们需要调用各组的数据服务(如 review 审核 group-a 的 gallery):

# 未来 review 服务的环境变量 (在 topfans-shared namespace)
# 需要按 group 动态获取或路由
DUBBO_GALLERY_SERVICE_URL: tri://galleryservice.topfans-group-a.svc.cluster.local:20001
DUBBO_GALLERY_SERVICE_URL_GROUP_B: tri://galleryservice.topfans-group-b.svc.cluster.local:20001

当前不实现,但 topfans-shared 的 chart 结构需考虑: 共享服务将来需要某种方式(配置中心/服务发现)拿到各组的服务地址。本期不展开,记入"未来工作"。

10.7 评审通过的完整决策

# 决策点 选择
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/ 目录 保留,继续支撑本地开发