继上次分享Kubernetes源码编译调试之后,一直想写些对scheduler,controller-manager,kubelete等组件的深入介绍,今天先介绍下Controller部分,在kubernetes内部提供了大量的controller,比如node controller,pod controller,endpoint controller等等。这些controller都是由controller-manager进行管理。每个Controller通过API Server提供的接口实时监控整个集群的每个资源对象的当前状态,当发生各种故障导致系统状态发生变化时,会尝试通过CRUD操作将系统状态修复到“期望状态”。
在kubernetes中一切皆资源,Kubernetes 1.7之后,提供了CRD(CustomResourceDefinitions)的自定义资源二次开发能力来扩展kubernetesAPI,通过此扩展,可以向kubernetes API中增加新类型,会比修改kubernetes的源代码或者是创建自定义的API server来的更加的简洁和容易,并且不会随着kuberntetes内核版本的升级,而出现需要代码重新merger的需要,以及兼容性方面的问题。这一功能特性的提供大大提升了kubernetes的扩展能力。
具体使用建议见:请参考Should I add a custom resource to myKubernetes Cluster?
Kube - Controller的内部大致实现逻辑
主要使用到 Informer和workqueue两个核心组件。Controller可以有一个或多个informer来跟踪某一个resource。Informter跟API server保持通讯获取资源的最新状态并更新到本地的cache中,一旦跟踪的资源有变化,informer就会调用callback。把关心的变更的Object放到workqueue里面。然后woker执行真正的业务逻辑,计算和比较workerqueue里items的当前状态和期望状态的差别,然后通过client-go向API server发送请求,直到驱动这个集群向用户要求的状态演化。
具体对client-go的使用,可参考《client-go的使用和源码分析》,在编写自定义的Controller的时候,需要大量使用client-go组件。上图中的蓝色部分全部是client-go已经包含的部分,不需要重新开发可以直接使用。红色的部分是自己的业务逻辑,需要自己开发。可见基于CRD开发一个自定义的资源管理API 来扩展kubernetes的底层能力还是非常简洁的。比如在kubernetes中目前还是没有办法可以做到通过pod直接查询到对应的service是谁。为了实现这个定制化的能力,为上层操作提供接口,就可以来扩展这个能力,写一个pod-service-controller。这个controller中提供两个informer,一个informer关注pod资源,一个informer关注service资源。把各自的变化情况通过回调的方式放到workerqueue中。然后在worker中并发的去处理queue中的item。整理出pod和service的映射关系。这样上层就可以直接通过该controller的RESTful API 查询到他们之间的映射关系。而不需要在上层哐哐的写一堆业务处理逻辑。
下面以sample-controller为例,来讲解开发自定义Controller的关键步骤和注意点。
1. 根据CRD的模板定义出自己的资源管理对象。比如crd.yaml文件
apiVersion:apiextensions.k8s.io/v1beta1
kind:CustomResourceDefinition
metadata:
#名称必须符合下面的格式:
name: foos.samplecontroller.k8s.io
spec:
# REST API使用的组名称:/apis/
group: samplecontroller.k8s.io
# REST API使用的版本号:/apis/
version: v1alpha1
names:
# CamelCased格式的单数类型。在清单文件中使用
kind: Foo
# URL中使用的复数名称:/apis/
plural: foos
# Namespaced或Cluster
scope: Namespaced
validation:
openAPIV3Schema:
properties:
spec:
properties:
replicas:
type: integer
minimum: 1
maximum:10
2. Kubectl create –f crd.yaml 执行完成后就创建了Foo这个资源对象。没有修改任何kubernetes内核代码,仅通过定义CustomResourceDefinition类型的yaml文件就可以直接创建新的资源对象,非常的简单和方便
一旦你创建CRD后,可以使用如下命令来验证CRD是否创建成功
kubectl get crd-o 'custom-columns=NAME:{.metadata.name},ESTABLISHED:{.status.conditions[?(@.type=="Established")].status}'
执行后输出如下:
NAME ESTABLISHED
foos.samplecontroller.k8s.io True
3. 使用Foo这个新创建的类型,创建一个pod。
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
name: example-foo
spec:
deploymentName:example-foo
replicas: 1
使用kubectl create –f example-foo.yaml
NAME READY STATUS RESTARTS AGE
example-foo-86ccd54874-9ctfm 1/1 Running 0 3h
4. Foo类型的Pod对象创建成功后,结合上对Foo类型资源的控制才会让资源对象有意义。所以还需要开发一个自定义的Foo-Controller。
5. 定义一个Controller结构体
6. 构造出workqueue和Informer对象用于初始化Controller对象
7. 通过informer监控资源CRUD的操作的回调函数
8. 启动Controller
在run函数中在worker运行前,必须要等待状态的同步完成。使用go启动多个worker协程并发的从queue中一个个的获取待处理的Item。其中的runwoker是包含真正的业务逻辑的函数。
9.
以上便是一个完整的编写自定义Controller的完整过程。比如推荐大家一个比较好的Controller学习参考实现就是kubewatch,事件探测器。
https://github.com/bitnami-labs/kubewatch
参考:
1. Client-go的使用和源码分析
http://www.huweihuang.com/article/source-analysis/client-go-source-analysis/
2. 使用 client-go 控制原生及拓展的 KubernetesAPI
https://www.kubernetes.org.cn/1309.html