自己实现一个Controller——精简型
写在最前
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——精简型的更多相关文章
- 自己实现一个Controller——终极型
经过前两篇的学习与实操,也大致掌握了一个k8s资源的Controller写法了,如有不熟,可回顾 自己实现一个Controller--标准型 自己实现一个Controller--精简型 但是目前也只能 ...
- SpringMVC从Controller跳转到另一个Controller
1. 需求背景 需求:spring MVC框架controller间跳转,需重定向.有几种情况:不带参数跳转,带参数拼接url形式跳转,带参数不拼接参数跳转,页面也能显示. 本来以为挺简单的一件事 ...
- SpringMVC实现一个controller写多个方法
MultiActionController与ParameterMethodNameResolver在一个Controller类中定义多个方法,并根据使用者的请求来执行当中的某个方法,相当于Struts ...
- SpringMVC从Controller跳转到另一个Controller(转)
http://blog.csdn.net/jackpk/article/details/44117603 [PK亲测] 能正常跳转的写法如下: return "forward:aaaa/bb ...
- SpringMVC实现一个controller里面有多个方法
我们都知道,servlet代码一般来说只能在一个servlet中做判断去实现一个servlet响应多个请求, 但是springMVC的话还是比较方便的,主要有两种方式去实现一个controller里能 ...
- springMVC一个Controller处理所有用户请求的并发问题(转)
springMVC一个Controller处理所有用户请求的并发问题 有状态和无状态的对象基本概念: 有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的.一 ...
- SpringMVC从Controller跳转到还有一个Controller
1. 需求背景 需求:spring MVC框架controller间跳转,需重定向.有几种情况:不带參数跳转.带參数拼接url形式跳转,带參数不拼接參数跳转,页面也能显示. 本来以为挺简单的一件事情. ...
- springmvc怎么重定向,从一个controller跳到另一个controller
第一种情况,不带参数跳转: 方法一:使用ModelAndView return new ModelAndView("redirect:/toList"); 这样可以重定向到toL ...
- restful风格url Get请求查询所有和根据id查询的合并成一个controller
restful风格url Get请求查询所有和根据id查询的合并成一个controller的方法 原代码 // 127.0.0.1:8080/dep/s @ApiOperation(value=&qu ...
随机推荐
- Use Emacs as Personal Knowledge Base
http://stackoverflow.com/questions/2014636/how-to-maintain-an-emacs-based-knowledge-base
- Qt Designer中自定义控件的使用(提升法与插件法)
准备乱写一点Qt自定义Widget在Designer中的使用.可是又不想重复提升法(promotion)及插件法基本用法,因为Manual中Using Custom Widgets with Qt D ...
- 适配Android10 拍照,相册,裁剪,上传图片
这篇文章主要介绍了适配Android 10(Q)后,调用系统拍照,系统相册,系统裁剪和上传问题,这是一个很常用的功能,但是在Android 6.0,Android 7.0和Android 10.0以上 ...
- JavaScript之BOM和DOM及其兼容操作详细总结
BOM(浏览器对象模型) 所有浏览器都支持window对象,他表示浏览器窗口. 所有js全局对象,函数,变量均自动成为window对象的成员. 全局变量是window对象的属性. 全局函数是windo ...
- IDEA远程调试代码
一.设置远程调式端口 点击Remote 设置名字和要部署的远程服务器IP地址和端口 二.将Jar包上传到远程服务器运行 启动命令 java -Xdebug -agentlib:jdwp=transpo ...
- zookeeper同一台服务器创建伪集群
下载zk wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7 ...
- mysql 优化面试题
第一方面:30种mysql优化sql语句查询的方法 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by涉及的列上建立索引. 2.应尽量避免在 where 子句中使用 ...
- C#多线程---Mutex类实现线程同步
一.例子 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 ...
- C++泛型编程之类模板
泛型语义 泛型(Generic Programming),即是指具有在多种数据类型上皆可操作的含意.泛型编程的代表作品 STL 是一种高效.泛型.可交互操作的软件组件. 泛型编程最初诞生于 C++中, ...
- 学校acm比赛题
这道题 用位运算必然简单 但是苦逼的是自己不熟练 那就 用本办法 输入一个十进制数 转换成二进制翻转 去掉高位的零 然后再转化为十进制 输出! 1 #include<stdio.h> ...