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/