{
    "componentChunkName": "component---src-templates-blog-blog-detail-tsx",
    "path": "/blog/tidb-operator-source-code-2",
    "result": {"pageContext":{"blog":{"id":"Blogs_60","title":"TiDB Operator 源码阅读 (二) Operator 模式","tags":["TiDB Operator 源码阅读"],"category":{"name":"产品技术解读"},"summary":"本文讨论了 Operator 模式，并从 Operator 模式的视角介绍 TiDB Operator 的代码的执行逻辑。","body":"在[上一篇文章](https://pingcap.com/blog-cn/tidb-operator-source-code-1/)中我们讨论了 TiDB Operator 的应用场景，了解了 TiDB Operator 可以在 Kubernetes 集群中管理 TiDB 的生命周期。可是，TiDB Operator 的代码是怎样运行起来的？TiDB 组件的生命周期管理的逻辑又是如何编排的呢？我们将从 Operator 模式的视角，介绍 TiDB Operator 的代码实现，在这篇文章中我们主要讨论 controller-manager 的实现，介绍从代码入口到组件的生命周期事件被触发中间的过程。\n\n## Operator模式的演化: 从 Controller 模式到 Operator 模式\n\nTiDB Operator 参考了 kube-controller-manager 的设计，了解 Kubernetes 的设计有助于了解 TiDB Operator 的代码逻辑。Kubernetes 内的 Resources 都是通过 Controller 实现生命周期管理的，例如 Namespace、Node、Deployment、Statefulset 等等，这些 Controller 的代码在 kube-controller-manager 中实现并由 kube-controller-manager 启动后调用。\n\n为了支持用户自定义资源的开发需求，Kubernetes 社区基于上面的开发经验，提出了 Operator 模式。Kubernetes 支持通过 CRD（CustomResourceDefinition）来描述自定义资源，通过 CRD 创建 CR（CustomResource）对象，开发者实现相应 Controller 处理 CR 及关联资源的变更的需求，通过比对资源最新状态和期望状态，逐步完成运维操作，实现最终资源状态与期望状态一致。通过定义 CRD 和实现对应 Controller，无需将代码合并到 Kubernetes 中编译使用， 即可完成一个资源的生命周期管理。\n\n## TiDB Operator 的 Controller Manager\n\nTiDB Operator 使用 tidb-controller-manager 管理各个 CRD 的 Controller。从 cmd/controller-manager/main.go 开始，tidb-controller-manager 首先加载了 kubeconfig，用于连接 kube-apiserver，然后使用一系列 NewController 函数，加载了各个 Controller 的初始化函数。\n\n```go\ncontrollers := []Controller{\n    tidbcluster.NewController(deps),\n    dmcluster.NewController(deps),\n    backup.NewController(deps),\n    restore.NewController(deps),\n    backupschedule.NewController(deps),\n    tidbinitializer.NewController(deps),\n    tidbmonitor.NewController(deps),\n}\n```\n\n在 Controller 的初始化函数过程中，会初始化一系列 Informer，这些 Informer 主要用来和 kube-apiserver 交互获取 CRD 和相关资源的变更。以 TiDBCluster 为例，在初始化函数 NewController 中，会初始化 Informer 对象：\n \n```go\ntidbClusterInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n        AddFunc: c.enqueueTidbCluster,\n        UpdateFunc: func(old, cur interface{}) {\n            c.enqueueTidbCluster(cur)\n        },\n        DeleteFunc: c.enqueueTidbCluster,\n    })\nstatefulsetInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n        AddFunc: c.addStatefulSet,\n        UpdateFunc: func(old, cur interface{}) {\n            c.updateStatefulSet(old, cur)\n        },\n        DeleteFunc: c.deleteStatefulSet,\n    })\n \n```\n \nInformer 中添加了处理添加，更新，删除事件的 EventHandler，把监听到的事件涉及到的 CR 的 Key 加入队列。\n \n初始化完成后启动 InformerFactory 并等待 cache 同步完成。\n \n```go\ninformerFactories := []InformerFactory{\n            deps.InformerFactory,\n            deps.KubeInformerFactory,\n            deps.LabelFilterKubeInformerFactory,\n        }\n        for _, f := range informerFactories {\n            f.Start(ctx.Done())\n            for v, synced := range f.WaitForCacheSync(wait.NeverStop) {\n                if !synced {\n                    klog.Fatalf(\"error syncing informer for %v\", v)\n                }\n            }\n        }\n```\n \n随后 tidb-controller-manager 会调用各个 Controller 的 Run 函数，开始循环执行 Controller 的内部逻辑。\n \n```go\n// Start syncLoop for all controllers\nfor _,controller := range controllers {\n    c := controller\n    go wait.Forever(func() { c.Run(cliCfg.Workers,ctx.Done()) },cliCfg.WaitDuration)\n}\n```\n \n以 TiDBCluster Controller 为例，Run 函数会启动 worker 处理工作队列。\n \n```go\n// Run runs the tidbcluster controller.\nfunc (c *Controller) Run(workers int, stopCh <-chan struct{}) {\n    defer utilruntime.HandleCrash()\n    defer c.queue.ShutDown()\n \n    klog.Info(\"Starting tidbcluster controller\")\n    defer klog.Info(\"Shutting down tidbcluster controller\")\n \n    for i := 0; i < workers; i++ {\n        go wait.Until(c.worker, time.Second, stopCh)\n    }\n \n    <-stopCh\n}\n```\n \nWorker 会调用 processNextWorkItem 函数，弹出队列的元素，然后调用 sync 函数进行同步：\n \n```go\n// worker runs a worker goroutine that invokes processNextWorkItem until the the controller's queue is closed\nfunc (c *Controller) worker() {\n    for c.processNextWorkItem() {\n    }\n}\n \n// processNextWorkItem dequeues items, processes them, and marks them done. It enforces that the syncHandler is never\n// invoked concurrently with the same key.\nfunc (c *Controller) processNextWorkItem() bool {\n    key, quit := c.queue.Get()\n    if quit {\n        return false\n    }\n    defer c.queue.Done(key)\n    if err := c.sync(key.(string)); err != nil {\n        if perrors.Find(err, controller.IsRequeueError) != nil {\n            klog.Infof(\"TidbCluster: %v, still need sync: %v, requeuing\", key.(string), err)\n        } else {\n            utilruntime.HandleError(fmt.Errorf(\"TidbCluster: %v, sync failed %v, requeuing\", key.(string), err))\n        }\n        c.queue.AddRateLimited(key)\n    } else {\n        c.queue.Forget(key)\n    }\n    return true\n}\n```\n \nSync 函数会根据 Key 获取对应的 CR 对象，例如这里的 TiDBCluster 对象，然后对这个 TiDBCluster 对象进行同步。\n \n```go\n// sync syncs the given tidbcluster.\nfunc (c *Controller) sync(key string) error {\n    startTime := time.Now()\n    defer func() {\n        klog.V(4).Infof(\"Finished syncing TidbCluster %q (%v)\", key, time.Since(startTime))\n    }()\n \n    ns, name, err := cache.SplitMetaNamespaceKey(key)\n    if err != nil {\n        return err\n    }\n    tc, err := c.deps.TiDBClusterLister.TidbClusters(ns).Get(name)\n    if errors.IsNotFound(err) {\n        klog.Infof(\"TidbCluster has been deleted %v\", key)\n        return nil\n    }\n    if err != nil {\n        return err\n    }\n \n    return c.syncTidbCluster(tc.DeepCopy())\n}\n \nfunc (c *Controller) syncTidbCluster(tc *v1alpha1.TidbCluster) error {\n    return c.control.UpdateTidbCluster(tc)\n}\n```\n \nsyncTidbCluster 函数调用 updateTidbCluster 函数，进而调用一系列组件的 Sync 函数实现 TiDB 集群管理的相关工作。在 pkg/controller/tidbcluster/tidb_cluster_control.go 的 updateTidbCluster 函数实现中，我们可以看到各个组件的 Sync 函数在这里调用，在相关调用代码注释里描述着每个 Sync 函数执行的生命周期操作事件，可以帮助理解每个组件的 Reconcile 需要完成哪些工作，例如 PD 组件:\n \n```go\n// works that should do to making the pd cluster current state match the desired state:\n//   - create or update the pd service\n//   - create or update the pd headless service\n//   - create the pd statefulset\n//   - sync pd cluster status from pd to TidbCluster object\n//   - upgrade the pd cluster\n//   - scale out/in the pd cluster\n//   - failover the pd cluster\nif err := c.pdMemberManager.Sync(tc); err != nil {\n    return err\n}\n```\n \n我们将在下篇文章中介绍组件的 Sync 函数完成了哪些工作，TiDBCluster Controller 是怎样完成各个组件的生命周期管理。\n\n## 小结\n\n通过这篇文章，我们了解到 TiDB Operator 如何从 cmd/controller-manager/main.go 初始化运行和如何实现对应的 Controller 对象，并以 TidbCluster Controller 为例介绍了 Controller 从初始化到实际工作的过程以及 Controller 内部的工作逻辑。通过上面的代码运行逻辑的介绍，我们清楚了组件的生命周期控制循环是如何被触发的，问题已经被缩小到如何细化这个控制循环，添加 TiDB 特殊的运维逻辑，使得 TiDB 能在 Kubernetes 上部署和正常运行，完成其他的生命周期操作。我们将在下一篇文章中讨论如何细化这个控制循环，讨论组件的控制循环的实现。\n\n我们介绍了社区对于 Operator 模式的探索和演化。对于一些希望使用 Operator 模式开发资源管理系统的小伙伴，Kubernetes 社区中提供了 Kubebuilder 和 Operator Framework 两个 Controller 脚手架项目。相比于参考 [kubernetes/sample-controller](https://github.com/kubernetes/sample-controller) 进行开发，Operator 脚手架基于 [kubernetes-sigs/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) 生成 Controller 代码，减少了许多重复引入的模板化的代码。开发者只需要专注于完成 CRD 对象的控制循环部分即可，而不需要关心控制循环启动之前的准备工作。\n\n如果有什么好的想法，欢迎通过 [#sig-k8s](https://slack.tidb.io/invite?team=tidb-community&channel=sig-k8s&ref=pingcap-tidb-operator) 或 [pingcap/tidb-operator](https://github.com/pingcap/tidb-operator) 参与 TiDB Operator 社区交流。\n\n> 相关阅读：  \n[TiDB Operator 源码阅读 (一) 序](https://pingcap.com/zh/blog/tidb-operator-source-code-1)；  \n[TiDB Operator 源码阅读 (三) 编排组件控制循环](https://pingcap.com/zh/blog/tidb-operator-source-code-3)；  \n[TiDB Operator 源码阅读 (四) 组件的控制循环](https://pingcap.com/zh/blog/tidb-operator-source-code-4)；  \n> [TiDB Operator 源码阅读 (五) 备份与恢复](https://pingcap.com/zh/blog/tidb-operator-source-code-5)。","date":"2021-03-19","author":"陈逸文","fillInMethod":"writeDirectly","customUrl":"tidb-operator-source-code-2","file":null,"relatedBlogs":[]}}},
    "staticQueryHashes": ["1327623483","1820662718","3081853212","3430003955","3649515864","4265596160","63159454"]}