前言
上一篇我把通用后台的"完全体"架构画了出来:Kong、Go-zero、Kafka、MongoDB、ES、Python、Rust、Backstage……一套下来技术栈二十多个组件,看着很爽,部署起来就是另一回事了——光是把这些东西在云上拉起来,每个月账单先把我劝退。
这篇是它的"冷启动版本",也就是真正落地一个 MVP 时我会怎么砍。
砍的原则只有一条:MVP 阶段,每多一个需要单独部署、单独运维、单独花钱的组件,都要被审判一次。能不上的坚决不上,能合并的坚决合并。
最终结论很激进:整个系统只在 Sealos 上跑 3 个应用,外加一个白嫖 Vercel 的管理后台。
技术栈全景(MVP 版)
| 分类 | 完全体方案 | MVP 选型 | 备注 |
|---|---|---|---|
| 部署平台 | K8s 自建 | Sealos | 按量计费,托管中间件 |
| 网关 | Kong | Gin 中间件顶上 | |
| 开发语言 | Go | Go | 不变 |
| Web 框架 | Gin | Gin | 不变 |
| RPC | Go-zero | 单体,函数调用即"RPC" | |
| ORM | GORM | GORM | 不变 |
| 后台管理 | Refine | Refine | 发布到 Vercel |
| CI/CD | (未提) | GitHub Actions | 测试 + 构建镜像 |
| 镜像仓库 | (未提) | GHCR 私有仓库 | 免费私有,跟代码同源 |
| 异步任务 | Kafka + Asynq | Asynq | 一个就够 |
| 关系型数据库 | PostgreSQL | PostgreSQL | 不变 |
| 连接池 | PgBouncer | PgBouncer | 跟 PG 打包成一个应用 |
| 缓存 / 锁 / 队列 | Redis | Redis | 一鱼三吃 |
| NoSQL | MongoDB | PG 的 JSONB 顶上 | |
| 全文搜索 | Elasticsearch | PG 全文检索顶上 | |
| 配置中心 | Etcd | 环境变量 + YAML | |
| 可观测性 | OTel+Jaeger+Prometheus+Grafana | Sealos 自带监控 + Zap 日志 | |
| 开发者中心 | Backstage | 一个 README 解决一切 | |
| 多语言扩展 | Python + Rust | 等真有 AI 需求再说 |
3 个 Sealos 应用 + 1 个 Vercel 部署,就这。
整体架构
整张图就这么大。
注意几个关键决策:
- 管理后台不占 Sealos 名额。Refine 本质是个 React 静态站点,扔 Vercel 上免费托管,构建产物走 CDN,它只是通过 REST 调用 Sealos 上那个 Go 应用而已。
- Asynq Worker 不单独部署。它和 Gin API 跑在同一个 Go 二进制里,启动时多开一个 goroutine 消费队列。这样"应用端"始终是 1 个应用。
- PgBouncer 和 PostgreSQL 打包成一个应用。Sealos 的数据库托管模板通常自带连接池,或者用一个 Pod 跑两个容器(PG + PgBouncer sidecar),对外只暴露 PgBouncer 的端口。
- 代码到线上走 GHCR 中转。Go 应用由 GitHub Actions 构建成镜像、推到私有 GHCR,Sealos 凭 imagePullSecret 拉取部署;Refine 那条线交给 Vercel 全自动。详见后文流水线一节。
为什么是 Sealos
选 Sealos 的核心理由,就俩字:省钱。
更准确地说,是省"心智成本"和"现金成本":
- 按量计费。MVP 阶段没流量,半夜没人访问的时候你不希望还在为闲置的虚拟机付费。Sealos 按实际用量结算,跑得少花得少。
- 托管中间件开箱即用。PostgreSQL、Redis 在 Sealos 上是模板,点几下就起来了,不用自己装、自己调参、自己搞高可用脚本。MVP 阶段我没空当 DBA。
- 本质是 K8s,但不用懂 K8s。这点很重要——等业务长大要回到完全体架构时,底层还是 Kubernetes,迁移路径平滑,不会推倒重来。
一句话:Sealos 让你"先用 PaaS 的姿势开发,将来有 K8s 的退路"。
应用端:Go + Gin + GORM + Asynq 的单体
完全体里那条 Kong → Gin → Go-zero RPC 的链路,在 MVP 里被压扁成一个单体进程。
砍掉 Kong,谁来鉴权限流?
Gin 中间件。完全体里把鉴权、限流、CORS 放网关是为了"关注点分离",但那是服务多了之后的奢侈品。MVP 就一个应用,多写几个中间件,比维护一个 Kong 集群划算太多了。限流直接用 Redis 做计数器(INCR + EXPIRE),几十行代码搞定。
砍掉 Go-zero RPC,业务怎么拆?
不拆。MVP 阶段服务拆分是负优化——你还没搞清楚业务边界在哪,拆出来的服务大概率会被推翻重画。单体里用清晰的 Handler / Service / Repository 分层,把边界划在包(package)层面而不是进程层面。将来要拆,把某个 Service 包提出来套上 Go-zero 就是了,业务代码几乎不动。
Asynq Worker 为什么塞进同一个进程?
为了守住"3 个应用"这条线。生产环境里 Worker 和 API 分开部署是对的(互不影响、独立扩缩容),但 MVP 阶段:
func main() {
// 同一个二进制,两条腿走路
go startAsynqWorker(redisOpt) // 后台消费
startGinServer(addr) // 前台 HTTP
}等哪天异步任务把 CPU 吃满影响到 API 响应了,再把 startAsynqWorker 拎出来单独部署——那时候说明你有这个"幸福的烦恼"了,恭喜。
存储层:一个 PG 顶三个组件
完全体里 PostgreSQL、MongoDB、Elasticsearch 各司其职。MVP 里,PostgreSQL 一个人把这活全干了。
- 替代 MongoDB:检测结果、行为事件这类"Schema 灵活"的数据,丢进 PG 的
JSONB字段。配合 GIN 索引,查询性能在 MVP 的数据量下完全够用,还白送了事务能力。 - 替代 Elasticsearch:PG 自带的
tsvector+GIN全文索引,应付中小数据量的搜索绰绰有余。ES 那套分词、集群、JVM 调优,MVP 阶段碰都别碰。 - PgBouncer 依然保留:这是少数我不肯砍的东西。PG 的连接是重资源,哪怕 MVP,一个写得不好的循环里反复建连,也能把
max_connections打满。PgBouncer 用 Transaction 模式兜底,成本几乎为零。
⚠️ PgBouncer 的 Transaction 模式不支持会话级 SET 语句,用之前确认 GORM 没有依赖会话状态的行为(比如自定义的会话级参数),否则会踩坑。Redis:一鱼三吃
Redis 在 MVP 里身兼三职,这是它性价比最高的用法:
缓存、分布式锁、Asynq 的任务队列,全压在同一个 Redis 上。MVP 阶段数据量小、隔离需求低,没必要分多个实例。
唯一要留个心眼的:Asynq 的队列和你的业务缓存共享内存,如果将来任务积压严重,注意别把内存挤爆导致缓存被驱逐。真到那一步,再拆 Redis 实例不迟。
异步任务:只留 Asynq
完全体里我纠结过 Kafka 和 Asynq 的边界——事件驱动、多消费者、数据管道用 Kafka;延迟、重试、定时用 Asynq。
MVP 阶段这个纠结直接消失:Kafka 不上,全用 Asynq。
Kafka 的价值在于高吞吐、多消费者、数据回放——这些是日活百万级的需求。日活万级以下,那套运维成本(ZooKeeper/KRaft、分区、消费组、Rebalance)纯属自我折磨。
需要"事件驱动"的地方,用 Asynq 任务链模拟即可:任务 A 完成后,在 Worker 里直接 client.Enqueue(taskB)。够用。
管理后台:Refine 白嫖 Vercel
这是省钱的点睛之笔:管理后台一分钱不花,还不占 Sealos 应用名额。
完全体里我设计了独立的 Admin BFF,让运营后台和用户 API 走两条链路,避免运营的慢查询拖垮用户侧。MVP 阶段这个担忧暂时不存在——没那么多并发,也没那么复杂的报表。
所以直接让 Refine 复用应用端那套 REST 接口,通过 RBAC 中间件区分管理员权限即可。Refine 本身是纯前端 React CRUD 框架,vercel deploy 一把梭,免费层的构建额度和 CDN 流量对 MVP 完全够用。
等运营报表真的开始拖慢用户响应了,再在 Go 应用里分出一组 /admin 路由、走只读副本——那是增长期的事。发布流水线:GitHub Actions + 私有 GHCR
前面把"系统长什么样"讲清楚了,但还差一环:代码是怎么从 git push 变成线上运行的容器的。这一段补上完整的 CI/CD 流程。
整条流水线分两条线,左边 Go 应用,右边 Refine 后台:
为什么镜像要放 GHCR,而不是 Docker Hub?
三个理由,都跟 MVP 的"省"和"稳"对齐:
- 私有免费。GHCR 对私有镜像不收费(在 GitHub 免费额度内),而 Docker Hub 的私有仓库数量有限、还有匿名拉取限流。MVP 阶段镜像里多少藏着点不想公开的东西(内部依赖、配置模板),私有是默认选项。
- 跟代码同源。镜像和源码在同一个 GitHub 账户/组织下,权限用一套 GitHub Token 管理,不用再去 Docker Hub 单独维护一套凭据。
- Actions 原生集成。在 GitHub Actions 里推 GHCR,认证直接用工作流自带的
GITHUB_TOKEN,不用手动配 secret,这是体验上最丝滑的一点。
Go 应用的 workflow
一个最小可用的 .github/workflows/deploy.yml 长这样(省略了细节,重点看流程):
name: build-and-push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # 推 GHCR 必须
steps:
- uses: actions/checkout@v4
# 测试先行,挂了就不浪费构建时间
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- run: go vet ./... && go test ./...
# 用工作流自带 token 登录 GHCR,零配置
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 多阶段构建 + 用 commit sha 打标签
- uses: docker/build-push-action@v6
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest几个实践要点:
- 用 commit SHA 打标签,而不是只用
latest。latest是飘的,出了问题你都不知道线上跑的到底是哪次提交。SHA 标签让每次部署可追溯、可回滚。latest留着给"图省事手动拉取"用。 - 测试不过就别构建。
go test放在docker build前面,省 Actions 的构建分钟数——免费额度也是钱。 - 多阶段 Dockerfile。Go 的优势就是能编译成静态二进制,最终镜像用
scratch或distroless,几十 MB,拉取快、攻击面小。
Sealos 怎么拉私有镜像
GHCR 是私有的,Sealos 默认拉不到,需要配一个 imagePullSecret:
操作上就是在 Sealos 集群里建一个 docker-registry 类型的 secret:
kubectl create secret docker-registry ghcr-cred \
--docker-server=ghcr.io \
--docker-username=<你的 GitHub 用户名> \
--docker-password=<一个 read:packages 权限的 PAT> \
--namespace=<你的 Sealos 命名空间>然后在应用的部署配置里引用这个 secret 即可。
⚠️ 这里的 PAT 要单独签发、只给 read:packages 这一个权限,别图省事用一个 full access 的 token。Sealos 这边只需要"拉镜像"的能力,给多了纯属给自己埋雷。触发更新:MVP 阶段别整太复杂
镜像推到 GHCR 之后,怎么让 Sealos 用上新镜像?按"折腾程度"从低到高有三档:
| 方式 | 折腾程度 | 适合阶段 |
|---|---|---|
| Sealos 控制台手动改镜像 tag、点重启 | ⭐ 最省事 | MVP 第一周,一天发不了几次 |
Actions 里加一步 kubectl set image 远程触发 | ⭐⭐ | 发布频繁起来之后 |
| 上 Argo CD / Flux 做 GitOps 自动同步 | ⭐⭐⭐ | 增长期,回到完全体时 |
MVP 阶段我建议直接用第一档或第二档。GitOps 那套(Argo CD)很香,但它本身又是一个要部署、要运维、要学习的组件——这跟本文"把成本压到地板"的主旨是冲突的。等服务多了、发布频繁了再上不迟。
一句话总结这条流水线:push 代码 → Actions 测试+构建 → 推私有 GHCR → Sealos 拉取滚动更新。Refine 那条线交给 Vercel 全自动,你只管 git push。配置与可观测性:能少则少
配置:Etcd 砍掉。静态配置走 YAML,敏感信息(DB 密码、JWT 密钥)走 Sealos 的环境变量 / Secret 注入。功能开关这种"动态配置",MVP 阶段重启一下服务也能接受,没必要为了热更新引入配置中心。
可观测性:OTel + Jaeger + Prometheus + Grafana 这套"三件套"全砍。MVP 阶段:
- 日志:Zap 结构化日志,输出到 stdout,Sealos 控制台直接看。
- 指标:用 Sealos 自带的应用监控面板(CPU / 内存 / 请求),够用。
- 链路追踪:单体应用,一次请求基本不跨进程,日志里带个
request_id串起来就行,不需要 Jaeger。
真正需要分布式追踪,是服务拆成一堆微服务之后的事。MVP 是个单体,追踪个啥。
典型场景走读
场景:用户记录一条任务
整个链路在一个进程内完成,没有跨服务调用,没有网关跳转。延迟低,排查问题就看一份日志。这就是单体在 MVP 阶段的爽点。
它怎么长大?
MVP 不是终点,是起点。这套最简架构留好了回到完全体的路:
| 触发信号 | 加什么 | 从哪长出来 |
|---|---|---|
| 异步任务拖慢 API | Asynq Worker 独立部署 | 把同进程的 goroutine 拎成第 4 个应用 |
| 运营查询拖慢用户侧 | Admin BFF / 只读副本 | Gin 里分出 /admin 路由组 |
| 多消费者 / 数据回放需求 | Kafka | 替换 Asynq 的"伪事件驱动"部分 |
| 全文搜索性能不够 | Elasticsearch | 从 PG 的 tsvector 迁出去 |
| 服务边界清晰且团队变大 | Go-zero RPC 拆服务 | 把 Service 包提出来 |
| 发布频繁 / 想要自动同步 | Argo CD / Flux(GitOps) | 在现有 GHCR 镜像基础上接 GitOps |
| 服务数量 > 10 | Kong + Backstage + OTel 三件套 | 该上的都上 |
每一步都是"被业务推着走",而不是"提前设计好"。
总结
MVP 架构的核心思路,和完全体正好相反:
完全体追求"边界清晰",MVP 追求"成本最低"。
具体落到这套方案上:
- 3 个 Sealos 应用(Go App / PG+PgBouncer / Redis)+ 1 个免费 Vercel(Refine),把现金成本压到地板。
- 发布流水线全免费:GitHub Actions 测试构建、私有 GHCR 存镜像、Sealos 拉取部署,
git push即上线,一条龙不花钱。 - 能合并的全合并:Worker 进主进程、PgBouncer 跟 PG 打包、Redis 一鱼三吃、PG 顶替 Mongo 和 ES。
- 能砍的全砍:Kong、Go-zero、Kafka、Etcd、可观测性三件套、Backstage、多语言扩展——这些都是"长大以后"的事。
- 但留好退路:底层是 Sealos(K8s),分层是
Handler/Service/Repo,镜像和源码同源在 GitHub,将来要回到完全体(甚至上 GitOps),是"长出来"而不是"推倒重来"。
完全体那篇讲的是"架构刚好够用、且能随业务生长"。这篇是它的另一半——在业务还没生长之前,先别让架构把你拖死。
先活下来,再谈优雅。