kubebuilder 的运行逻辑

概述

下面是kubebuilder 的架构图。可以看到最外层是通过名为Manager的组件驱动的,Manager中包含了多个组件,其中Cache中保存了gvk和informer的映射关系,用于通过informer的方式缓存kubernetes 的对象。Controller使用workqueue的方式缓存informer传递过来的对象,后续提取workqueue中的对象,传递给Reconciler进行处理。

本文不介绍kuberbuilder的用法,如有需要可以参考如下三篇文章:

本次使用的controller-runtime的版本是:v0.11.0

下述例子的代码生成参考:Building your own kubernetes CRDs

Managers

manager负责运行controllers和webhooks,并设置公共依赖,如clients、caches、schemes等。

kubebuilder的处理

kubebuilder会自动在main.go中创建Manager:

	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "3b9f5c61.com.bolingcavalry",
})

controllers是通过调用Manager.Start接口启动的。

Controllers

controller使用events来触发reconcile的请求。通过controller.New接口可以初始化一个controller,并通过manager.Start启动该controller。

func New(name string, mgr manager.Manager, options Options) (Controller, error) {
c, err := NewUnmanaged(name, mgr, options)
if err != nil {
return nil, err
} // Add the controller as a Manager components
return c, mgr.Add(c) // 将controller添加到manager中
}

kubebuilder的处理

kubebuilder会自动在main.go中生成一个SetupWithManager函数,在Complete中创建并将controller添加到manager,具体见下文:

func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&webappv1.Guestbook{}).
Complete(r)
}

main.go中调用Manager.Start接口来启动controller:

mgr.Start(ctrl.SetupSignalHandler())

Reconcilers

Controller的核心是实现了Reconciler接口。Reconciler 会接收到一个reconcile请求,该请求中包含对象的name和namespace。reconcile会对比对象和其所拥有(own)的资源的当前状态与期望状态,并据此做出相应的调整。

通常Controller会根据集群事件(如Creating、Updating、Deleting Kubernetes对象)或外部事件(如GitHub Webhooks、轮询外部资源等)触发reconcile。

注意:Reconciler中传入的reqeust中仅包含对象的名称和命名空间,并没有对象的其他信息,因此需要通过kubernetes client来获取对象的相关信息。

type Request struct {
// NamespacedName is the name and namespace of the object to reconcile.
types.NamespacedName
}
type NamespacedName struct {
Namespace string
Name string
}

Reconciler接口的描述如下,其中给出了其处理逻辑的例子:

  • 读取一个对象以及其所拥有的所有pod
  • 观察到对象期望的副本数为5,但实际只有一个pod副本
  • 创建4个pods,并设置OwnerReferences
/*
Reconciler implements a Kubernetes API for a specific Resource by Creating, Updating or Deleting Kubernetes
objects, or by making changes to systems external to the cluster (e.g. cloudproviders, github, etc). reconcile implementations compare the state specified in an object by a user against the actual cluster state,
and then perform operations to make the actual cluster state reflect the state specified by the user. Typically, reconcile is triggered by a Controller in response to cluster Events (e.g. Creating, Updating,
Deleting Kubernetes objects) or external Events (GitHub Webhooks, polling external sources, etc). Example reconcile Logic: * Read an object and all the Pods it owns.
* Observe that the object spec specifies 5 replicas but actual cluster contains only 1 Pod replica.
* Create 4 Pods and set their OwnerReferences to the object. reconcile may be implemented as either a type: type reconcile struct {} func (reconcile) reconcile(controller.Request) (controller.Result, error) {
// Implement business logic of reading and writing objects here
return controller.Result{}, nil
} Or as a function: controller.Func(func(o controller.Request) (controller.Result, error) {
// Implement business logic of reading and writing objects here
return controller.Result{}, nil
}) Reconciliation is level-based, meaning action isn't driven off changes in individual Events, but instead is
driven by actual cluster state read from the apiserver or a local cache.
For example if responding to a Pod Delete Event, the Request won't contain that a Pod was deleted,
instead the reconcile function observes this when reading the cluster state and seeing the Pod as missing.
*/
type Reconciler interface {
// Reconcile performs a full reconciliation for the object referred to by the Request.
// The Controller will requeue the Request to be processed again if an error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
Reconcile(context.Context, Request) (Result, error)
}

kubebuilder的处理

kubebuilder会在guestbook_controller.go 中生成一个实现了Reconciler接口的模板:

func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx) // TODO(user): your logic here return ctrl.Result{}, nil
}

那么Reconciler又是怎么和controller关联起来的呢?在上文提到 kubebuilder 会通过Complete(SetupWithManager中调用)创建并添加controller到manager,同时可以看到Complete中传入的就是reconcile.Reconciler接口,这就是controller和Reconciler关联的入口:

func (blder *Builder) Complete(r reconcile.Reconciler) error {
_, err := blder.Build(r)
return err
}

后续会通过: Builder.Build -->Builder.doController-->newController 最终传递给controller的初始化接口controller.New,并赋值给Controller.Do变量。controller.New中创建的controller结构如下,可以看到还为MakeQueue赋予了一个创建workqueue的函数,新事件会缓存到该workqueue中,后续传递给Reconcile进行处理:

	// Create controller with dependencies set
return &controller.Controller{
Do: options.Reconciler,
MakeQueue: func() workqueue.RateLimitingInterface {
return workqueue.NewNamedRateLimitingQueue(options.RateLimiter, name)
},
MaxConcurrentReconciles: options.MaxConcurrentReconciles,
CacheSyncTimeout: options.CacheSyncTimeout,
SetFields: mgr.SetFields,
Name: name,
Log: options.Log.WithName("controller").WithName(name),
RecoverPanic: options.RecoverPanic,
}, nil

上面有讲controller会根据事件来调用Reconciler,那它是如何传递事件的呢?

可以看下Controller的启动接口(Manager.Start中会调用Controller.Start接口),可以看到其调用了processNextWorkItem来处理workqueue中的事件:

func (c *Controller) Start(ctx context.Context) error {
... c.Queue = c.MakeQueue() //通过MakeQueue初始化一个workqueue
... wg := &sync.WaitGroup{}
err := func() error {
...
wg.Add(c.MaxConcurrentReconciles)
for i := 0; i < c.MaxConcurrentReconciles; i++ {
go func() {
defer wg.Done()
for c.processNextWorkItem(ctx) {
}
}()
}
...
}()
...
}

继续查看processNextWorkItem,可以看到该处理逻辑与client-go中的workqueue的处理方式一样,从workqueue中拿出事件对象,然后传递给reconcileHandler

func (c *Controller) processNextWorkItem(ctx context.Context) bool {
obj, shutdown := c.Queue.Get() //获取workqueue中的对象
if shutdown {
// Stop working
return false
} defer c.Queue.Done(obj) ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(1)
defer ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(-1) c.reconcileHandler(ctx, obj)
return true
}

后续会通过Controller.reconcileHandler --> Controller.Reconcile -->Controller.Do.Reconcile 最终将事件传递给Reconcile(自己实现的Reconcile赋值给了controller的Do变量)。

总结一下:kubebuilder首先通过SetupWithManagerReconcile赋值给controller,在Manager启动时会调用Controller.Start启动controller,controller会不断获取其workqueue中的对象,并传递给Reconcile进行处理。

Controller事件来源

上面讲了controller是如何处理事件的,那么workqueue中的事件是怎么来的呢?

回到Builder.Complete-->Builder.build,从上面内容可以知道在doController函数中进行了controller的初始化,并将Reconciler和controller关联起来。在下面有个doWatch函数,该函数中注册了需要watch的对象类型,以及eventHandler(类型为handler.EnqueueRequestForObject),并通过controller的Watch接口启动对资源的监控:

func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, error) {
...
// Set the ControllerManagedBy
if err := blder.doController(r); err != nil {//初始化controller
return nil, err
} // Set the Watch
if err := blder.doWatch(); err != nil {
return nil, err
} return blder.ctrl, nil
}
func (blder *Builder) doWatch() error {
// Reconcile type
typeForSrc, err := blder.project(blder.forInput.object, blder.forInput.objectProjection)//格式化资源类型
if err != nil {
return err
}
src := &source.Kind{Type: typeForSrc} //初始化资源类型
hdler := &handler.EnqueueRequestForObject{} //初始化eventHandler
allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil { //启动对资源的监控
return err
}
...
}

上述的blder.forInput.object就是SetupWithManager中的For的参数(&webappv1.Guestbook{})

func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&webappv1.Guestbook{}).
Complete(r)
}

继续看controller.Watch接口,可以看到其调用了src.Start(src的类型为 source.Kind),将evthdler(&handler.EnqueueRequestForObject{})、c.Qeueue关联起来(c.Qeueue为Reconciler提供参数)

func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate) error {
...
return src.Start(c.ctx, evthdler, c.Queue, prct...)
}

在Kind.Start 中会根据ks.Type选择合适的informer,并添加事件管理器internal.EventHandler

在Manager初始化时(如未指定)默认会创建一个Cache,该Cache中保存了gvkcache.SharedIndexInformer 的映射关系,ks.cache.GetInformer 中会提取对象的gvk信息,并根据gvk获取informer。

在Manager.Start的时候会启动Cache中的informer。

func (ks *Kind) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface,
prct ...predicate.Predicate) error {
...
go func() {
...
if err := wait.PollImmediateUntilWithContext(ctx, 10*time.Second, func(ctx context.Context) (bool, error) {
// Lookup the Informer from the Cache and add an EventHandler which populates the Queue
i, lastErr = ks.cache.GetInformer(ctx, ks.Type)
...
return true, nil
});
...
i.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})
...
}() return nil
}

internal.EventHandler中实现了SharedIndexInformer所需的ResourceEventHandler接口

type ResourceEventHandler interface {
OnAdd(obj interface{})
OnUpdate(oldObj, newObj interface{})
OnDelete(obj interface{})
}

看下EventHandler 是如何将OnAdd监听到的对象添加到队列中的:

func (e EventHandler) OnAdd(obj interface{}) {
...
e.EventHandler.Create(c, e.Queue)
}

可以看到在EnqueueRequestForObject.Create中提取了对象的名称和命名空间,并添加到了队列中:

func (e *EnqueueRequestForObject) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) {
...
q.Add(reconcile.Request{NamespacedName: types.NamespacedName{
Name: evt.Object.GetName(),
Namespace: evt.Object.GetNamespace(),
}})
}

至此将整个Kubebuilder串起来了。

与使用client-go的区别

client-go

在需要操作kubernetes资源时,通常会使用client-go来编写资源的CRUD逻辑,或使用informer机制来监听资源的变更,并在OnAdd、OnUpdate、OnDelete中进行相应的处理。

kubebuilder Operator

从上述讲解可以了解到,Operator一般会涉及两方面:object以及其所有(own)的资源。Reconcilers是核心处理逻辑,但其只能获取到资源的名称和命名空间,并不知道资源的操作(增删改)是什么,也不知道资源的其他信息,目的就是在收到资源变更时,根据object的期望状态来调整资源的状态。

kubebuilder也提供了client库,可以对kubernetes资源进行CRUD操作,但建议这种情况下直接使用client-go进行操作:

package main

import (
"context" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
) var c client.Client func main() {
// Using a typed object.
pod := &corev1.Pod{}
// c is a created client.
_ = c.Get(context.Background(), client.ObjectKey{
Namespace: "namespace",
Name: "name",
}, pod) // Using a unstructured object.
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(schema.GroupVersionKind{
Group: "apps",
Kind: "Deployment",
Version: "v1",
})
_ = c.Get(context.Background(), client.ObjectKey{
Namespace: "namespace",
Name: "name",
}, u)
}

kubebuilder operator的运行逻辑的更多相关文章

  1. 测试.NET core MiddleWare 运行逻辑

    话不多说,直接开整. 首先创建一个.NET CORE web 工程,创建完成之后,会自动创建相关文件如图(本示例基于.NET CORE 3.0): 打开Startup.cs可以看到在Configure ...

  2. beego框架开发投票网站(1) beego基础之运行逻辑

    本文档需结合beego官方文档食用 博主也仅仅是边学边记录,不保证内容的正确性,请当做通俗读物来看待 首先 beego是一个基于go语言的框架 其次 beego是一个mvc框架 框架可以理解为对底层又 ...

  3. python运行逻辑

    Python程序在解释器上执行分两个过程: 编译:如果Python进程在机器上拥有写入权限,那么它会把程序的字节码保存为一个以 .pyc 为扩展名的文件.当程序运行后,会在源代码的同一个目录下看到 . ...

  4. C# Abort() 多线程运行逻辑

    / Thread t ; Thread t2: t.Abort()执行后,会阻止主线程继续运行,但是不会影响t2线程的执行. static void Main(string[] args) { Con ...

  5. scrapy 运行逻辑

    爬虫的步骤:发送请求获得响应→解析并提取数据→保存数据 我们沿用这个朴素的逻辑去理解scrapy 一.发送请求获得响应 1.爬虫发送请求request到引擎 2.引擎将请求request传递给调度器s ...

  6. 使用 kubebuilder 创建并部署 k8s-operator

    一.准备 本文中的示例运行环境及相关软件版本如下: Kubernetes v1.16.3 Go 1.15.6 Kubebuilder 3.1.0 Docker 20.10.7 安装kubebuilde ...

  7. kubebuilder实战之二:初次体验kubebuilder

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. [源码解析] TensorFlow 分布式环境(6) --- Master 动态逻辑

    [源码解析] TensorFlow 分布式环境(6) --- Master 动态逻辑 目录 [源码解析] TensorFlow 分布式环境(6) --- Master 动态逻辑 1. GrpcSess ...

  9. 并发包的线程池第一篇--ThreadPoolExecutor执行逻辑

    学习这个很长时间了一直没有去做个总结,现在大致总结一下并发包的线程池. 首先,任何代码都是解决问题的,线程池解决什么问题? 如果我们不用线程池,每次需要跑一个线程的时候自己new一个,会导致几个问题: ...

随机推荐

  1. 挂载的卸载与运行 关闭selinux

    目录 一:系统目录结构介绍 1 定义挂载设备信息 光驱设备 /cd/cdrom 2 完成设备挂载操作 运行挂载 mount/dev/cdrom /mnt/ 3 检查测试挂载结果 挂载检查 df -h ...

  2. 二进制方式搭建Kubernetes高可用集群

    转:https://jiangxl.blog.csdn.net/article/details/120428703 详细:https://developer.aliyun.com/article/78 ...

  3. APP 性能分析工作台——你的最佳桌面端性能分析助手

    目前 MARS-App 性能分析工作台版本为开发者提供Fastbot桌面版的服务. 旨在帮助开发者们更快.更便捷地开启智能测试之旅,成倍提升稳定性测试的效率. 作者:字节跳动终端技术--王凯 背景 F ...

  4. 开发升讯威在线客服系统启示录:怎样编写堪比 MSDN 的用户手册

    本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程. 免费在线使用 & 免费私有化部署:https://kf.shengxunwei.com 文章目 ...

  5. python办公自动化系列之金蝶K3自动登录(一)

    做办公自动化的小伙伴都知道,驱动SAP GUI我们有SAP原生提供的[脚本录制与回放]以及SAP Scripting API可参考:驱动Office Excel等,我们有微软提供的[录制宏]功能:驱动 ...

  6. Android中四大组件

    Activity  BroadCast Receiver 广播接收者 Service  服务 Content Provider 内容提供者   四大组件都需要在清单文件里面配置一下

  7. Lua 语言: 语法

    转载请注明来源:https://www.cnblogs.com/hookjc/ -- 两个横线开始单行的注释 --[[  加上两个[和]表示     多行的注释.--]] -------------- ...

  8. JS实现new关键字的功能

    一.前言 众所周知:没有对象怎么办?那就new一个! 那么在JS中,当我们new一个对象的时候,这个new关键字内部都干了什么呢? 现在我们就来剖析一下原生JS中new关键字内部的工作原理. 二.原始 ...

  9. ◆JAVA加密解密-3DES

    从数据安全谈起       当你使用网银时,是否担心你的银行卡会被盗用?     当你和朋友用QQ进行聊天时,是否担心你的隐私会被泄露?     作为开发者,编写安全的代码比编写优雅的代码更重要,因为 ...

  10. mybatis 字段类型映射一览表