先把项目当成一座自动化公寓
第一次打开这个项目,很容易被 CRD、StatefulSet、NetworkPolicy、Gateway API 和一堆 Go 代码吓住。其实它做的事情可以先用一句话概括:
用户提交一张名为
SubPool的“入住申请”,Operator 自动建出一套隔离的 CPA 运行环境,并持续保证它与申请内容一致。
可以把 Kubernetes 集群想成一座公寓楼:
| 项目概念 | 公寓类比 | 实际职责 |
|---|---|---|
SubPool | 入住申请单 | 描述一个号池想要的镜像、资源、存储和访问方式 |
| Operator | 物业管家 | 读取申请,创建、修正、暂停或删除资源 |
| Namespace | 独立房间 | 隔离不同号池的资源 |
| StatefulSet | 固定工位 | 运行 CPA 与 cpa-manager 两个容器 |
| PVC | 带锁储物柜 | 保存账号配置、日志和管理数据 |
| Service | 稳定房间号码 | 为会变化的 Pod 提供稳定访问地址 |
| NetworkPolicy | 门禁规则 | 限制谁能进入、Pod 能去哪里 |
| Shared Mihomo | 共用出站大厅 | 为所有号池提供代理出口 |
| Envoy Gateway | 共用前台 | 在 Gateway 模式下接收外部 HTTP 流量 |
这个类比不是 Kubernetes 的正式定义,但它能帮我们抓住项目最重要的设计:一个号池不是一个孤零零的 Pod,而是一组彼此配合、由 Operator 统一管理的对象。
一张图看懂完整链路
| |
这里有两条不同方向的数据流:
- 入站流量:用户访问 CPA。开发时通常通过
kubectl port-forward;生产的 Gateway 模式通过 Envoy Gateway 和HTTPRoute。 - 出站流量:CPA 访问外部服务时,只允许走 Shared Mihomo Gateway,再由 Mihomo 选择健康的上游代理。
不要把二者混为一谈。Envoy 解决“外面怎么进来”,Mihomo 解决“里面怎么出去”。
三层结构:声明、控制和数据
声明层:SubPool CR
用户不直接创建十几种底层资源,而是提交一个 SubPool:
| |
这是项目对使用者提供的业务 API。使用者只表达“我要什么”,不必自己拼装 Namespace、PVC、Service 和 NetworkPolicy。
控制层:Operator
Operator 是一个运行在 subpool-system Namespace 中的 Deployment。它监听 SubPool 的变化,然后不断执行控制循环:
| |
这套循环叫 reconcile。它不是只执行一次的安装脚本;只要资源被误删或配置发生漂移,Operator 还会尝试把它恢复到期望状态。
数据层:CPA、cpa-manager 与网关
每个号池的 CPA 和 cpa-manager 运行在同一个 Pod 中,共享网络和 RWO 数据卷。Shared Mihomo 与 Envoy Gateway 是集群级共享基础设施,不归任何单个 SubPool 所有。
这个边界很关键:删除一个 SubPool 可以删除它自己的 Namespace,但不能顺手删除共享网关,否则一个号池的生命周期操作会影响所有号池。
创建一个 SubPool 后会发生什么
以 demo-pool 为例,Operator 会派生 Namespace:
| |
随后创建或维护这些资源:
| 资源 | 用途 |
|---|---|
| Namespace | 形成号池隔离边界 |
| ServiceAccount | 给运行时 Pod 提供身份,同时禁止自动挂载 token |
| Secret | 保存随机生成的 CPA 和管理端凭据 |
| ConfigMap | 保存幂等初始化脚本 |
| StatefulSet | 在一个 Pod 中运行 CPA 与 cpa-manager |
| PVC | 保存两套程序的持久数据 |
| 两个 Service | 分别提供 CPA 和 cpa-manager 的稳定入口 |
| ResourceQuota | 限制整个号池 Namespace 的资源上限 |
| PodDisruptionBudget | 限制自愿中断期间的可用副本数 |
| NetworkPolicy | 默认拒绝,并只开放必要流量 |
| HTTPRoute | 仅 Gateway 模式创建,将域名流量转给 CPA |
Operator 还会把检查结果写入 SubPool.status.conditions,让使用者知道究竟卡在工作负载、代理网关还是外部入口。
两种访问模式
Internal:适合开发和管理
Internal 只创建 ClusterIP Service,不创建公网入口。访问方式是:
| |
它的优点是简单、安全、几乎不依赖集群外部设施。缺点是终端一断开,转发就停止,因此不适合作为生产入口。
Gateway:适合统一的外部入口
Gateway 模式会额外创建 HTTPRoute,使用 <pool-id>.<baseDomain> 这样的域名连接 Shared Envoy Gateway。
Operator 不会为每个号池创建独立 LoadBalancer、DNS 记录或 TLS 证书。共享入口只维护一套,单个号池只维护自己的路由。这既节约成本,也让证书、域名和入口安全策略集中管理。
Ready 到底表示什么
本项目的 Ready=True 表示“空号池平台已经准备好”,具体包括:
- StatefulSet 至少有一个 Ready replica;
- CPA 与 cpa-manager Service 存在;
- Shared Mihomo Service 有 Ready Endpoint;
- 当前暴露模式的前提已满足;
- Operator 已观察到当前这一版 spec。
它不表示以下业务事实:
- 已导入账号;
- OAuth 一定有效;
- Mihomo 的每个上游节点都可用;
- 真实模型请求已经成功;
- 每个请求都会切换出口 IP。
基础设施 Ready 和业务 Ready 是两个层次。把边界写清楚,比做一个“看起来什么都检查了、实际上没人知道含义”的健康状态更可靠。
生命周期不是只有创建
一个 SubPool 主要有四种操作:
| 操作 | 发生什么 | 数据是否保留 |
|---|---|---|
| 创建 | 建 Namespace 和全部运行资源 | 新建数据 |
| 更新 | 镜像和计算资源在策略范围内收敛 | 保留 |
| 暂停 | StatefulSet 缩容为 0,删除 HTTPRoute | 保留 Namespace、PVC 和 Secret |
| 删除 | 删除专属 Namespace,等待清理完成 | 永久删除 |
暂停和删除的区别非常大。只是临时停用时应设置 spec.suspend: true,不要删除 CR。
推荐阅读顺序
这套笔记按依赖关系排列:
- 先读 Kubernetes 对象、Namespace、Label 和 Selector,建立“对象如何互相找到”的直觉。
- 再读 Pod、多容器、StatefulSet、PVC、Service,理解号池的运行实体。
- 接着读 ConfigMap、Secret、健康探针、资源管理和 NetworkPolicy,理解稳定性与安全基线。
- 然后读 Gateway API、RBAC 和 leader election,理解共享入口及控制面高可用。
- 最后读 CRD、reconcile、finalizer 与 Kustomize,把所有积木拼回完整 Operator。
读代码时从哪里下手
项目中的关键入口如下:
| 路径 | 建议关注点 |
|---|---|
examples/subpool-internal.yaml | 用户最小输入是什么 |
api/v1alpha1/subpool_types.go | CRD 的 Go 类型和校验规则 |
controllers/subpool_controller.go | reconcile 入口、watch 和 finalizer |
controllers/subpool_reconcile.go | 创建资源的顺序与阻塞条件 |
internal/subpool/resources.go | 一个 SubPool 会生成哪些对象 |
internal/subpool/workloads.go | StatefulSet、容器、探针和存储细节 |
internal/subpool/network.go | NetworkPolicy 与 HTTPRoute |
config/default/operator-config.yaml | 全局平台配置和资源包络 |
deploy/gateway/ | Shared Mihomo 与 Envoy Gateway |
学习 Kubernetes 最舒服的方式不是背完所有 Kind,而是每次追踪一个具体问题:这个对象是谁创建的、被谁选择、依赖谁、坏了看哪里、删除时谁负责收尾。这个项目正好把这些问题串成了一条完整链路。