Operator 与 CRD:扩展 Kubernetes 能力

深入理解 Operator 模式、自定义资源定义(CRD)、以及使用 KubeBuilder 构建生产级 Operator 的完整指南。

概述

Kubernetes 的核心优势之一是可扩展性。Operator 模式允许开发者自定义资源类型和控制器,将复杂的运维逻辑封装到 Kubernetes 原生 API 中。本文将深入探讨:

学习目标

  • 理解 CustomResourceDefinition(CRD)的定义和使用
  • 掌握 Operator 模式与控制器工作原理
  • 学会使用 KubeBuilder 构建 Operator
  • 理解 RBAC、资源清理和错误处理
  • 了解社区成熟 Operator 的使用

CRD:自定义资源

什么是 CRD?

┌─────────────────────────────────────────────────────────────────┐
│                    Kubernetes 资源模型                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   内置资源(Built-in)                                          │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  Pod, Deployment, Service, ConfigMap, Secret...          │   │
│   │  - K8s 官方定义                                          │   │
│   │  - 控制器内置                                            │   │
│   │  - API Server 原生支持                                    │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   自定义资源(Custom Resource)                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  MySQL, RedisCluster, Prometheus, CertManager...        │   │
│   │  - 用户自定义                                            │   │
│   │  - 通过 CRD 声明                                         │   │
│   │  - 需要 Operator 控制器管理                             │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

创建 CRD

# crd-example.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: databases.myapp.example.com
spec:
  group: myapp.example.com      # API 组
  names:
    kind: Database              # 资源类型名
    plural: databases           # 复数形式(API 路径)
    singular: database         # 单数形式
    shortNames:
    - db                       # 简称
    listKind: DatabaseList      # 列表类型名
  scope: Namespaced            # 作用域:Namespaced/Cluster
  versions:
  - name: v1
    served: true               # 是否提供版本
    storage: true              # 是否为存储版本(只能有一个)
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              engine:
                type: string
                enum:
                - mysql
                - postgresql
              version:
                type: string
              replicas:
                type: integer
                minimum: 1
                maximum: 10
          status:
            type: object
            properties:
              phase:
                type: string
              conditions:
                type: array
                items:
                  type: object
  preserveUnknownFields: false

使用自定义资源

# database.yaml - 创建自定义资源
apiVersion: myapp.example.com/v1
kind: Database
metadata:
  name: production-db
spec:
  engine: postgresql
  version: "15"
  replicas: 3

---
# 操作自定义资源
kubectl get databases           # 查看所有 Database
kubectl get db                 # 使用简称
kubectl describe database production-db
kubectl edit database production-db
kubectl delete database production-db

多版本 CRD

# crd-multi-version.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: websites.myapp.example.com
spec:
  group: myapp.example.com
  names:
    kind: Website
    plural: websites
  versions:
  - name: v1alpha1
    served: true
    storage: false
    schema: ...
  - name: v1beta1
    served: true
    storage: false
    schema: ...
  - name: v1
    served: true
    storage: true              # 唯一存储版本
    schema: ...

# 版本转换(Webhook)
  conversion:
    strategy: Webhook
    webhook:
      conversionReviewVersions:
      - v1
      - v1beta1
      clientConfig:
        url: https://operator.myapp.svc/conversion

Operator 模式

什么是 Operator?

┌─────────────────────────────────────────────────────────────────┐
│                    Operator 工作原理                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   用户创建自定义资源                                            │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  apiVersion: myapp.example.com/v1                     │   │
│   │  kind: Database                                        │   │
│   │  metadata:                                            │   │
│   │    name: production-db                                 │   │
│   │  spec:                                                │   │
│   │    engine: postgresql                                  │   │
│   │    version: "15"                                       │   │
│   └─────────────────────────────────────────────────────────┘   │
│          │                                                      │
│          ▼                                                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                   API Server                            │   │
│   │  - 存储 CR 到 ETCD                                      │   │
│   │  - 通知 Watchers                                        │   │
│   └─────────────────────────────────────────────────────────┘   │
│          │                                                      │
│          ▼                                                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │              Operator Controller                       │   │
│   │                                                          │   │
│   │  ┌─────────────────────────────────────────────────┐    │   │
│   │  │                 Reconcile Loop                  │    │   │
│   │  │                                                 │    │   │
│   │  │  1. 监听 CR 变化 (Watch)                       │    │   │
│   │  │  2. 获取期望状态 (spec)                        │    │   │
│   │  │  3. 获取当前状态 (实际资源)                     │    │   │
│   │  │  4. 比较差异 (Reconcile)                       │    │   │
│   │  │  5. 采取行动达到期望状态                        │    │   │
│   │  │  6. 更新 CR 状态 (status)                      │    │   │
│   │  └─────────────────────────────────────────────────┘    │   │
│   └─────────────────────────────────────────────────────────┘   │
│          │                                                      │
│          ▼                                                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                 实际资源                                 │   │
│   │  Deployment, Service, ConfigMap, Secret...             │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Reconcile 循环

// Operator 核心逻辑
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // 1. 获取 CR 实例
    db := &myappv1.Database{}
    if err := r.Get(ctx, req.NamespacedName, db); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 2. 定义期望状态
    desired := r.buildDeployment(db)

    // 3. 检查并创建/更新 Deployment
    found := &appsv1.Deployment{}
    if err := r.Get(ctx, types.NamespacedName{
        Name:      db.Name,
        Namespace: db.Namespace,
    }, found); err != nil {
        // 不存在则创建
        if errors.IsNotFound(err) {
            log.Info("Creating Deployment", "name", db.Name)
            return ctrl.Result{}, r.Create(ctx, desired)
        }
        return ctrl.Result{}, err
    }

    // 4. 检查副本数是否匹配
    if *found.Spec.Replicas != db.Spec.Replicas {
        found.Spec.Replicas = &db.Spec.Replicas
        return ctrl.Result{}, r.Update(ctx, found)
    }

    // 5. 更新 CR status
    if !r.compareStatus(db, found) {
        db.Status.Phase = "Running"
        return ctrl.Result{}, r.Status().Update(ctx, db)
    }

    return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

控制器模式图

┌─────────────────────────────────────────────────────────────────┐
│                    控制器架构                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                         ETCD                                     │
│                            │                                    │
│          ┌─────────────────┼─────────────────┐                  │
│          │                 │                 │                   │
│          ▼                 ▼                 ▼                   │
│   ┌────────────┐    ┌────────────┐    ┌────────────┐           │
│   │  CRD      │    │  CR       │    │  CR       │           │
│   │  定义     │    │  实例 1    │    │  实例 2    │           │
│   └────────────┘    └────────────┘    └────────────┘           │
│          │                 │                 │                   │
│          │     ┌───────────┴───────────┐     │                   │
│          │     │                       │     │                   │
│          ▼     ▼                       ▼     ▼                   │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │                    Informer/Lister                     │  │
│   │                                                          │  │
│   │  ┌─────────┐  ┌─────────┐  ┌─────────┐                 │  │
│   │  │ CRD     │  │ CR      │  │ 其他资源 │                 │  │
│   │  │ Cache   │  │ Cache   │  │ Cache   │                 │  │
│   │  └─────────┘  └─────────┘  └─────────┘                 │  │
│   │        │            │            │                     │  │
│   │        └────────────┴────────────┘                     │  │
│   │                     │                                   │  │
│   │                     ▼                                   │  │
│   │   ┌─────────────────────────────────────────────┐     │  │
│   │   │           WorkQueue                         │     │  │
│   │   │  [db-1] [db-2] [dep-1] [svc-1]              │     │  │
│   │   └─────────────────────────────────────────────┘     │  │
│   └─────────────────────────────────────────────────────────┘  │
│                          │                                      │
│                          ▼                                      │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │              Reconcile Worker Pool                     │  │
│   │                                                          │  │
│   │  Worker 1: Reconcile(db-1)                             │  │
│   │  Worker 2: Reconcile(db-2)                             │  │
│   │  Worker 3: Reconcile(dep-1)                            │  │
│   │                                                          │  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

KubeBuilder 入门

什么是 KubeBuilder?

┌─────────────────────────────────────────────────────────────────┐
│                    KubeBuilder 定位                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   KubeBuilder = Kubebuilder + K8s Controller Runtime           │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                     KubeBuilder                         │   │
│   │                                                          │   │
│   │   ┌─────────────┐  ┌─────────────────┐  ┌────────────┐  │   │
│   │   │   自动生成   │  │   最佳实践封装   │  │   单元测试  │  │   │
│   │   │   代码骨架   │  │   Controller   │  │   框架     │  │   │
│   │   │             │  │   Runtime      │  │            │  │   │
│   │   └─────────────┘  └─────────────────┘  └────────────┘  │   │
│   │        │                 │                 │            │   │
│   │        └─────────────────┴─────────────────┘            │   │
│   │                       │                                   │   │
│   │                       ▼                                   │   │
│   │         ┌─────────────────────────────┐                 │   │
│   │         │     生产级 Operator        │                 │   │
│   │         └─────────────────────────────┘                 │   │
│   │                                                          │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

安装 KubeBuilder

# 安装 kubebuilder CLI
os=$(go env GOOS)
arch=$(go env GOARCH)

curl -L -o kubebuilder $(curl -s https://api.github.com/repos/kubernetes-sigs/kubebuilder/releases/latest | grep "kubebuilder-${os}-${arch}" | grep "browser_download" | cut -d '"' -f 4)
chmod +x kubebuilder
sudo mv kubebuilder /usr/local/kubebuilder

# 安装 controller-gen(自动生成代码)
go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest
go install sigs.k8s.io/controller-runtime/cmd/setup-envtest@latest

# 验证安装
kubebuilder version

创建项目

# 创建项目目录
mkdir -p operator && cd operator

# 初始化项目(使用 Go modules)
go mod init github.com/myorg/operator

# 创建 API(CRD + Controller)
kubebuilder init --domain myapp.example.com --repo github.com/myorg/operator

# 创建 CRD 和 Controller
kubebuilder create api --group myapp --version v1 --kind Database

# 查看生成的文件结构
ls -la
# api/
#   v1/
#     database_types.go      # CRD 类型定义
#     groupversion_info.go
#     zz_generated.deepcopy.go  # 自动生成
# config/
#   crd/                     # CRD YAML
#   rbac/                    # RBAC 配置
#   manager/                # Deployment
# controllers/
#   database_controller.go  # 控制器逻辑
# main.go                   # 入口

定义资源类型

// api/v1/database_types.go
package v1

import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// DatabaseSpec 定义期望状态
type DatabaseSpec struct {
    // +kubebuilder:validation:Enum=mysql;postgresql
    Engine string `json:"engine"`
    Version string `json:"version"`
    Replicas int32 `json:"replicas"`
    Storage string `json:"storage"`
}

// DatabaseStatus 定义当前状态
type DatabaseStatus struct {
    Phase string `json:"phase"`
    Replicas int32 `json:"replicas"`
    ReadyReplicas int32 `json:"readyReplicas"`
}

// Database 是 Database 自定义资源
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=db
type Database struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   DatabaseSpec   `json:"spec,omitempty"`
    Status DatabaseStatus `json:"status,omitempty"`
}

// DatabaseList 包含 Database 列表
// +kubebuilder:object:root=true
type DatabaseList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items []Database `json:"items"`
}

func init() {
    SchemeBuilder.Register(&Database{}, &DatabaseList{})
}

生成代码

# 生成 CRD YAML
make manifests

# 生成 DeepCopy 代码
make generate

# 运行测试
make test

# 构建镜像
make docker-build IMG=myregistry/operator:v1.0.0

# 推送到仓库
make docker-push IMG=myregistry/operator:v1.0.0

# 安装到集群
make deploy IMG=myregistry/operator:v1.0.0

控制器开发实战

实现 Reconcile

// controllers/database_controller.go
package controllers

import (
    "context"
    "fmt"
    "reflect"
    "time"

    "github.com/go-logr/logr"
    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/types"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/log"

    myappv1 "github.com/myorg/operator/api/v1"
)

// DatabaseReconciler 重新实现 Reconcile
type DatabaseReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

// +kubebuilder:rbac:groups=myapp.example.com,resources=databases,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=myapp.example.com,resources=databases/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete

func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    logger := log.FromContext(ctx)

    // 1. 获取 CR 实例
    db := &myappv1.Database{}
    if err := r.Get(ctx, req.NamespacedName, db); err != nil {
        if client.IgnoreNotFound(err) == nil {
            return ctrl.Result{}, nil
        }
        return ctrl.Result{}, err
    }

    logger.Info("Reconciling Database", "name", db.Name)

    // 2. 构建 Deployment
    dep := r.buildDeployment(db)
    if err != nil {
        return ctrl.Result{}, err
    }

    // 3. 检查 Deployment 是否存在
    found := &appsv1.Deployment{}
    err := r.Get(ctx, types.NamespacedName{
        Name:      dep.Name,
        Namespace: dep.Namespace,
    }, found)

    if err != nil && errors.IsNotFound(err) {
        logger.Info("Creating Deployment", "name", dep.Name)
        if err := r.Create(ctx, dep); err != nil {
            return ctrl.Result{}, err
        }
    } else if err != nil {
        return ctrl.Result{}, err
    }

    // 4. 检查副本数
    if *found.Spec.Replicas != db.Spec.Replicas {
        logger.Info("Updating Deployment replicas", "current", *found.Spec.Replicas, "desired", db.Spec.Replicas)
        found.Spec.Replicas = &db.Spec.Replicas
        if err := r.Update(ctx, found); err != nil {
            return ctrl.Result{}, err
        }
    }

    // 5. 更新 Status
    if !reflect.DeepEqual(db.Status.Replicas, db.Spec.Replicas) {
        db.Status.Phase = "Running"
        db.Status.Replicas = db.Spec.Replicas
        db.Status.ReadyReplicas = found.Status.ReadyReplicas
        if err := r.Status().Update(ctx, db); err != nil {
            return ctrl.Result{}, err
        }
    }

    // 6. 定期重新调谐(用于监控外部变化)
    return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

func (r *DatabaseReconciler) buildDeployment(db *myappv1.Database) *appsv1.Deployment {
    replicas := db.Spec.Replicas
    labels := map[string]string{
        "app": db.Name,
    }

    return &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      db.Name,
            Namespace: db.Namespace,
            OwnerReferences: []metav1.OwnerReference{
                *metav1.NewControllerRef(db, myappv1.GroupVersion.WithKind("Database")),
            },
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: &replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: labels,
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: labels,
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{
                        {
                            Name:  "database",
                            Image: fmt.Sprintf("%s:%s", db.Spec.Engine, db.Spec.Version),
                            Ports: []corev1.ContainerPort{
                                {ContainerPort: 5432, Name: "db"},
                            },
                        },
                    },
                },
            },
        },
    }
}

添加 OwnerReference

// 设置 OwnerReference 实现级联删除
// 确保 CR 删除时,关联的 Deployment 也被清理

func (r *DatabaseReconciler) buildDeployment(db *myappv1.Database) *appsv1.Deployment {
    return &appsv1.Deployment{
        // ...
        ObjectMeta: metav1.ObjectMeta{
            Name:      db.Name,
            Namespace: db.Namespace,
            // 关键:设置 OwnerReference
            OwnerReferences: []metav1.OwnerReference{
                *metav1.NewControllerRef(db, myappv1.GroupVersion.WithKind("Database")),
            },
        },
    }
}

Finalizer 处理

// 处理资源清理逻辑
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    db := &myappv1.Database{}
    if err := r.Get(ctx, req.NamespacedName, db); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 检查是否正在删除
    if !db.DeletionTimestamp.IsZero() {
        if controllerutil.ContainsFinalizer(db, "database.finalizer.myapp.example.com") {
            // 执行清理逻辑
            if err := r.cleanupResources(ctx, db); err != nil {
                return ctrl.Result{}, err
            }

            // 移除 Finalizer
            controllerutil.RemoveFinalizer(db, "database.finalizer.myapp.example.com")
            if err := r.Update(ctx, db); err != nil {
                return ctrl.Result{}, err
            }
        }
        return ctrl.Result{}, nil
    }

    // 添加 Finalizer(防止误删)
    if !controllerutil.ContainsFinalizer(db, "database.finalizer.myapp.example.com") {
        controllerutil.AddFinalizer(db, "database.finalizer.myapp.example.com")
        if err := r.Update(ctx, db); err != nil {
            return ctrl.Result{}, err
        }
    }

    // 正常业务逻辑...
    return r.reconcileDatabase(ctx, db)
}

RBAC 注解

// 在代码中声明需要的权限,KubeBuilder 会自动生成 RBAC 配置

// +kubebuilder:rbac:groups=myapp.example.com,resources=databases,verbs=get;list;watch;create;update;patch;delete
// 创建/更新 CR 的权限

// +kubebuilder:rbac:groups=myapp.example.com,resources=databases/status,verbs=get;update;patch
// 更新 status 子资源的权限

// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// 管理 Deployment 的权限

// +kubebuilder:rbac:groups="",resources=pods;services;configmaps;secrets,verbs=get;list;watch;create;update;patch;delete
// 管理其他资源的权限

Webhook 验证

验证 Webhook

// api/v1/database_webhook.go
package v1

import (
    "context"
    "fmt"
    "net/http"

    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    logf "sigs.k8s.io/controller-runtime/pkg/log"
    "sigs.k8s.io/controller-runtime/pkg/webhook"
)

var databaselog = logf.Log.WithName("database-resource")

//+kubebuilder:webhook:path=/validate-myapp-example-com-v1-database,mutating=false,failurePolicy=fail,groups=myapp.example.com,resources=databases,verbs=create;update,versions=v1,name=vdatabase.kb.io

var _ webhook.Validator = &Database{}

func (r *Database) ValidateCreate() error {
    databaselog.Info("validate create", "name", r.Name)

    // 验证创建请求
    if r.Spec.Replicas < 1 {
        return fmt.Errorf("replicas must be at least 1")
    }

    if r.Spec.Engine != "mysql" && r.Spec.Engine != "postgresql" {
        return fmt.Errorf("engine must be mysql or postgresql")
    }

    return nil
}

func (r *Database) ValidateUpdate(old runtime.Object) error {
    databaselog.Info("validate update", "name", r.Name)

    // 验证更新请求
    oldDb := old.(*Database)

    // 不允许降低版本
    if r.Spec.Version < oldDb.Spec.Version {
        return fmt.Errorf("cannot downgrade version")
    }

    return nil
}

func (r *Database) ValidateDelete() error {
    databaselog.Info("validate delete", "name", r.Name)
    return nil
}

func (r *Database) SetupWebhookWithManager(mgr ctrl.Manager) error {
    return ctrl.NewWebhookManagedBy(mgr).
        For(r).
        Complete()
}

生成 Webhook 配置

# KubeBuilder 自动生成 webhook 配置
make manifests

# 生成的文件:
# config/
#   webhook/
#     manifests.yaml    # ValidatingWebhookConfiguration

高级主题

Status 子资源

// 启用 status 子资源后,可以独立更新 status 而不更新 spec
// 需要在 CR 定义中添加:+kubebuilder:subresource:status

// api/v1/database_types.go
// +kubebuilder:subresource:status

// 更新 status(不触发 spec 更新)
func (r *DatabaseReconciler) updateStatus(db *myappv1.Database) error {
    db.Status.Phase = "Running"
    return r.Status().Update(context.TODO(), db)
}

级联删除

# OwnerReference 实现自动垃圾回收
# CR 删除时,所有拥有该 CR OwnerReference 的资源会被自动删除

# Kubernetes 默认行为:
# - 前景删除(Foreground):等待所有拥有 OwnerReference 的资源删除后再删除 CR
# - 后台删除(Background):立即删除 CR,由 GC 在后台清理资源

# 修改删除策略
metadata:
  finalizers:
  - example.com/protect
  # CR 必须移除 finalizer 才能被删除

Leader Election

// 在 main.go 中配置 Leader Election
func main() {
    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
        Scheme:                 scheme,
        MetricsBindAddress:     fmt.Sprintf(":%d", 8080),
        Port:                   9443,
        LeaderElection:         true,
        LeaderElectionID:       "database-controller-lock",
        LeaderElectionNamespace: "system",
    })
    // ...
}

指标暴露

// 使用 controller-runtime 提供的指标
import (
    "sigs.k8s.io/controller-runtime/pkg/metrics"
    "github.com/prometheus/client_golang/prometheus"
)

var (
    reconciliationTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "database_reconciliation_total",
            Help: "Total number of reconciliation attempts",
        },
        []string{"result"},
    )

    reconciliationDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "database_reconciliation_duration_seconds",
            Help:    "Duration of reconciliation attempts",
            Buckets: prometheus.DefBuckets,
        },
        []string{"result"},
    )
)

func init() {
    metrics.Registry.MustRegister(reconciliationTotal)
    metrics.Registry.MustRegister(reconciliationDuration)
}

常见 Operator 推荐

┌─────────────────────────────────────────────────────────────────┐
│                    成熟 Operator 推荐                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  数据存储                                                      │
│  ├─ prometheus-operator      # Prometheus 监控                 │
│  ├─ grafana-operator         # Grafana 可视化                  │
│  ├─ postgres-operator        # PostgreSQL 高可用                │
│  ├─ mysql-operator           # MySQL  Operator                   │
│  └─ redis-operator          # Redis Cluster                    │
│                                                                 │
│  服务网格                                                      │
│  ├─ istio-operator           # Istio 服务网格                   │
│  └─ linkerd-operator         # Linkerd 服务网格                 │
│                                                                 │
│  证书管理                                                      │
│  ├─ cert-manager             # Let's Encrypt 证书                │
│  └─ external-secrets         # 外部密钥集成                     │
│                                                                 │
│  应用管理                                                      │
│  ├─ argocd-operator          # ArgoCD GitOps                    │
│  ├─ flux-operator            # Flux GitOps                      │
│  └─ sealed-secrets          # 加密 Secrets                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

安装 Prometheus Operator

# 使用 Helm 安装
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace

# 访问 Prometheus
kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80

常见问题与避坑指南

Q1:Controller 不生效?

# 排查步骤
# 1. 检查 Controller 是否运行
kubectl get pods -n operator-system

# 2. 查看 Controller 日志
kubectl logs -n operator-system deployment/operator-controller-manager -f

# 3. 检查 RBAC 权限
kubectl auth can-i get databases --as=system:serviceaccount:operator-system:operator-controller-manager

# 4. 检查 CRD 是否存在
kubectl get crd databases.myapp.example.com

# 5. 检查 webhook 是否注册
kubectl get validatingwebhookconfiguration vdatabase.kb.io

Q2:如何调试 Reconcile?

// 添加详细日志
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    logger := log.FromContext(ctx)
    logger.Info("Starting reconcile", "name", req.NamespacedName)

    // 获取资源
    db := &myappv1.Database{}
    if err := r.Get(ctx, req.NamespacedName, db); err != nil {
        logger.Error(err, "Failed to get Database")
        return ctrl.Result{}, err
    }

    logger.Info("Got Database", "spec", db.Spec)
    // ...
}

Q3:如何处理外部依赖?

// 场景:Database CR 依赖外部云数据库资源

// 方案1:检查依赖状态
func (r *DatabaseReconciler) reconcileCloudDB(ctx context.Context, db *myappv1.Database) error {
    // 创建云端数据库
    cloudDB, err := r.createCloudDB(db)
    if err != nil {
        db.Status.Phase = "Provisioning"
        db.Status.Message = "Creating cloud database..."
        return err
    }

    // 更新状态
    db.Status.Phase = "Running"
    db.Status.ConnectionString = cloudDB.ConnectionString
    return nil
}

// 方案2:幂等创建
// 使用外部资源的 Name/ID 作为唯一标识,重复调用不会重复创建

Q4:如何处理版本迁移?

// 场景:Operator 升级后需要迁移已有资源

func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    db := &myappv1.Database{}
    r.Get(ctx, req.NamespacedName, db)

    // 检查是否需要迁移
    if db.Status.Version != db.Spec.Version {
        if err := r.migrateData(db); err != nil {
            db.Status.Phase = "Migrating"
            return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
        }
    }

    // 继续正常逻辑...
}

总结

┌─────────────────────────────────────────────────────────────────┐
│                    核心要点回顾                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  CRD(自定义资源)                                              │
│  ├── 定义新资源类型                                             │
│  ├── 多版本支持                                                │
│  └── Webhook 验证                                              │
│                                                                 │
│  Operator 模式                                                 │
│  ├── Watch CR 变化                                             │
│  ├── Reconcile 达到期望状态                                    │
│  ├── Status 反馈当前状态                                        │
│  └── Finalizer 处理清理                                        │
│                                                                 │
│  KubeBuilder                                                   │
│  ├── 自动生成代码骨架                                          │
│  ├── 简化 CRD 开发                                            │
│  └── 集成测试框架                                              │
│                                                                 │
│  最佳实践                                                       │
│  ├── RBAC 权限最小化                                           │
│  ├── OwnerReference 级联删除                                  │
│  ├── Finalizer 资源清理                                        │
│  └── Leader Election 高可用                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

思考题

  1. Operator 和 Helm Chart 有什么区别?什么场景下选择哪个?
  2. 如何设计一个安全的 Operator,避免误操作导致数据丢失?
  3. 如何测试 Operator 的并发行为和错误恢复能力?

引用与参考

  1. Kubebuilder Book
  2. Operator Pattern
  3. Custom Resources

下篇预告

下一篇文章我们将探讨 服务网格 Istio,包括:

  • Sidecar 代理与数据平面
  • 流量管理(路由、负载均衡)
  • mTLS 与服务安全
  • 可观测性集成

敬请期待!