写在最前

controller-manager作为K8S master的其中一个组件,负责众多controller的启动和终止,这些controller负责监控着k8s中各种资源,执行调谐,使他们的实际状态能不断趋近与期望状态。这些controller包括servercontroller,nodecontroller,deploymentcontroller等。对于自定义资源(CRD)也需要为之配备controller,CRD的controller也需要有controller-manager启动之,停止它。整个过程篇幅较长,故鄙人将其拆分成多篇,通过本系列,首篇将介绍如何定义一个controller-manager去启动它所管辖的controller,并实现一个最简单的controller。第二篇再实现一个较为标准的controller,并介绍informer的结构;最后一篇将介绍不借助脚手架如何实现一个CRD的controller。

介绍一下整个项目的结构

controller-demo
|---api //用于放定义CRD各个属性的struct
|---v1
|---client
|---versiond
|----scheme //用于存放CRD的scheme
|----typed //用于存放CRD对应的client
|---controller //用于存放各个controller
|---informers //用于存放informer,包含各个apiGroup各个version及一个factory
|---ecsbind/v1 //其中一个apiGroup,其中一个version的informer,当然也是唯一一个
|---internalinterfaces //informer的interface接口
|---listers //用于存放lister,包含各个apiGroup各个version
|---ecsbind/v1 //其中一个apiGroup,其中一个version的informer,同样也是唯一一个

controller-manager

controller有两个函数,一个负责供main函数调用启动controller-manager,作为controller-manager的入口;另一个是用于启动他所管理的所有controller。

供main函数调用的Run函数定义如下

func Run(stopCh <-chan struct{}) error {
run :=func(stopCh <-chan struct{}){
err := StartController(stopCh)
if err != nil {
glog.Fatalf("error running service controllers: %v", err)
}
select {}
}
///忽略leader选举的相关逻辑
......
run(stopCh) panic("unreachable")
}

上述函数传入一个通道,用于传递给各个controller一个终止的信号,函数里定义了一个run的函数,用于调用StartController,之所以需要定义一个run函数,是因为一般这类的组件虽然为了高可用会运行多个副本,但是仅有一个副本是真正运行,其他的副本是作为待命状态运行,而这个真正运行的副本称为leader,从普通副本中通过资源争夺称为leader的过程称为leader选举,仅有leader挂掉了,剩余的副本再进行一次leader选举成为新leader。当然也可进行leader选举模式运行。因此Run函数中应该包含是否进行leader选举,若是则执行leader选举的逻辑,当选成leader才执行run函数;如果不进行leader选举则直接执行run。不过这段逻辑被省略了。

controller-manager的另一个函数是真正启动各个controller。StartController同样接收了从Run函数传过来的通道,这个通道最终转给各个controller,传递停止的信号。在函数中会构造各个controller,通过开辟一个协程调用controller的Run方法将controller启动,代码如下所示

unc StartController(stopCh <-chan struct{}) error {
cfg, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")
if err != nil {
glog.Fatalf("error building kubernetes config:%s", err.Error())
}
kubeClient, err := kubernetes.NewForConfig(cfg)
factory := informers.NewSharedInformerFactory(kubeClient, 0) podInformer:=factory.Core().V1().Pods()
pc:=controller.NewPodController(kubeClient,podInformer,"k8s-cluster")
go pc.Run(stopCh) factory.Start(stopCh)
return nil
}

controller.NewPodController是构造了一个PodController,构造PodController时所需要的kubeClient,informer需要预先构造。对于k8s原有的资源,其informer都可以通过SharedInformerFactory获得,通过协程执行 pc.Run(stopCh)后,也需要执行factory.Start(stopCh),factroy.Start需要等各个controller Run了之后方可执行,否则对应Controlle则会没有运行效果。

一个精简的Controller

这个controller的作用是统计集群中所有pod的数量,然后将pod的总数写到master的某个label上,且被统计过的pod都会在它的event中产生一条新记录来表明此pod被统计过。

罗列一下这个podcontroller结构的字段

type PodController struct {

	kubeClient       kubernetes.Interface    //用于给master打label
clusterName string
podLister corelisters.PodLister //用于获取被监控的pod资源
podListerSynced cache.InformerSynced //用于同步cache
broadcaster record.EventBroadcaster //用于广播事件
recorder record.EventRecorder //用于记录pod的event
}

Controller的构造函数如下

func NewPodController(kubeClient kubernetes.Interface,podInformer coreinformers.PodInformer,clusterName string)*PodController  {

	eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof)
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "pod_controller"}) rc:=&PodController{
kubeClient:kubeClient,
clusterName:clusterName,
podLister:podInformer.Lister(),
podListerSynced:podInformer.Informer().HasSynced,
broadcaster:eventBroadcaster,
recorder:recorder,
}
return rc
}

controller的各个属性中,除了broadcaster和recorder是自身构造外,其余都是通过参数传入。由于controller中需要用到事件记录,提供这一功能的是recorder,然而触发了事件需要将其散播出去给订阅者的需要一个broadcaster,这里涉及到k8s的事件机制,并不打算在细述。在构造函数中已经把事件广播绑定到glog.Infof,也就是说recorder触发了事件,会在日志中输出事件的信息。后面在Run controller的时候还会用到这个广播器。

整个Controller的启动方法如下

func (p *PodController)Run(stopCh <-chan struct{})  {
glog.Info("Starting pod controller\n")
defer glog.Info("Shutting down pod controller\n") if !controller.WaitForCacheSync("pod", stopCh, p.podListerSynced) {
return
} if p.broadcaster != nil {
p.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(p.kubeClient.CoreV1().RESTClient()).Events("")})
} go wait.NonSlidingUntil(func() {
if err := p.reconcilePods(); err != nil {
glog.Errorf("Couldn't reconcile pod: %v", err)
}
}, metav1.Duration{Duration: 10 * time.Second}.Duration, stopCh) <-stopCh
}

WaitForCacheSync用于同步指定资源的缓存,这个缓存后续会使用到,万一同步失败的话controller将不能运行

StartRecordingToSink用于把事件广播到apiserver中,如果此处不执行,即便是recorder触发了事件,apiserver没收到这个事件,最终事件信息没保存到对应pod中,我们通过kubectl describe po时就会看不到相应的event记录

通过启动一个协程去定期执行reconcilePods()方法,NonSlidingUntil函数被调用后马上执行传进去的func,后续每隔10秒就重复执行一次,直到收到stopCh通道传来的终止信号才停止。

最后通过等待接收stopCh传来的信号而阻塞当前协程,从而阻止了完成本函数的调用

reconcilePods方法的大致逻辑如前所述,经过多次调用从lister中获取所有pod,遍历每个pod把pod的命名空间和pod的名称打印出来,然后通过labelselector找出集群中的master节点将pod的数量打到名为hopegi/pod-count的label上,最后给每个pod的event事件添加一条pod count is n(这个n是pod的总数)这样的记录。

func (p *PodController)reconcilePods()error  {
glog.Infof("reconcilePods ")
pods,err:= p.podLister.List(labels.Everything())
if err!=nil{
return fmt.Errorf("error listing pods: %v", err)
}
return p.reconcile(pods)
} func (p *PodController)reconcile(pods []*v1.Pod)error {
glog.Infof("reconcile pods")
for _,pod :=range pods{
fmt.Printf("pod name is %s.%s \n",(*pod).Namespace,(*pod).Name) }
nodes,err:= p.kubeClient.CoreV1().Nodes().List(metav1.ListOptions{LabelSelector:"node-role.kubernetes.io/master"})
if err!=nil{
glog.Infof("get master error %v\n",err)
return err
}
for _,n:=range nodes.Items{ n.Labels["hopegi/pod-count"]=fmt.Sprintf("%d",len(pods))
_,err= p.kubeClient.CoreV1().Nodes().Update(&n)
if err!=nil{
glog.Infof("label node error:%v ",err)
}
}
if p.recorder!=nil {
msg:=fmt.Sprintf("pod count is %d",len(pods))
for _, pod := range pods {
p.recorder.Eventf(&v1.ObjectReference{
Kind:"Pod",
Name:pod.Name,
UID:pod.UID,
Namespace:pod.Namespace,
},v1.EventTypeNormal,"SuccessCalculatePod",msg)
}
}
return nil
}

获取集群里所有的pod用的是lister而不是通过kubeclient去获取差别在于,作为某个K8S资源的informer(本例中是pod),它内部都有一个对应资源的缓存,这个缓存在listAndWatch机制的作用下与集群中存储的pod数据保持一致,这个listAndWatch将在下一篇中介绍;后者是每访问一次都会往apiserver中发一次请求,在众多controller频繁地跟apiserver通讯,apiserver会不堪重负,且消耗大量网络资源,获取效率也低下。

小结

本篇简单的实现了一个controller,虽然它并没有如开篇所说的那样对资源的实际状态与期望状态的差异进行调谐,通过最简单的方式周期性地检查pod的状态,引入了informer,获取了它listAndWatch的结果,后续将介绍这个informer的机制,它如何执行listAndWatch,一个较为常见的标准的Controller是如何实现的。

自己实现一个Controller——精简型的更多相关文章

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

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

  2. SpringMVC从Controller跳转到另一个Controller

    1. 需求背景   需求:spring MVC框架controller间跳转,需重定向.有几种情况:不带参数跳转,带参数拼接url形式跳转,带参数不拼接参数跳转,页面也能显示. 本来以为挺简单的一件事 ...

  3. SpringMVC实现一个controller写多个方法

    MultiActionController与ParameterMethodNameResolver在一个Controller类中定义多个方法,并根据使用者的请求来执行当中的某个方法,相当于Struts ...

  4. SpringMVC从Controller跳转到另一个Controller(转)

    http://blog.csdn.net/jackpk/article/details/44117603 [PK亲测] 能正常跳转的写法如下: return "forward:aaaa/bb ...

  5. SpringMVC实现一个controller里面有多个方法

    我们都知道,servlet代码一般来说只能在一个servlet中做判断去实现一个servlet响应多个请求, 但是springMVC的话还是比较方便的,主要有两种方式去实现一个controller里能 ...

  6. springMVC一个Controller处理所有用户请求的并发问题(转)

    springMVC一个Controller处理所有用户请求的并发问题 有状态和无状态的对象基本概念: 有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的.一 ...

  7. SpringMVC从Controller跳转到还有一个Controller

    1. 需求背景 需求:spring MVC框架controller间跳转,需重定向.有几种情况:不带參数跳转.带參数拼接url形式跳转,带參数不拼接參数跳转,页面也能显示. 本来以为挺简单的一件事情. ...

  8. springmvc怎么重定向,从一个controller跳到另一个controller

    第一种情况,不带参数跳转: 方法一:使用ModelAndView return new ModelAndView("redirect:/toList");  这样可以重定向到toL ...

  9. restful风格url Get请求查询所有和根据id查询的合并成一个controller

    restful风格url Get请求查询所有和根据id查询的合并成一个controller的方法 原代码 // 127.0.0.1:8080/dep/s @ApiOperation(value=&qu ...

随机推荐

  1. javaScript学习DOM模型

    DOM 全称是 Document Object Model 文档对象模型大白话,就是把文档中的标签,属性,文本,转换成为对象来管理                                   ...

  2. 阿里面试Redis常考问题

    一提到Redis缓存,我们不得不了解的三个问题就是:缓存雪崩.缓存击穿和缓存穿透.这三个问题一旦发生,会导致大量的请求直接请求到数据库层.如果并发压力大,就会导致数据库崩溃.那p0级的故障是没跑了. ...

  3. 时间转换 BASIC-14

    时间转换 给定一个以秒为单位的时间t,要求用"::"的格式来表示这个时间.表示时间,表示分钟,而表示秒,它们都是整数且没有前导的"0".例如,若t=0,则应输出 ...

  4. kong整合Cosul实践(3)

    1. 准备工作: 需要.netcore或者java程序一套,引入consul第三方包 我这里搭建了一个.netcore的webapi项目,引入Consul第三方包,网上可查资料一堆 环境需要kong, ...

  5. PXE高效批量装机

    目录 一.PXE概述 二.PXE的优点 三.搭建PXE的前提 四.搭建PXE远程安装服务器 4.1.安装并启用TFTP服务 4.2.安装dhcp服务 4.3.准备linux内核.初始化镜像文件 4.3 ...

  6. Elasticsearch IK分词器

    Elasticsearch-IK分词器 一.简介 因为Elasticsearch中默认的标准分词器(analyze)对中文分词不是很友好,会将中文词语拆分成一个一个中文的汉字,所以引入中文分词器-IK ...

  7. QT中的对象模型――QPointer

    QPointer是一个模板类,为QObject对象提供了守卫指针(Guarded Pointer).什么是守卫指针?守卫指针QPointer<T>类似于普通C++指针T *,有且仅有一点不 ...

  8. Redis 在项目中合理使用经验总结

    转自:https://my.oschina.net/u/920698/blog/3031587 背景 Redis 是一个开源的内存数据结构存储系统. 可以作为数据库.缓存和消息中间件使用. 支持多种类 ...

  9. [ASP.NET MVC]@RenderSection,@RenderBody(),@RenderPage

    1.@RenderBody()  作用和母版页中的服务器控件类似,当创建基于此布局页面的视图时,视图的内容会和布局页面合并,而新创建视图的内容会通过布局页面的@RenderBody()方法呈现在标签之 ...

  10. Mybatis出现错误org.apache.ibatis.executor.ExecutorException: No constructor found in

    错误显示没有发现构造器. 其实就是重写了构造器后,忘了补写一个默认的空参构造器了.此类的错误还经常出现在spring等这种大量使用反射的框架中.因为这些框架在调用反射的类后会默认调用默认的构造器 解决 ...