标准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. 删除EFI系统分区(ESP)后Windows无法启动,重建引导分区并修复启动的过程

    @ 目录 一.开机故障描述 二.工具: 三.什么是EFI系统分区 四.如何查看EFI系统分区 五.删除后如何重建系统分区 1.建立未分配空间 2.建立ESP分区 3.按下图,ESP分区的大小200M即 ...

  2. 【笔记】F1 score

    F1 score 关于精准率和召回率 精准率和召回率可以很好的评价对于数据极度偏斜的二分类问题的算法,有个问题,毕竟是两个指标,有的时候这两个指标也会产生差异,对于不同的算法,精准率可能高一些,召回率 ...

  3. 【笔记】初探KNN算法(1)

    KNN算法(1) 全称是K Nearest Neighbors k近邻算法: 思想简单 需要的数学知识很少 效果不错 可以解释机器学习算法使用过程中的很多细节问题 更加完整的刻画机器学习应用的流程 其 ...

  4. Java异常处理的两种方式以及自定义异常的使用方法

    异常 就是程序出现了不正常的情况 Error:严重问题,不需要处理 Exception:称为异常类,他表示程序本身可以处理的问题 RuntimeException:在编译期是不检查的,出现问题后,需要 ...

  5. NOIP 模拟 $15\; \text{夜莺与玫瑰}$

    题解 一道很妙的题,让求对于一个矩阵中,两点相连成线,有多少条直线,他们的交集是有限集. 转化一下题目,发现水平和竖直的只有 \(n+m\) 条,而左斜和右斜的条数是相同的,所以我们只需求出左或右中的 ...

  6. C#多线程实践-锁和线程安全

    锁实现互斥的访问,用于确保在同一时刻只有一个线程可以进入特殊的代码片段,考虑下面的类: class ThreadUnsafe { static int val1, val2; static void ...

  7. C++ 保存读取二进制

    一.保存二进制 #include <iostream> #include <fstream> int main(){ float* output = new float[100 ...

  8. uwp 的work project 的 取消闹钟

    private void initalAlarmHanle() { string cancelAlarm = "CancelAlarmEvent"; ConnectionManag ...

  9. 【java虚拟机】Java内存模型

    作者:平凡希 原文地址:https://www.cnblogs.com/xiaoxi/p/7518259.html 一.什么是Java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Jav ...

  10. Javascript - Vue - 在vscode里使用webpack

    cnpm(node package manager)和webpack模块 npm是运行在node.js环境下的包管理工具,使用npm可以很快速的安装前端文件里需要依赖的那些项目文件,比如js.css文 ...