写在最前

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. 在STM32F401上移植uC/OS的一个小问题 [原创]

    STM32F401xx是意法半导体新推出的Cortex-M4内核的MCU,相较于已经非常流行的STM32F407xx和STM32F427xx等相同内核的MCU而言,其特点是功耗仅为128uA/MHz, ...

  2. linux copy_id

    ssh-keygen 产生公钥与私钥对. ssh-copy-id 将本机的公钥复制到远程机器的authorized_keys文件中,ssh-copy-id也能让你有到远程机器的home, ~./ssh ...

  3. Notes about WindowPadX

    WindowPadX乃一Autohotkey脚本,具有强大的单/多显示器窗口排布能力且易于配置.有了它,那些Pro版收费的.需要安装的DisplayFusion, MultiMon TaskBar, ...

  4. 华为应用市场更新APP多次被拒

    最近公司的APP发布了新版本,只进行了线上bug的修复,基本没改什么主体业务功能.各大应用市场都顺利更新上架,但是国货之光华为,被闷了几次.拒来拒去,就是那些反复的内容.内容一般如下: 经检测发现,您 ...

  5. 消息协议AMQP 与 JMS对比

    https://blog.csdn.net/hpttlook/article/details/23391967 https://www.jianshu.com/p/6e6821604efc https ...

  6. Pikachu-Over Permission模块

    一.概述 如果使用A用户的权限去操作B用户的数据,A的权限小于B的权限,如果能够成功操作,则称之为越权操作. 越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的. 一般越权漏洞容易出现在权限页 ...

  7. Sqli-Labs less29-31

    Less-29 可以从介绍上看出,第29关被称为世界上最好的WAF,网上许多讲解的办法就是和第一关差不多,其实是不对的. sqli-labs文件夹下面还有tomcat文件,这才是真正的less,里面的 ...

  8. 解决Git中fatal: refusing to merge unrelated histories

    原文链接: https://blog.csdn.net/wd2014610/article/details/80854807 Git的报错 在使用Git的过程中有时会出现一些问题,那么在解决了每个问题 ...

  9. 图解Win 10 应用开发之Sqlite 数据库的简单用法

    尽管目前 UWP-RT 库中还没有自带操作Sqlite数据库的API,不过,真要使用的话也不难,因为通过 Nuget ,我们其实可以获取很多支持 Sqlite 操作的第三方组件,当然了,组件虽多,但不 ...

  10. The Programmer's Oath程序员的誓言----鲍勃·马丁大叔(Bob Martin)

    In order to defend and preserve the honor of the profession of computer programmers, I Promise that, ...