编写一个自定义K8s Controller

在 K8s 中当我们需要监控某个资源的变化并作一系列操作时,使用 K8s 提供的 controller 机制来实现,同时 K8s 官方提供了一个通用库 client-go,通过它可以很容易实现自定义controller.

Client-go & controller 架构

在编写controller 之前,我们需要了解 client-go 对资源监控的整个架构和流程,并需要知道我们所需要自定义的是哪部分组件。

在 client-go 中包含了编写自定义 controller 可以使用的各种机制,这些机制在库的k8s.io/client-go/tools k8s.io/client-go/util中定义。

如图所示,图中包含了 client-go 和 controller 整个交互流程。

client-go

在client-go 中主要包含了以下组件:

  • Reflector:通过调用 LISTWATCH 接口与 ApiServer 通信,监听特定资源(此处监听的资源需要我们指定),并把资源的更新动态存入 Delta FIFO 队列
  • Informer:从Delta FIFO队列拿出对象,完成此操作的函数是processLoop。
  • Indexer: 提供线程级别安全来存储对象和key。

custom controller

  • Informer reference: Informer对象引用
  • Indexer reference: Indexer对象引用
  • Resource Event Handlers: 被Informer调用的回调函数,这些函数的作用通常是获取对象的key,并把key放入Work queue,以进一步做处理。
  • Work queue: 工作队列,用于将对象的交付与其处理分离,编写Resource event handler functions以提取传递的对象的key并将其添加到工作队列。此处可以过滤掉我们不关心的信息。
  • Process Item: 用于处理Work queue中的对象,可以有一个或多个其他函数一起处理;这些函数通常使用Indexer reference或Listing wrapper来检索与该键对应的对象。这里就是我们需要自定义的业务逻辑

Sample Controller

这里编写一个简易的 Controller, 用于监听 pod 创建、删除信息,并将信息打印出来。

Controller 逻辑

首先我们需要定义一个这样的 Controller 结构体

type Controller struct {
    indexer  cache.Indexer  // Indexer 的引用
    queue    workqueue.RateLimitingInterface  //workqueue 的引用
    informer cache.Controller // Informer 的引用
}

定义 Controller 的工作流

func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
    defer runtime.HandleCrash()

    defer c.queue.ShutDown()

    klog.Info("Starting pod controller")

    go c.informer.Run(stopCh)   // 启动 informer

    if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
        runtime.HandleError(fmt.Errorf("Time out waitng for caches to sync"))
        return
    }

    // 启动多个 worker 处理 workqueue 中的对象
    for i := 0; i < threadiness; i++ {
        go wait.Until(c.runWorker, time.Second, stopCh)
    }

    <-stopCh
    klog.Info("Stopping Pod controller")
}

具体处理 worker queue 中对象的流程

func (c *Controller) runWorker() {
    // 启动无限循环,接收并处理消息
    for c.processNextItem() {

    }
}
// 从 workqueue 中获取对象,并打印信息。
func (c *Controller) processNextItem() bool {
    key, shutdown := c.queue.Get()
    // 退出
    if shutdown {
        return false
    }

    // 标记此key已经处理
    defer c.queue.Done(key)

    // 将key对应的 object 的信息进行打印
    err := c.syncToStdout(key.(string))

    c.handleError(err, key)
    return true
}

// 获取 key 对应的 object,并打印相关信息
func (c *Controller) syncToStdout(key string) error {
    obj, exists, err := c.indexer.GetByKey(key)
    if err != nil {
        klog.Errorf("Fetching object with key %s from store failed with %v", key, err)
        return err
    }
    if !exists {
        fmt.Printf("Pod %s does not exist")
    } else {
        fmt.Printf("Sync/Add/Update for Pod %s\n", obj.(*core_v1.Pod).GetName())
    }
    return nil
}

Main 函数逻辑

func main() {
    var kubeconfig string
    var master string
    // 从外部获取集群信息(kube.config)
    flag.StringVar(&kubeconfig, "kubeconfig", "", "kubeconfig file")
    // 获取集群master 的url
    flag.StringVar(&master, "master", "", "master url")

    // 读取构建 config
    config, err := clientcmd.BuildConfigFromFlags(master, kubeconfig)
    if err != nil {
        klog.Fatal(err)
    }

    // 创建 k8s client
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        klog.Fatal(err)
    }

    // 指定 ListWatcher 在所有namespace下监听 pod 资源
    podListWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", v1.NamespaceAll, fields.Everything())

    // 创建 workqueue
    queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

    // 创建 indexer 和 informer
    indexer, informer := cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
        // 当有 pod 创建时,根据 Delta queue 弹出的 object 生成对应的Key,并加入到 workqueue中。此处可以根据Object的一些属性,进行过滤
        AddFunc: func(obj interface{}) {
            key, err := cache.MetaNamespaceKeyFunc(new)
            if err == nil {
                queue.Add(key)
            }
        },
        // pod 删除操作
        DeleteFunc: func(obj interface{}) {
            // DeletionHandlingMetaNamespaceKeyFunc 会在生成key 之前检查。因为资源删除后有可能会进行重建等操作,监听时错过了删除信息,从而导致该条记录是陈旧的。
            key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
            if err == nil {
                queue.Add(key)
            }
        },
    }, cache.Indexers{})

    controller := pkg.NewController(queue, indexer, informer)

    stop := make(chan struct{})

    defer close(stop)
    // 启动 controller
    go controller.Run(1, stop)

    select {}

}

后记

Controller 的整体流程综上,如果我们使用 CRD ,Controller 流程也不会变化很多,更多的是需要对CRD 的监控,并根据变化创建对应的 Deployment 或 StatefulSet。同时 CRD 也需要增加对应的 Validation 和 Spec 的解析。这一部分我们下一篇继续。

To Be Continue...如果喜欢,请关注我的公众号,或者查看我的博客 http://packyzbq.coding.me. 我会不定时的发送我自己的学习记录,大家互相学习交流哈~weixin

<think>嗯,用户现在想要了解如何编写一个调谐通用资源的Kubernetes控制器。首先,我需要明确用户的需求。他们可能已经知道Kubernetes的基本概念,但想深入控制器的工作原理,特别是如何编写一个自定义控制器来管理通用资源。这里的“通用资源”可能指的是用户自定义的CRD(Custom Resource Definition),或者是Kubernetes中的某些标准资源,但需要更灵活的调谐逻辑。 接下来,我应该回顾用户之前的问题历史。之前他们询问过GORM无法修改字段类型或删除字段的问题,可能是在开发过程中遇到了数据库迁移的问题。现在转向Kubernetes控制器,可能他们的工作涉及云原生应用开发,需要自动化资源管理。用户可能是开发人员或DevOps工程师,需要实现自定义逻辑来管理Kubernetes集群中的资源。 用户的需求可能包括控制器的基本结构、使用哪些客户端库、如何监听事件、调谐逻辑的实现,以及如何处理状态更新。他们可能希望有一个具体的例子,而不仅仅是理论上的解释。此外,他们可能关心最佳实践,比如错误处理、性能优化和测试方法。 我需要考虑用户可能存在的深层需求。比如,他们是否了解Kubernetes的Operator模式?是否需要解释控制器与Operator的区别?或者是否需要指导如何打包和部署控制器?可能还需要提到一些工具或框架,如Kubebuilder或Operator SDK,这些可以简化控制器的开发流程。 在回答结构上,应该分步骤介绍,从创建CRD开始,到初始化控制器,定义事件处理,实现调谐逻辑,处理状态更新,最后部署和测试。需要提供代码示例,但确保代码正确且符合Go语言的惯例。同时,要提醒用户注意关键点,比如并发控制、重试机制、权限设置等。 另外,用户可能对调谐循环(Reconcile Loop)的具体实现细节感兴趣,比如如何处理资源的创建、更新、删除,如何与API服务器交互。需要解释Informer和Workqueue的作用,以及如何高效地处理事件而不会阻塞系统。 最后,应该总结最佳实践,比如使用声明式API、幂等操作、资源版本控制等,帮助用户编写健壮且高效的控制器。可能还需要推荐一些扩展学习资源,如官方文档或开源项目,供用户进一步参考。</think>以下是编写 Kubernetes 通用资源控制器的完整指南,包含核心模式和实践方案: --- ### 一、控制器核心架构 ```mermaid graph TD A[API Server] -->|Watch| B(Informer) B --> C[Resource Event] C --> D{事件类型} D -->|Add| E[Workqueue] D -->|Update| E D -->|Delete| E E --> F[Worker] F --> G[Reconcile逻辑] G --> H[调谐实际状态] ``` --- ### 二、标准开发流程 #### 1. 创建 CRD(以 MyApp 为例) ```yaml # myapp-crd.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: myapps.example.com spec: group: example.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: replicas: type: integer image: type: string scope: Namespaced names: plural: myapps singular: myapp kind: MyApp ``` #### 2. 初始化控制器 ```go import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" ) type Controller struct { clientset clientset.Interface informer cache.SharedIndexInformer queue workqueue.RateLimitingInterface } func NewController(clientset clientset.Interface) *Controller { c := &Controller{ clientset: clientset, queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), } c.informer = cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { return clientset.ExampleV1().MyApps(metav1.NamespaceAll).List(context.TODO(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { return clientset.ExampleV1().MyApps(metav1.NamespaceAll).Watch(context.TODO(), options) }, }, &v1.MyApp{}, 0, // 无重新同步周期 cache.Indexers{}, ) c.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.handleAdd, UpdateFunc: c.handleUpdate, DeleteFunc: c.handleDelete, }) return c } ``` #### 3. 事件处理核心逻辑 ```go func (c *Controller) handleAdd(obj interface{}) { key, err := cache.MetaNamespaceKeyFunc(obj) if err == nil { c.queue.Add(key) } } func (c *Controller) handleUpdate(oldObj, newObj interface{}) { if !reflect.DeepEqual(oldObj.(*v1.MyApp).Spec, newObj.(*v1.MyApp).Spec) { key, err := cache.MetaNamespaceKeyFunc(newObj) if err == nil { c.queue.Add(key) } } } func (c *Controller) handleDelete(obj interface{}) { key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err == nil { c.queue.Add(key) } } ``` #### 4. 调谐逻辑实现 ```go func (c *Controller) reconcile(key string) error { namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { return err } obj, err := c.clientset.ExampleV1().MyApps(namespace).Get(context.TODO(), name, metav1.GetOptions{}) if errors.IsNotFound(err) { // 处理删除逻辑 return nil } if err != nil { return err } // 实际调谐逻辑 desiredDeployments := generateDeployments(obj) currentDeployments, err := c.getCurrentDeployments(obj) if err := c.manageDeployments(currentDeployments, desiredDeployments); err != nil { return err } return c.updateStatus(obj) } ``` --- ### 三、关键优化策略 #### 1. 事件合并处理 ```go // 设置队列参数 c.queue = workqueue.NewRateLimitingQueue( workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second), ) ``` #### 2. 状态更新模式 ```go func (c *Controller) updateStatus(obj *v1.MyApp) error { newStatus := calculateNewStatus(obj) if reflect.DeepEqual(obj.Status, newStatus) { return nil } obj.Status = newStatus _, err := c.clientset.ExampleV1().MyApps(obj.Namespace).UpdateStatus(context.TODO(), obj, metav1.UpdateOptions{}) return err } ``` --- ### 四、生产级最佳实践 #### 1. 错误处理策略 ```go const maxRetries = 5 func (c *Controller) processNextItem() bool { key, quit := c.queue.Get() if quit { return false } defer c.queue.Done(key) err := c.reconcile(key.(string)) if err != nil { if c.queue.NumRequeues(key) < maxRetries { c.queue.AddRateLimited(key) return true } // 记录错误日志并放弃重试 } c.queue.Forget(key) return true } ``` #### 2. Leader 选举配置 ```yaml # deployment.yaml spec: replicas: 3 template: spec: containers: - args: - --leader-elect=true - --leader-elect-lease-duration=15s - --leader-elect-renew-deadline=10s - --leader-elect-retry-period=2s ``` --- ### 五、调试与测试方案 #### 1. 单元测试框架 ```go func TestReconcile(t *testing.T) { testCases := []struct { name string input *v1.MyApp expectedErr bool }{ { name: "normal case", input: &v1.MyApp{ Spec: v1.MyAppSpec{ Replicas: 3, Image: "nginx:latest", }, }, expectedErr: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // 初始化 fake client // 执行 reconcile // 断言结果 }) } } ``` #### 2. 集成测试环境 ```bash kind create cluster --name controller-test go test ./... -tags=integration -v ``` --- ### 六、性能优化技巧 #### 1. 缓存优化配置 ```go c.informer = cache.NewSharedIndexInformer( ... cache.Indexers{ "byNamespace": func(obj interface{}) ([]string, error) { return []string{obj.(*v1.MyApp).Namespace}, nil }, }, ) ``` #### 2. 并发控制策略 ```go func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) { for i := 0; i < threadiness; i++ { go wait.Until(c.runWorker, time.Second, stopCh) } <-stopCh } ``` --- ### 七、扩展模式推荐 #### 1. Finalizer 机制实现 ```go const finalizerName = "example.com/finalizer" func (c *Controller) handleFinalizers(obj *v1.MyApp) error { if obj.DeletionTimestamp.IsZero() { if !controllerutil.ContainsFinalizer(obj, finalizerName) { controllerutil.AddFinalizer(obj, finalizerName) return c.updateObject(obj) } } else { if controllerutil.ContainsFinalizer(obj, finalizerName) { // 执行清理逻辑 controllerutil.RemoveFinalizer(obj, finalizerName) return c.updateObject(obj) } } return nil } ``` #### 2. 多版本兼容处理 ```go func convertToNewVersion(oldObj runtime.Object) (runtime.Object, error) { switch old := oldObj.(type) { case *v1beta1.MyApp: newObj := &v1.MyApp{} // 转换逻辑 return newObj, nil default: return nil, fmt.Errorf("unsupported version") } } ``` --- ### 八、部署架构推荐 ```mermaid graph TD A[Controller Pod] -->|Watch| B(API Server) B --> C[Custom Resources] C --> D[Controller] D -->|Create/Update| E[Deployment] D -->|Manage| F[Service] D -->|Sync| G[ConfigMap] H(Metrics Server) --> D I(Prometheus) -->|监控指标| D ``` 建议使用 Operator SDK 或 Kubebuilder 框架加速开发,这两个工具提供了完整的脚手架生成和代码模板功能。对于复杂业务场景,可以结合使用 Admission Webhook 实现更精细的控制策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值