1. Controller Runtime 概述

Controller Runtime 是 Kubernetes 社区官方维护的 Go 库,用于构建控制器和 Operator。它封装了 client-go 的底层细节,提供了一套声明式的、可组合的 API,让开发者专注于业务逻辑(Reconcile),而非重复编写事件监听、队列管理、领导者选举等基础设施代码。

1.1 什么是 Controller Runtime

Controller Runtime 本质上是一个 控制器框架,它定义了控制面控制器的标准构建范式。其核心设计哲学是:

  • 关注点分离:将事件源、调度、业务逻辑解耦为独立组件。
  • 约定优于配置:提供默认的 Manager、Controller、Reconciler 实现,开箱即用。
  • 可测试性:通过 fake client 和 envtest 支持单元测试与集成测试。
关键认知:Controller Runtime 不是 client-go 的替代品,而是 client-go 的上层抽象。它使用 client-go 与 API Server 通信,但隐藏了 Informer、WorkQueue、Reflector 等复杂组件。

1.2 核心组件:Manager、Controller、Reconciler

Controller Runtime 的架构围绕三个核心抽象构建,它们形成了从启动到业务处理的完整链路:

组件 职责 关键方法/接口
Manager 生命周期管理、共享依赖注入(Client、Cache、Scheme)、领导者选举 New()、Start()、Add()
Controller 事件源注册、队列调度、并发控制、触发 Reconciler New()、Watch()、Start()
Reconciler 业务逻辑实现:将实际状态调整为期望状态 Reconcile(ctx, Request) (Result, error)

三者协作流程如下:

  1. Manager 启动时初始化共享 Cache 和 Client,并启动所有已注册的 Controller。
  2. Controller 通过 Watch() 注册事件源(如对 Deployment 的变更),事件进入内部 WorkQueue。
  3. Reconciler 从队列中取出请求,执行 Reconcile() 方法,返回结果或错误。
调试切入点:当控制器行为异常时,首先检查 Manager 的启动日志(是否成功连接 API Server),然后检查 Controller 的 Watch 是否注册了正确的事件源,最后在 Reconciler 中打日志或使用 Delve 断点。

1.3 与 client-go 的关系

client-go 是 Kubernetes 官方提供的 Go 客户端库,提供对 API Server 的底层访问能力。Controller Runtime 在 client-go 之上构建了更高层的抽象:

维度 client-go Controller Runtime
抽象层级 底层 HTTP 客户端、REST 调用 高层控制器框架
事件处理 手动管理 Informer、WorkQueue 自动通过 Watch 注册,内置队列
缓存 需自行构建或使用 Informer 缓存 Manager 提供共享 Cache
领导者选举 需手动实现 Manager 内置支持
典型使用场景 CLI 工具、一次性查询、自定义客户端 Operator、控制器、Webhook

Controller Runtime 内部大量使用 client-go 的组件:

  • Cache 基于 client-go 的 SharedInformerFactory 实现。
  • Client 封装了 client-go 的 ClientSet 或 DynamicClient,提供类型安全的读写接口。
  • WorkQueue 直接复用 client-go 的 workqueue.RateLimitingInterface。
调试陷阱:Controller Runtime 的 Client 默认使用缓存读取(直接从 Cache 返回),而非每次都请求 API Server。这在调试时可能导致“读到旧数据”的错觉。如需强制读取 API Server,应使用 client.Reader 或设置 CacheOption。

1.4 整体架构图

下图展示了 Controller Runtime 的核心组件及其与 client-go 的协作关系:

Controller Runtime 核心架构与 client-go 关系 Manager Controller Reconciler 注册 & 启动 触发 Reconcile client-go 底层通信 Kubernetes API Server HTTP/gRPC Controller Runtime 封装 client-go,提供 Manager → Controller → Reconciler 三层抽象,开发者只需实现 Reconciler 业务逻辑

1.5 组件协作时序图

以下时序图展示了一个典型 Reconcile 请求的完整生命周期:

Reconcile 请求生命周期 API Server Informer WorkQueue Reconciler Cache Watch 事件 入队 key 出队请求 Get 对象 返回对象 Update/Status 更新 Watch 回调更新缓存 调试关键点:1) Informer 是否收到事件;2) WorkQueue 是否堆积;3) Reconciler 是否返回错误;4) Cache 是否与 API Server 一致 使用 --v=4 或 --v=5 可查看 Informer 和 WorkQueue 的详细日志

1.6 最小控制器代码示例

以下代码展示了 Controller Runtime 三个核心组件的典型使用方式:

package main

import (
    "context"
    "fmt"
    "os"
    "time"

    corev1 "k8s.io/api/core/v1"
    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"
    "sigs.k8s.io/controller-runtime/pkg/log/zap"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// PodReconciler 实现 Reconciler 接口
type PodReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

// Reconcile 是业务逻辑入口
func (r *PodReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
    log := log.FromContext(ctx)
    log.Info("Reconciling Pod", "namespace", req.Namespace, "name", req.Name)

    var pod corev1.Pod
    if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
        log.Error(err, "unable to fetch Pod")
        return reconcile.Result{}, client.IgnoreNotFound(err)
    }

    fmt.Printf("Pod %s/%s 当前 Phase: %s\n", pod.Namespace, pod.Name, pod.Status.Phase)
    return reconcile.Result{RequeueAfter: 30 * time.Second}, nil
}

func main() {
    // 1. 设置日志
    ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

    // 2. 创建 Manager
    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
        Scheme:             scheme,
        MetricsBindAddress: ":8080",
        LeaderElection:     true,
        LeaderElectionID:   "pod-controller.example.com",
    })
    if err != nil {
        fmt.Println("unable to start manager:", err)
        os.Exit(1)
    }

    // 3. 创建 Controller 并注册 Reconciler
    err = ctrl.NewControllerManagedBy(mgr).
        For(&corev1.Pod{}).           // 监听 Pod 事件
        Owns(&corev1.Event{}).        // 同时监听关联的 Event
        Complete(&PodReconciler{
            Client: mgr.GetClient(),
            Scheme: mgr.GetScheme(),
        })
    if err != nil {
        fmt.Println("unable to create controller:", err)
        os.Exit(1)
    }

    // 4. 启动 Manager(阻塞)
    if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
        fmt.Println("problem running manager:", err)
        os.Exit(1)
    }
}
调试建议:在 Reconcile 方法中增加 log.Info() 输出请求的 NamespacedName,配合 --v=4 启动参数,可以清晰看到每个 Reconcile 请求的触发时机和结果。这是定位“控制器未按预期触发”问题的最快方法。