k8s自定义controller设计与实现 创建CRD
登录可以执行kubectl命令的机器,创建student.yaml
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: # metadata.name的内容是由"复数名.分组名"构成,如下,students是复数名,bolingcavalry.k8s.io是分组名 name: students.bolingcavalry.k8s.io spec: # 分组名,在REST API中也会用到的,格式是: /apis/分组名/CRD版本 group: bolingcavalry.k8s.io # list of versions supported by this CustomResourceDefinition versions: - name: v1 # 是否有效的开关. served: true # 只有一个版本能被标注为storage storage: true # 范围是属于namespace的 scope: Namespaced names: # 复数名 plural: students # 单数名 singular: student # 类型名 kind: Student # 简称,就像service的简称是svc shortNames: - stu
在student.yaml所在目录执行命令kubectl apply -f student.yaml,即可在k8s环境创建Student的定义,今后如果发起对类型为Student的对象的处理,k8s的api server就能识别到该对象类型了
创建Student对象前面的步骤使得k8s能识别Student类型了,接下来创建Students对象
创建object-student.yaml文件
apiVersion: bolingcavalry.k8s.io/v1 kind: Student metadata: name: object-student spec: name: "张三" school: "深圳中学"
在object-student.yaml文件所在目录执行命令kubectl apply -f object-student.yaml,会看到提示创建成功
执行命令kubectl get stu可见已创建成功的Student对象
至此,自定义API对象(也就是CRD)就创建成功了,此刻我们只是让k8s能识别到Student这个对象的身份,但是当我们创建Student对象的时候,还没有触发任何业务(相对于创建Pod对象的时候,会触发kubelet在node节点创建docker容器)
自动生成代码 为什么要做controller如果仅仅是在etcd保存Student对象是没有什么意义的,试想通过deployment创建pod时,如果只在etcd创建pod对象,而不去node节点创建容器,那这个pod对象只是一条数据而已,没有什么实质性作用,其他对象如service、pv也是如此。
controller的作用就是监听指定对象的新增、删除、修改等变化,针对这些变化做出相应的响应(例如新增pod的响应为创建docker容器)
如上图,API对象的变化会通过Informer存入队列(WorkQueue),在Controller中消费队列的数据做出响应,响应相关的具体代码就是我们要做的真正业务逻辑。
自动生成代码是什么从上图可以发现整个逻辑还是比较复杂的,为了简化我们的自定义controller开发,k8s的大师们利用自动代码生成工具将controller之外的事情都做好了,我们只要专注于controller的开发就好。
开始实战接下来要做的事情就是编写API对象Student相关的声明的定义代码,然后用代码生成工具结合这些代码,自动生成Client、Informet、WorkQueue相关的代码;
在$GOPATH/src目录下创建一个文件夹k8s_customize_controller
进入文件夹执行如下命令创建三层目录
mkdir -p pkg/apis/bolingcavalry
在新建的bolingcavalry目录下创建文件register.go
package bolingcavalry const( GroupName = "bolingcavalry.k8s.io" Version = "v1" )
在新建的bolingcavalry目录下创建名为v1的文件夹
在v1文件夹下创建文件doc.go
package v1上述代码中的两行注释,都是代码生成工具会用到的,一个是声明为整个v1包下的类型定义生成DeepCopy方法,另一个声明了这个包对应的API的组名,和CRD中的组名一致;
在v1文件夹创建文件types.go,里面定义Student对象的具体内容
package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // +genclient // +genclient:noStatus // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type Student struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec StudentSpec `json:"spec"` } type StudentSpec struct { name string `json:"name"` school string `json:"school"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // StudentList is a list of Student resources type StudentList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata"` Items []Student `json:"items"` }从上述源码可见,Student对象的内容已经被设定好,主要有name和school这两个字段,表示学生的名字和所在学校,因此创建Student对象的时候内容就要和这里匹配了;
在v1目录下创建register.go文件,此文件的作用是通过addKnownTypes方法使得client可以知道Student类型的API对象
package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s_customize_controller/pkg/apis/bolingcavalry" ) var SchemeGroupVersion = schema.GroupVersion{ Group: bolingcavalry.GroupName, Version: bolingcavalry.Version, } var ( SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) AddToScheme = SchemeBuilder.AddToScheme ) func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes( SchemeGroupVersion, &Student{}, &StudentList{}, ) // register the type in the scheme metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil }
至此,为自动生成代码做的准备工作已经完成