Kubernetes Operator自定义控制器开发与CRD资源管理实战

Kubernetes Operator模式将运维人员的领域知识编码为自定义控制器,通过CRD(Custom Resource Definition)扩展Kubernetes API,实现对复杂有状态应用的全生命周期自动化管理。Operator持续监听自定义资源的变化,根据期望状态(Desired State)与实际状态的差异自动执行操作,将部署、扩容、备份、升级、故障恢复等运维流程转化为代码逻辑。Prometheus Operator、ArgoCD Operator、Strimzi Kafka Operator等已在生产环境广泛部署。

CRD定义与API资源注册

Operator的第一步是定义CRD,声明自定义资源的结构和校验规则。以下以MySQL Operator为例,定义一个MySQLCluster CRD,包含副本数、存储配置、版本等字段。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: mysqlclusters.database.yunthe.com
spec:
  group: database.yunthe.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
              required: [replicas, version]
              properties:
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 9
                version:
                  type: string
                  enum: ["8.0", "8.4"]
                storageSize:
                  type: string
                  default: "10Gi"
            status:
              type: object
              properties:
                readyReplicas:
                  type: integer
                phase:
                  type: string
kubectl apply -f mysqlcluster-crd.yaml

cat << EOF | kubectl apply -f -
apiVersion: database.yunthe.com/v1alpha1
kind: MySQLCluster
metadata:
  name: prod-mysql
  namespace: database
spec:
  replicas: 3
  version: "8.4"
  storageSize: "100Gi"
EOF

使用Go和controller-runtime构建Operator

Operator的核心是Reconcile循环:监听CRD资源变化,比较当前状态与期望状态,执行调和操作。

package controllers

import (
    "context"
    "fmt"
    "time"

    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/api/errors"
    "k8s.io/apimachinery/pkg/api/resource"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/types"
    "sigs.k8s.io/controller-runtime/pkg/client"

    databasev1alpha1 "yunthe.com/mysql-operator/api/v1alpha1"
)

type MySQLClusterReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

func (r *MySQLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    instance := &databasev1alpha1.MySQLCluster{}
    err := r.Get(ctx, req.NamespacedName, instance)
    if errors.IsNotFound(err) {
        return ctrl.Result{}, nil
    }
    if err != nil {
        return ctrl.Result{}, err
    }

    expectedSts := r.buildStatefulSet(instance)
    foundSts := &appsv1.StatefulSet{}
    err = r.Get(ctx, types.NamespacedName{
        Name: instance.Name, Namespace: instance.Namespace,
    }, foundSts)

    if errors.IsNotFound(err) {
        if err := r.Create(ctx, expectedSts); err != nil {
            return ctrl.Result{}, err
        }
        instance.Status.Phase = "Creating"
        r.Status().Update(ctx, instance)
        return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
    }

    if *foundSts.Spec.Replicas != instance.Spec.Replicas {
        foundSts.Spec.Replicas = &instance.Spec.Replicas
        if err := r.Update(ctx, foundSts); err != nil {
            return ctrl.Result{}, err
        }
    }

    instance.Status.ReadyReplicas = int(foundSts.Status.ReadyReplicas)
    if instance.Status.ReadyReplicas == instance.Spec.Replicas {
        instance.Status.Phase = "Running"
    }
    r.Status().Update(ctx, instance)
    return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

Reconcile循环的幂等性与最终一致性

Reconcile函数必须设计为幂等的——同样输入无论执行多少次都产生相同结果。关键设计规则:

  • 每次Reconcile从头获取资源当前状态,不依赖内存中的缓存
  • 创建资源前先查询是否已存在,存在则比较并更新差异部分
  • 状态更新使用Status subresource,避免触发spec变更的级联Reconcile
  • 对于需要多步骤的操作,每步完成后return并Requeue,下一步在新的Reconcile中执行

Owner Reference与垃圾回收级联

Operator创建的子资源需要设置Owner Reference指向CRD实例。当CR被删除时,Kubernetes垃圾回收器自动清理所有关联的子资源。

if err := ctrl.SetControllerReference(instance, expectedSts, r.Scheme); err != nil {
    return ctrl.Result{}, err
}

SetControllerReference在创建资源时调用,将CRD实例的APIVersion、Kind、Name、UID写入子资源的metadata.ownerReferences字段。级联删除策略默认为Foreground(先删子资源再删Owner),也可配置为Background。

Finalizer实现资源清理钩子

Finalizer机制在资源上设置一个标记,Kubernetes不会立即删除带有Finalizer的资源,直到Finalizer被移除。

const finalizerName = "database.yunthe.com/finalizer"

func (r *MySQLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    instance := &databasev1alpha1.MySQLCluster{}
    if err := r.Get(ctx, req.NamespacedName, instance); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    if !instance.DeletionTimestamp.IsZero() {
        if containsString(instance.Finalizers, finalizerName) {
            if err := r.backupFinalData(ctx, instance); err != nil {
                return ctrl.Result{}, err
            }
            r.cleanupDNSRecords(instance)
            instance.Finalizers = removeString(instance.Finalizers, finalizerName)
            if err := r.Update(ctx, instance); err != nil {
                return ctrl.Result{}, err
            }
        }
        return ctrl.Result{}, nil
    }

    if !containsString(instance.Finalizers, finalizerName) {
        instance.Finalizers = append(instance.Finalizers, finalizerName)
        if err := r.Update(ctx, instance); err != nil {
            return ctrl.Result{}, err
        }
        return ctrl.Result{Requeue: true}, nil
    }
    // normal reconcile logic...
}

Finalizer使用时需注意:如果清理逻辑执行失败但未返回错误,Finalizer永远不会被移除,导致资源卡在Terminating状态。如果Operator Pod重启,新的Reconcile需要能幂等地恢复清理操作。建议清理逻辑设计为可重试的,并设置超时与告警。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/kubernetesoperator-zi-ding-yi-kong-zhi-qi-kai-fa-yu-crd-zi/

(0)
小编小编
上一篇 11小时前
下一篇 11小时前

相关推荐