标准Controller

上一篇通过一个简单的例子,编写了一个controller-manager,以及一个极简单的controller。从而对controller的开发有个最基本的认识,但是细心观察前一篇实现的controller仅仅是每次全量获取了所有资源,虽然都是从缓存中获取速度是比较快的,如果单次处理一个资源时的时间比较长,而且没必要每次都把所有资源都扫描一遍,上一篇实现的controller显然不符合使用场景了,本篇将继续用另一种方式开发一个结构较为标准的Controller,并介绍支撑controller功能实现的Informer架构。

先介绍一下本次实现的controller需要实现的功能,本controller通过监控Node节点的新增,发现新增是通过特定方式加入集群的,就给该node打上label。

看下本次controller的结构包含的成员

type NodeController struct {
kubeClient *kubernetes.Clientset //用于给符合条件的node打标记用
nodeLister corelisters.NodeLister //用于获取被监控的node资源
nodeListerSynced cache.InformerSynced
nodesQueue workqueue.DelayingInterface //一个延时队列,用于记录需要controller的node的key
cloudProvider cloudproviders.CloudProvider //用于判定node是否符合条件打标记,此成员并非controller关键结构的成员
}

下面是Controller的构造函数

func NewNodeController(kubeClient *kubernetes.Clientset, nodeInformer coreinformers.NodeInformer, cp cloudproviders.CloudProvider) *NodeController {

	n := &NodeController{
kubeClient: kubeClient,
nodeLister: nodeInformer.Lister(),
nodeListerSynced: nodeInformer.Informer().HasSynced,
nodesQueue: workqueue.NewNamedDelayingQueue("nodes"),
cloudProvider: cp,
} nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(cur interface{}) {
node := cur.(*v1.Node)
fmt.Printf("controller: Add event, nodes [%s]\n", node.Name)
n.syncNodes(node)
},
DeleteFunc: func(cur interface{}) {
node := cur.(*v1.Node)
fmt.Printf("controller: Delete event, nodes [%s]\n", node.Name)
n.syncNodes(node)
},
}) return n
}

传入构造函数的Informer除了提供Lister和HasSynced函数以外,多了一个事件注册的操作,注册了node资源的Add事件和Delete事件。这个AddEventHandler除了注册这两种事件外,还可以注册Update事件,由于在这次例子中不需要处理这方面的事件,因此没有用上。此处通过Informer的事件回调实现了哪个资源有变更,就会触发事件,通知到controller来,与前一个controller相比这种就能立马定位到有变更的资源,无需将资源全量扫描才找到对应的资源。代码中两个事件处理函数都是获取到变更的node,打印出node的名字和变更事件,最后调用syncNodes方法

func (n *NodeController) syncNodes(node *v1.Node) {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(node)
if err != nil {
fmt.Printf("Couldn't get key for object %#v: %v \n", node, err)
return
}
fmt.Printf("working queue add node %s\n", key)
n.nodesQueue.Add(key)
}

syncNodes单纯给资源求了一个key,再把这个key塞到workqueue中。接收informer传递过来的变更事件处理也就此结束。

然后到启动controller的Run方法

func (n *NodeController) Run(stopCh <-chan struct{}) {

	defer runtime.HandleCrash()
defer n.nodesQueue.ShutDown() fmt.Println("Starting service controller")
defer fmt.Println("Shutting down service controller") if !cache.WaitForCacheSync(stopCh, n.nodeListerSynced) {
runtime.HandleError(fmt.Errorf("time out waiting for caches to sync"))
return
} for i := 0; i < WorkerCount; i++ {
go wait.Until(n.worker, time.Second, stopCh)
} <-stopCh
}
func (n *NodeController) worker() {
for {
func() {
key, quit := n.nodesQueue.Get()
if quit {
return
}
defer n.nodesQueue.Done(key)
err := n.handleNode(key.(string))
if err != nil {
fmt.Printf("controller: error syncing node, %v \n", err)
}
}()
}
}

接收终止信号和缓存处理与之前的controller没多大区别,区别就在于启动了若干个worker协程,worker方法就从workqueue中取出key,然后处理node

func (n *NodeController) handleNode(key string) error {
_, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
return err
}
node, err := n.nodeLister.Get(name)
switch {
case errors.IsNotFound(err):
fmt.Printf("Node has been deleted %v \n", key)
return nil
case err != nil:
fmt.Printf("Unable to retrieve node %v from store: %v \n", key, err)
n.nodesQueue.AddAfter(key, time.Second*30)
return err
default:
err := n.processNodeAddIntoCluster(node)
if err != nil {
n.nodesQueue.AddAfter(key, time.Second*30)
}
return err
}
return nil
}

由于workqueue里存的也只是代表资源的key,此时需要将key转换回资源的name,还是通过lister从cache中把资源取出。当取不出来,就表明资源已经被删除了,如果有相应的删除相关的逻辑就可以在此处执行;如果能取出来,当前的资源已经是最新版本的资源(更新时会有新旧两个版本的资源,这里拿到的是新版本的),当然在本例中没有更新这个事件的处理;整个过程中出现了非期望的错误(如因资源被删除导致的IsNotFound error),处理逻辑需要重试,重试的方式就是将延时重新入队,延时队列的作用在此处得以发挥,因为有可能出现这种极端场景:当前只有一个资源需要被处理,而且该资源在刚创建的时候因为其状态未就绪确实会一直处理失败,假设当其被处理失败时马上又重新入队,那整个controller就会陷入一个类似于死循环的状态中,直到资源状态就绪,这样会浪费不少计算资源。

最后的processNodeAddIntoCluster就是给node打上label并调用kubeclient更新之,这里就不展示了。

看到上面使用了workqueue,颇有生产者消费者的模式,从informer的事件处理函数中获取到变更的资源将其放入队列,这个是生产者;worker处理方法从workqueue里取出变更资源处理,这个是消费者。使用了这个workqueue而不是直接在事件处理函数直接处理的目的在于事件触发的速度与资源处理的速度不一致,有workqueue在其中起到缓冲作用,免除了因实际处理变更的逻辑造成了事件触发方的阻塞导致影响了事件的实时性。

类似的队列和生产者消费者模式在informer中也有出现,作为controller处理资源变更调谐资源状态的上游,它又如何提供及时的变更事件给下游的controller,以及给下游的controller提供与apiserver一致的资源缓存。

Informer机制

Informer机制架构如下图所示



从该架构中还涉及了几个相关组件:

  • Reflector:用于监控apiserver相关资源的变化,及时把变更获取回来,触发相关的变更事件,变更的资源存放到Delta FIFO里面;
  • Delta FIFO:一个存放资源变更的FIFO队列,里面元素是以 Add/Update/Delete 为key,变更的资源为value这样的一个对象;
  • Indexder:用于存放从Delta FIFO队列取出后,经过处理好的值,是apiserver的一个缓存,无需每次从apiserver以及Etcd中获取,减轻两者的压力,里面的缓存应与apiserver(实质是Etcd)保持一致。

从上面的结构中,尤其是Delta FIFO,模式与上面使用了WorkQueue的controller是一致的,生产者消费者模式:

  • 生产者方面:主要操作由Reflector执行,它主要依靠ListAndWatch方法对apiserver的资源进行监控,ListAndWatch方法中就调用了由Informer提供的List方法和Watch方法,一旦获得变更的资源,就将资源及其变更的方式(Add,Update,Delete)一同存入Delta FIFO,如上面介绍Delta FIFO时所述;
  • 消费者方面:主要操作由Controller执行,它主要调用Delta FIFO的Pop方法,从队列中取出变更的资源及其变更方式,调用之前注册到Informer里面相关的事件(即例子中NewNodeController函数中AddEventHandler方法注册进去的事件处理函数),将变更分发出去,在这里Delta FIFO除了记录变更的资源本身数据,也附带记录变更方式的作用体现出来了。最后将这个资源缓存到Indexer中,缓存时也是通过变更方式对缓存进行操作,比如是Add变更,则直接往缓存中Add一个记录;是Update则把缓存记录更新;是Delete则删除记录。这也是另一个体现记录变更方式的地方。

Informer和自定义的Controller(这个controller并非消费者的controller)的交互主要是两处,其一是AddEventHandler时,把变更资源收到自己的workerqueue中供后续执行调谐操作;其二是在调谐期间从Indexer(这个并非是直接调用,是通过调用Informer时嵌套调用的)获取缓存的资源值。整个过程所有涉及的成员如下图所示

小结Informer

Informer在整个过程中起到衔接各方的作用。Reflector对apiserver的监控是由Informer提供的ListAndWatch方法,资源从apiserver接口拿回来资源数据由注册进去Informer的反序列化器进行反序列化(这部分跟Schema有关)。而数据进入Indexer缓存前,的事件分发也是通过Informer调用各个已经注册上来的事件处理函数。

了解了Informer机制,后面则进行自定义资源的Controller开发,里面还需要给自定义资源扩展一个Informer,及封装跟apiserver访问自定义资源的client,实现完整的手捏Controller.

自己实现Controller——标准型的更多相关文章

  1. 自己实现一个Controller——终极型

    经过前两篇的学习与实操,也大致掌握了一个k8s资源的Controller写法了,如有不熟,可回顾 自己实现一个Controller--标准型 自己实现一个Controller--精简型 但是目前也只能 ...

  2. POCO Controller 你这么厉害,ASP.NET vNext 知道吗?

    写在前面 阅读目录: POCO 是什么? 为什么会有 POJO? POJO 的意义 POJO 与 PO.VO 的区别 POJO 的扩展 POCO VS DTO Controller 是什么? 关于 P ...

  3. 尝试asp.net mvc 基于controller action 方式权限控制方案可行性

    微软在推出mvc框架不久,短短几年里,版本更新之快,真是大快人心,微软在这种优秀的框架上做了大量的精力投入,是值得赞同的,毕竟程序员驾驭在这种框架上,能够强力的精化代码,代码层次也更加优雅,扩展较为方 ...

  4. iOS controller解耦探究实现——第一次写博客

    大学时曾经做过android的开发,目前的工作是iOS的开发.之前自己记录东西都是通过自己比较喜欢的笔记类的应用记录下了.直到前段时一个哥们拉着我注册了一个博客.现在终于想明白了,博客这个东西受众会稍 ...

  5. angularjs 1 开发简单案例(包含common.js,service.js,controller.js,page)

    common.js var app = angular.module('app', ['ngFileUpload']) .factory('SV_Common', function ($http) { ...

  6. ASP.NET Core MVC 中的 [Controller] 和 [NonController]

    前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...

  7. ASP.NET MVC 5 Web编程3 -- Controller的应用及扩展

    Controller基础 一. 访问修饰符 1.1 类的访问修饰符 Controller类的访问修饰符必须是public,url才能被拦截. internal能编译通过,但无法拦截url请求.priv ...

  8. DAO层,Service层,Controller层、View层 的分工合作

    DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口 ...

  9. iOS实现UICollectionViewDataSource与Controller的分离

    之前每次用到UICollectionView的时候都会都需要在Controller里面去实现DataSource & Delegate方法 单独Delegate方法还好不是很多, 但是再加上D ...

随机推荐

  1. SaToken学习笔记-01

    SaToken学习笔记-01 SaToken版本为1.18 如果有排版方面的错误,请查看:传送门 springboot集成 根据官网步骤maven导入依赖 <dependency> < ...

  2. 老杜告诉你java小白到大神是怎么炼成的(转载)

    老杜告诉你java小白到大神是怎么炼成的 1. 学习前的准备 一个好的学习方法(应该怎么学习更高效): 一个合格的程序员应该具备两个能力 有一个很好的指法速度(敲代码快) 有一个很好的编程思想(编程思 ...

  3. Solon 1.5.24 发布

    本次版本主要变化: 修复 solon.extend.sessionstate.jwt 在特定场景下会无限次解析的问题 优化 solon.extend.cors 对头信息的处理 插件 solon.boo ...

  4. MySQL-20-MySQL优化

    MySQL优化哲学 1 为什么优化? 为了获得成就感? 为了证实比系统设计者更懂数据库? 为了从优化成果来证实优化者更有价值? 但通常事实证实的结果往往会和你期待相反!优化有风险,涉足需谨慎! 2 优 ...

  5. Mysql中的Join详解

    一.Simple Nested-Loop Join(简单的嵌套循环连接) 简单来说嵌套循环连接算法就是一个双层for 循环 ,通过循环外层表的行数据,逐个与内层表的所有行数据进行比较来获取结果,当执行 ...

  6. NOIP 模拟 $16\; \rm God Knows$

    题解 \(by\;zj\varphi\) 对于这道题,不难想到可以用 \(dp\),就是求一个最小权极长上升子序列 设 \(dp_i\) 表示最后一个选 \(i\) 时,覆盖前 \(i\) 条边的最小 ...

  7. SpringBoot-AOP记录操作日志

    package com.meeno.inner.oa.extend.operaterecord.aop; import com.alibaba.fastjson.JSONArray; import c ...

  8. 测试框架unit之assertion断言使用详解

    NUnit是.Net平台的测试框架,广泛用于.Net平台的单元测试和回归测试中,下面我们用示例详细学习一下他的使用方法 任何xUnit工具都使用断言进行条件的判断,NUnit自然也不例外,与其它的xU ...

  9. final、finally与finalize的区别?

    一.final.finally与finalize的区别 final:final是一个修饰符,可以修饰类,方法和变量.final修饰类表示类不能被其它类继承,并且该类中的所有方法都会隐式的被final修 ...

  10. 【ArcEngine】AE连接SDE_For_SQLServer参数设置

    SDE for sqlserver直连的ArcEngine访问 Ae中的数据的连接实质还是采用服务连接的方式.连接代码如下: 1 public IWorkspace Getworkspace() 2 ...