当前位置:首页 > 问答 > 正文

想快速搞懂K8s controller-runtime到底是啥,怎么用,有点复杂但这篇能帮你理清楚

整理自云原生社区博客《kubebuilder 与 controller-runtime 的关系》、B站视频《Kubernetes Controller Runtime 详解》、知乎专栏《Controller-Runtime 源码分析》及官方文档,以最直白方式讲解)

第一部分:它到底是什么?一个生动的比喻

想象一下,你要在小区里开一个自动售货机(我们叫它“糖果机”),你的理想状态是:只要糖果库存少于10盒,就自动打电话给供应商补货到50盒。

你来扮演这个系统的“大脑”,但你不能24小时站在糖果机前数糖果,太累了,你需要一套自动化工具来帮你:

  1. 眼睛(Watch): 你得有个摄像头一直盯着糖果机的库存数字。
  2. 大脑(Reconcile): 你脑子里有个逻辑:“如果库存<10,就补货到50”。
  3. 双手(Client): 你还需要能直接打电话给供应商(调用K8s API)的手。

K8s controller-runtime 就是这套帮你管理“糖果机”的自动化工具包。 你不是K8s的开发者,你不用自己去造摄像头、去写底层通信协议,controller-runtime 把这些复杂活儿都封装好了,给你提供了现成的、稳定的“眼睛”、“大脑”框架和“双手”。

它的核心任务就是帮你高效、可靠地监听K8s集群里各种资源(如Pod、Deployment,或者你自定义的“糖果”资源)的变化,并根据你写的业务逻辑去调整集群状态,使其符合你的预期。

第二部分:它解决什么问题?为什么需要它?

没有controller-runtime之前,你要写一个K8s控制器(Controller)会非常痛苦(来源:官方文档中提及的client-go示例复杂性),你需要自己处理:

  • 复杂连接: 如何安全、高效地连接K8s API服务器。
  • 消息队列: 资源变化事件像洪水一样涌来,你需要一个可靠的队列来缓存和处理,防止丢事件或把系统压垮。
  • 并发控制: 同一个资源可能同时发生多个事件,如何保证不会重复处理或者产生冲突。
  • 故障恢复: 如果你的程序崩溃重启,如何从断点继续工作,而不是从头开始。

controller-runtime 把这些分布式系统里的脏活、累活都揽了下来,让你只需要关心最核心的那一件事:你的业务逻辑,也就是“那么…”的逻辑,这大大降低了开发K8s控制器的门槛和出错概率。

第三部分:核心概念拆解(一点也不专业)

使用controller-runtime,你主要跟三个东西打交道:

  1. Manager(经理): 它是你整个控制器的“大总管”,一个程序(进程)里通常只有一个Manager,它的作用是:

    • 设置工厂车间: 管理所有控制器需要的公共设施,比如连接K8s的“通行证”(Kubeconfig)。
    • 启动生产线: 当你把所有控制器都注册给它之后,你只需要调用 Manager.Start(),它就会自动启动所有控制器,并处理好它们的生命周期和协调工作。
  2. Controller(控制器): 这是你为特定“资源”编写的处理程序,你有一个“糖果”资源,你就需要写一个“糖果控制器”,它的核心是:

    • 告诉“眼睛”看哪里: 你通过代码设置这个控制器去Watch(监视) “糖果”这种资源,一旦有糖果被创建、更新或删除,控制器就会收到事件。
    • 绑定“大脑”: 你把你的业务逻辑函数(叫做 Reconcile)绑定到这个控制器上。
  3. Reconcile(调协逻辑): 这是你唯一需要认真写的核心代码,它就像一个万能公式:

    • 输入: 一个请求,里面通常包含了哪个“糖果”发生了变化(比如名叫“巧克力”的糖果)。
    • 过程:
      • 看一看: 用Manager提供的“手”(Client)去K8s集群里Get当前这个“巧克力”的最新状态。
      • 比一比: 检查它的库存状态(当前库存是5)。
      • 想一想: 根据你的逻辑(库存<10),判断需要补货。
    • 输出: 用“手”(Client)去Update这个“巧克力”的库存,把它改成50,然后返回一个结果(告诉controller-runtime“处理成功,没事别再来烦我”或者“有点问题,过会儿再试”)。

整个流程就是:Manager管着Controller,Controller盯着K8s资源变化,一旦有变,就调用你写的Reconcile函数去干活。

第四部分:怎么用?极简代码示例

以下是一个高度简化的伪代码,展示如何使用controller-runtime框架(来源:借鉴Kubebuilder生成的项目结构):

// 1. 创建“大总管”(Manager)
mgr, err := manager.New(...) // 传入连接K8s的配置
// 2. 创建“糖果控制器”
candyController, err := controller.New("candy-controller", mgr, controller.Options{
    Reconcile: &CandyReconciler{Client: mgr.GetClient()}, // 这里塞入我们的业务逻辑
})
// 3. 告诉控制器监视“糖果”资源
candyController.Watch(&source.Kind{Type: &v1alpha1.Candy{}})
// 4. 启动大总管,开始工作
mgr.Start(signals.SetupSignalHandler())

而你的核心业务逻辑 CandyReconciler 看起来像这样:

type CandyReconciler struct {
    client.Client // 这就是那个能操作K8s的“手”
}
func (r *CandyReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
    // 1. 获取当前的糖果对象
    candy := &v1alpha1.Candy{}
    err := r.Get(ctx, req.NamespacedName, candy)
    if err != nil {
        // 如果糖果已经被删了,那就没事可做了
        return reconcile.Result{}, err
    }
    // 2. 你的业务逻辑:如果库存小于10
    if candy.Spec.Stock < 10 {
        // 3. 执行操作:补货到50
        candy.Spec.Stock = 50
        // 4. 更新回K8s集群
        err = r.Update(ctx, candy)
        if err != nil {
            // 如果更新失败,返回错误,controller-runtime会稍后重试
            return reconcile.Result{}, err
        }
    }
    // 5. 一切正常,处理完毕
    return reconcile.Result{}, nil
}

第五部分:总结

controller-runtime是一个开发K8s控制器的“脚手架”或“样板间”框架。

  • 你不用关心: 怎么连接API、怎么缓存数据、怎么处理事件流、怎么实现多线程安全。
  • 你只需关心:Reconcile 函数里写下你的核心业务逻辑——“当我关心的资源变成A状态时,我就把它改成B状态”。

现在流行的Kubebuilder和Operator SDK这类工具,底层都是基于controller-runtime的,它们帮你生成项目模板,让你写控制器像做填空题一样简单。

下次听到controller-runtime,你就把它想象成那个帮你自动化管理“糖果机”的得力助手,它负责所有基础设施,而你,才是定义业务规则的“老板”。

想快速搞懂K8s controller-runtime到底是啥,怎么用,有点复杂但这篇能帮你理清楚