四、控制平面设计:API Server设计、资源抽象(CRD)、控制器模式(Operator)
好,咱们进入控制平面的核心部分。说实话,很多PaaS平台做不好,问题就出在控制平面上——要么API设计得乱七八糟,要么资源抽象不够灵活,要么控制器写成了“面条代码”。今天我就把这三大块掰开揉碎了讲清楚。
4.1 API Server设计:平台的“神经系统”
API Server是整个控制平面的入口。你想想看,所有客户端、CLI工具、Web控制台,甚至其他组件,都要通过它来操作资源。所以它的设计质量,直接决定了平台的可用性和扩展性。
核心设计原则:
- 声明式API:用户告诉系统“我要什么”,而不是“怎么做”。比如用户提交一个“创建MySQL实例”的请求,而不是“先装MySQL,再配参数,再启动”。
- 幂等性:同一个请求发多次,结果是一样的。我见过一个平台因为没处理好幂等性,用户重试时创建了两个数据库实例,那叫一个尴尬。
- 版本化:API版本要清晰,比如
/api/v1/databases。别搞什么/api/databases然后偷偷改字段,前端会骂娘的。
我个人习惯的做法:
API Server采用分层架构:路由层 → 认证鉴权层 → 校验层 → 业务逻辑层 → 存储层。每一层职责单一,出了问题也好排查。
来看一个简单的API设计示例:
// 创建数据库实例的API定义
POST /api/v1/databases
{
"apiVersion": "v1",
"kind": "Database",
"metadata": {
"name": "my-mysql",
"namespace": "production"
},
"spec": {
"engine": "mysql",
"version": "8.0",
"storage": "100Gi",
"replicas": 3,
"backup": {
"enabled": true,
"schedule": "0 2 * * *"
}
}
}
嗯,这里要注意:不要把所有字段都放在spec里。metadata放标识信息,spec放期望状态,status放实际状态。这是Kubernetes教我们的好习惯。
4.2 资源抽象(CRD):把数据库变成“一等公民”
资源抽象是PaaS平台的灵魂。说白了,就是把数据库、缓存、消息队列这些中间件,抽象成平台内部的“资源对象”。
为什么需要CRD?
- 标准化:所有数据库实例都长一个样,操作方式统一
- 可扩展:新增一种数据库,只需要定义新的CRD,不用改核心代码
- 自动化:配合控制器,实现“声明即所得”
我在项目中遇到过一个问题:一开始我们把所有数据库配置都写死在代码里,结果每次新增数据库类型都要改核心逻辑,测试回归一遍,累得半死。后来改成CRD方式,新增一种数据库只需要写一个CRD定义和一个控制器,爽多了。
来看一个MySQL数据库的CRD定义示例:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.paas.example.com
spec:
group: paas.example.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
engine:
type: string
enum: ["mysql", "postgresql", "redis"]
version:
type: string
storage:
type: string
pattern: "^[0-9]+(Gi|Ti)$"
replicas:
type: integer
minimum: 1
maximum: 10
backup:
type: object
properties:
enabled:
type: boolean
schedule:
type: string
scope: Namespaced
names:
plural: databases
singular: database
kind: Database
shortNames:
- db
避坑指南:我曾经把CRD的校验规则写得太松,结果用户传了个负数副本数,系统直接崩溃。后来我养成了习惯:所有字段都要做校验,尤其是数值和枚举类型。OpenAPI Schema的校验能力很强,别浪费了。
4.3 控制器模式(Operator):让资源“活”起来
有了CRD定义,资源还只是个“死”的YAML文件。控制器的作用,就是让这些资源“活”起来——监听资源变化,然后执行对应的操作。
控制器的核心逻辑:
- 监听:Watch API Server上的资源变化
- 比对:对比当前状态和期望状态
- 调和:执行操作,让当前状态趋近期望状态
- 更新:更新资源的状态字段
说白了,就是一个无限循环的“观察-决策-执行”过程。你想想看,用户提交了一个“创建3副本MySQL”的请求,控制器看到后,就去检查当前有没有3个Pod在跑,没有就创建,多了就删除,一直保持3个。
来看一个简化版的控制器代码:
// 数据库控制器的调和循环
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 1. 获取资源对象
db := &Database{}
if err := r.Get(ctx, req.NamespacedName, db); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 2. 检查当前状态
currentReplicas := r.getCurrentReplicas(db)
desiredReplicas := db.Spec.Replicas
// 3. 执行调和操作
if currentReplicas < desiredReplicas {
// 创建新的Pod
r.createPod(db)
} else if currentReplicas > desiredReplicas {
// 删除多余的Pod
r.deletePod(db)
}
// 4. 更新状态
db.Status.ReadyReplicas = currentReplicas
r.Status().Update(ctx, db)
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
注意:控制器一定要处理好“重试”和“错误”。我曾经写过一个控制器,遇到网络错误就直接返回错误,结果导致无限重试,把API Server打挂了。正确的做法是:区分临时错误和永久错误,临时错误用指数退避重试,永久错误直接记录到资源状态里。
4.4 三者如何协同工作?
API Server、CRD、控制器这三者,就像人体的神经系统、骨骼和肌肉:
| 组件 | 类比 | 职责 |
|---|---|---|
| API Server | 神经系统 | 接收指令、传递信息、控制访问 |
| CRD | 骨骼 | 定义资源结构、提供标准化框架 |
| 控制器 | 肌肉 | 执行动作、维持状态、处理异常 |
用户通过API Server提交一个CRD资源(比如Database),API Server校验后存入etcd。控制器监听到新资源,开始执行调和循环,创建对应的Pod、Service、PVC等底层资源。整个过程用户只需要声明“我要什么”,剩下的交给控制器。
我的建议:刚开始做PaaS平台时,别想着一步到位。先实现一个最简单的控制器——只支持一种数据库,只处理创建和删除。跑通了再慢慢加功能。我见过太多团队一上来就想做“全功能Operator”,结果半年过去了还在改Bug。
嗯,控制平面设计这块,说白了就是三件事:API Server定规矩,CRD定结构,控制器定行为。把这三件事做好了,PaaS平台的基础就稳了。下一章咱们聊聊数据平面的设计,那又是另一番天地了。