K8s 运维系列 | 第 23 天:扩展开发——CRD 与 Operator 模式

在掌握了 Deployment、StatefulSet、Service、Ingress、Helm 等内置对象之后,很多运维工程师会问一个更深层的问题:当 Kubernetes 内置的能力不够用时,我们还能做什么?答案就是 Kubernetes 的核心扩展机制——自定义资源定义(CRD,CustomResourceDefinition)与基于它的 Operator 模式。CRD 让你像在数据库里建表一样,向集群注册全新的资源类型;而 Operator 则是围绕这些自定义资源编写的自动化控制器,把领域知识和运维经验”固化”成可被声明式管理的代码。理解并掌握 CRD 与 Operator,是运维工程师从”会用 K8s”迈向”能扩展 K8s”的关键一跃。

K8s 运维 第23天

核心概念

什么是 CRD

CRD 本身也是一种 Kubernetes 资源,它通过 Kubernetes 的 API Server 注册,允许你定义一个全新的、结构化的资源类型。一旦 CRD 被创建,你就可以像操作原生对象(比如 Pod)一样,用 kubectl create / get / apply 去管理你的自定义资源。CRD 的核心价值在于:

  • 把复杂的应用配置”资源化”,纳入声明式模型;
  • 复用 Kubernetes 的所有原生能力:RBAC、Watch 机制、调度、控制器框架;
  • 让运维经验沉淀为可复用、可审计、可版本化的资源定义。

CRD 通过 apiextensions.k8s.io/v1 API 组进行管理,是 Kubernetes 官方推荐的扩展方式(相比旧的 Aggregated API Server,CRD 维护成本更低)。

什么是 Operator

Operator 是一种设计模式,由 Red Hat 提出,其核心思想是:把运维知识编码进控制器,使其能自动执行重复的运维任务。一个标准的 Operator 通常包含两部分:CRD(定义资源结构)+ Controller(控制循环,实现期望状态与实际状态的调和,即 Reconcile)。当某个有状态应用(如数据库、消息队列、AI 训练任务)需要备份、扩缩容、故障自愈时,一个 Operator 就能把这些流程自动化,彻底告别手动敲命令。

API 版本与资源命名

自定义资源有一个关键约定:CRD 的 kind 必须使用复数、小写、点分的格式,例如 mysqlclusters.example.com。其中 mysqlclusters 是资源的复数短名,example.com 是所属的 Group。资源名采用”域名反转”格式是为了避免命名冲突,这也是云原生社区的标准实践。

实战步骤

下面我们用 kubebuilder 脚手架,从零创建一个管理”MySQL 集群”的 Operator,演示 CRD 与 Operator 的完整开发流程。

步骤一:安装 kubebuilder

kubebuilder 是 Kubernetes 官方推荐的 Operator 开发框架,它基于 Go 语言,内置了 controller-runtime。先安装它:

# 安装 kubebuilder(以 v3 为例)
go install sigs.k8s.io/kubebuilder/v3@latest

# 验证安装
kubebuilder version

# 下载 kustomize(用于构建部署清单)
go install sigs.k8s.io/kustomize/kustomize/v4@latest

步骤二:初始化项目骨架

接下来初始化项目,并生成第一个 API 与对应的控制器:

# 初始化项目
kubebuilder init --domain example.com --repo github.com/myorg/mysql-operator

# 创建 CRD API 与控制器:GVR(Group Version Kind)
kubebuilder create api --group database --version v1alpha1 --kind MySQLCluster --controller --resource

执行后,项目目录会生成 CRD 的 Go 结构体定义、Reconcile 控制器逻辑、以及 RBAC 权限文件。我们修改生成的 CRD 定义文件 api/v1alpha1/mysqlcluster_types.go,定义期望字段:

// MySQLClusterSpec 定义 MySQL 集群的期望状态
type MySQLClusterSpec struct {
    Replicas     int32  `json:"replicas,omitempty"`     // 期望副本数
    Image        string `json:"image,omitempty"`        // MySQL 镜像
    StorageSize  string `json:"storageSize,omitempty"`  // 数据盘大小
}

步骤三:定义并部署 CRD

kubebuilder 会根据结构体自动生成 CRD 的 YAML。下面是生成的 mysqlclusters.example.com CRD 清单的核心结构(简化版,便于理解):

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: mysqlclusters.example.com
spec:
  group: database.example.com
  scope: Namespaced
  names:
    plural: mysqlclusters
    singular: mysqlcluster
    kind: MySQLCluster
    shortNames: [mysql]
  versions:
  - name: v1alpha1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              replicas:
                type: integer
                minimum: 1
                maximum: 8
              image:
                type: string

注意这里开启了 OpenAPI Schema 校验(openAPIV3Schema),Kubernetes 会自动校验用户提交的资源是否合法。部署 CRD 后,集群中便注册了这个新资源类型:

# 构建并部署 CRD 与 RBAC
kustomize build config/crd | kubectl apply -f -

# 查看 CRD 是否注册成功
kubectl get crd mysqlclusters.example.com

# 查看新资源类型
kubectl get mysqlclusters --all-namespaces

步骤四:提交自定义资源实例

CRD 注册好后,就可以像操作 Pod 一样创建 MySQLCluster 实例:

apiVersion: database.example.com/v1alpha1
kind: MySQLCluster
metadata:
  name: web-mysql
  namespace: production
spec:
  replicas: 3
  image: mysql:8.0
  storageSize: 100Gi

应用这份清单,Operator 的 Controller 就会收到 Watch 事件,开始执行调和逻辑。

步骤五:实现 Reconcile 控制循环

Operator 的精髓在 Reconcile 方法:它负责把”实际状态”调到”期望状态”。下面是控制器中创建 StatefulSet 的核心伪代码:

func (r *MySQLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    cluster := &databasev1alpha1.MySQLCluster{}
    if err := r.Get(ctx, req.NamespacedName, cluster); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }
    // 根据 spec.Replicas 创建/更新 StatefulSet
    desired := cluster.Spec.Replicas
    sts, err := r.buildStatefulSet(cluster)
    if err != nil {
        return ctrl.Result{}, err
    }
    if err := controllerutil.SetControllerReference(cluster, sts, r.Scheme); err != nil {
        return ctrl.Result{}, err
    }
    if err := r.Client.Patch(ctx, sts, client.Apply,
        client.FieldOwner("mysql-operator")); err != nil {
        return ctrl.Result{}, err
    }
    _ = desired
    return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

Reconcile 返回的 RequeueAfter 让控制器周期性重新检查,从而形成一个持续维护的闭环。

常见问题

Q1:CRD 与 Aggregated API Server 怎么选?
绝大多数场景优先选 CRD,开发部署成本远低于独立的 API Server。只有当你的扩展需要深度定制 API Server 行为(如复杂的存储后端、跨集群路由)时,才考虑 Aggregated API Server。

Q2:为什么自定义资源名要用域名反转?
为了避免不同厂商、不同项目的资源名冲突。mysqlclusters.example.com 里的 example.com 就是命名空间化的 Group,保证全局唯一。

Q3:删除 Operator 后,自定义资源会怎样?
删除 Controller 不影响已经创建的资源实例,它们会保留在 etcd 中但失去自动化能力。注意:不要随意删除 CRD 本身,否则会一并删除所有该类型的资源实例。

Q4:如何调试 Operator?
可用 kubectl describe mysqlcluster web-mysql 查看事件(Events)与状态,结合 Operator 容器的日志、以及 kubectl logs 查看控制器运行时的错误。

总结

CRD 与 Operator 是 Kubernetes 可扩展性的两大基石:CRD 让你向集群注册全新的资源类型,Operator 则把运维经验编码成自动化的控制循环。通过 kubebuilder 这样的脚手架,开发者可以快速构建出管理数据库、消息队列乃至 AI 任务的工作流自动化能力,真正做到”把运维变成代码(Ops as Code)”。掌握这一层,意味着你不仅能使用 Kubernetes,更能按需扩展 Kubernetes,让平台为你所用。

下期预告: 第 24 天我们将进入《K8s 运维系列 | 第 24 天:性能调优——节点与集群性能优化实战》,深入剖析 CPU/内存/Cgroup、节点内核参数、etcd 调优与集群容量规划,让集群性能发挥到极致。敬请期待!

微信二维码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容