Operator-SDK:Controller数据写入自定义CRD并实现自动更新
之前写了一个使用Operator-SDK自定义CRD实现Node的request信息收集的demo,现在要将其采集到的数据写入K8s,实现在集群内直接使用kubectl get命令就可以查询到信息。
Operator-SDK:Controller数据写入自定义CRD并实现自动更新
之前写了一个使用Operator-SDK自定义CRD实现Node的request信息收集的demo,现在要将其采集到的数据写入K8s,实现在集群内直接使用kubectl get
命令就可以查询到信息。
参考资料:
kubebuilder2.0学习笔记——进阶使用 - SegmentFault 思否
Operator-SDK:自定义CRD实现Node的request信息收集
Operator-SDK:结合基于GO构建的官方样例相关文档阅读及部分命令解释
文章目录
创建 API 和 Controller
创建一个CRD API,其中组为cache
,版本为v1alpha1
,类型为Noderequest。出现提示时,输入y
来创建resource和controller。
$ operator-sdk create api --group cache --version v1alpha1 --kind Noderequest --resource --controller
Writing scaffold for you to edit...
api/v1alpha1/noderequest_types.go
controllers/noderequest_controller.go
...
这将会构建一个 Noderequest 资源的 API 在目录 api/v1alpha1/noderequest_types.go
,控制器 controller 在目录 controllers/noderequest_controller.go
。创建出来的项目目录结构如下:
控制器的核心逻辑在先前的文档中有过分析:Operator-SDK:自定义CRD实现Node的request信息收集
查看noderequest-operator/api/v1alpha1
文件下的noderequest_types.go
文件,这个是自定义CRD的结构体文件:
// NoderequestSpec defines the desired state of Noderequest
type NoderequestSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Noderequest. Edit noderequest_types.go to remove/update
Foo string `json:"foo,omitempty"`
}
// NoderequestStatus defines the observed state of Noderequest
type NoderequestStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// Noderequest is the Schema for the noderequests API
type Noderequest struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec NoderequestSpec `json:"spec,omitempty"`
Status NoderequestStatus `json:"status,omitempty"`
}
这里,Spec
和Status
均是Noderequest
的成员变量,Status
并不像Pod.Status
一样,是Pod
的subResource
子资源。因此,如果在controller的代码中调用到Status().Update()
,会触发panic,并报错:the server could not find the requested resource
。
如果我们想像k8s中的设计那样,那么就要遵循k8s中status subresource
的使用规范:
- 用户只能指定一个CRD实例的spec部分;
- CRD实例的status部分由控制器进行变更。
设计subResource风格的Status
需要在Noderequest
的注释中添加一行// +kubebuilder:subresource:status
,在上述的代码中已经添加:
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// Noderequest is the Schema for the noderequests API
type Noderequest struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec NoderequestSpec `json:"spec,omitempty"`
Status NoderequestStatus `json:"status,omitempty"`
}
创建Noderequest
资源时,即便我们填入了非空的status
结构,也不会更新到apiserver中。Status只能通过对应的client进行更新。比如在controller中:
if Noderequest.Status.Progress == 0 {
Noderequest.Status.Progress = 1
err := r.Status().Update(ctx, &Noderequest)
if err != nil {
return ctrl.Result{}, err
}
}
这样,只要Noderequest
实例的status.Progress
为0时(比如我们创建一个Noderequest
实例时,由于status.Progress
无法配置,故初始化为默认值,即0),controller就会帮我们将它变更为1。
在type.go中设置scope标记
k8s中node、pv等资源是集群级别的,它们没有namespace字段,因此查询node资源时也无需规定要从哪个namespace查。
我们在进行k8s operator时经常也需要设计这样的字段。默认情况下,kubebuilder会给我们创建namespace scope的crd资源,可以手动在GO中的types.go
文件中增加和改变kubebuilder scope marker来设置我们资源的scope。这个文件在api/<version>/<kind>_types.go
或者api/<group>/<version>/<kind>_types.go
位置。
在执行kubebuilder create api ****
后,我们在生成的资源的*_types.go
文件中,找到资源的主结构体,增加一条注释kubebuilder:resource:scope=Cluster
,在这个项目中,是上文中的noderequest_types.go
文件:
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:scope=Cluster
// Noderequest is the Schema for the noderequests API
type Noderequest struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec NoderequestSpec `json:"spec,omitempty"`
Status NoderequestStatus `json:"status,omitempty"`
}
如果要设置为命名空间范围,要将标记替换为//+kubebuilder:resource:scope=Namespace
。
需要注意的是,// +kubebuilder:resource:scope=Cluster
这句注释,必须放在结构体的上方、且必须是最靠近该结构体的一条kubebuilder注释,否则会失效。
编写结构体并更新资源类型
将Node的request中的CPU和内存数据写回CRD,所以需要三个字段分别用来存储Node结点名称、CPU资源、内存资源,由于计算出来的资源需要查看百分比,所以CPU和内存再分别添加一个Rate
来存储所占用总资源的百分比。同时使用json
自动补全时,给出的json名称是下划线格式的,手动修改为Pascal格式:
// NoderequestStatus defines the observed state of Noderequest
type NoderequestStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
NodeName string `json:"NodeName"`
// CPU, in cores. (500m = .5 cores)
NodeCpu string `json:"NodeCpu"`
// CPU, in %
NodeCpuRate string `json:"NodeCpuRate"`
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
NodeMem string `json:"NodeMem"`
// Memory, in %
NodeMemRate string `json:"NodeMemRate"`
}
修改 *_types.go
文件后,必须要运行以下命令以更新该资源类型的生成代码:
make generate
上面的makefile目标将调用 controller-gen 实用程序来更新生成的 api/v1alpha1/zz_generated.deepcopy.go
文件,以确保API的Go类型定义可以实现应用在Kind类型上的 runtime.Object
接口。
生成 CRD manifests
使用 spec/status 字段和 CRD 验证 markers定义API以后,可以使用以下命令生成和更新 CRD manifests:
$ make manifests
这个 makefile 目标将会调用 controller-gen 在 config/crd/bases/cache.example.com_memcacheds.yaml
处生成 CRD manifests:
$ make generate
/home/shark/go-project/noderequest-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
$ make manifests
/home/shark/go-project/noderequest-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
最后执行make install
将CRD注册到集群中,相当于执行了kubectl apply -f
命令:
$ make install
/home/shark/go-project/noderequest-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
/home/shark/go-project/noderequest-operator/bin/kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/noderequests.noderequest.example.com created
如果需要卸载CRD,则执行make uninstall
。
生成的YAML文件中的status字段如下:
status:
description: NoderequestStatus defines the observed state of Noderequest
properties:
NodeCpu:
description: CPU, in cores. (500m = .5 cores)
type: string
NodeCpuRate:
description: CPU, in %
type: string
NodeMem:
description: Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024
* 1024)
type: string
NodeMemRate:
description: Memory, in %
type: string
NodeName:
type: string
required:
- NodeCpu
- NodeCpuRate
- NodeMem
- NodeMemRate
- NodeName
type: object
Controller代码
获取CR
首先获取到所有的CR实例,由于创建的时候是设置scope=Cluster
集群范围的CRD,所以命名空间为空时可以获取到所有的CR:
nodeRequestList := &noderequestv1.NoderequestList{}
nodeOpts := []client.ListOption{
client.InNamespace(""),
}
err = r.Client.List(ctx, nodeRequestList, nodeOpts...)
if err != nil {
fmt.Println("ERROR[GetNoderequest]:", err)
return
}
获取到当前所有的CR是为了判断集群内是否已经存在对应的监控资源,如果不存在需要Create,存在则需要进行更新(Patch或者Update)。
Patch
先定义一个Noderequest
资源类型的实例,将其Name设置为当前监控的集群结点的Node的name。
定义布尔类型变量exist
用来判断当前是否已经存在对该Node结点的监控资源,false
为不存在,true
为存在。默认值为false
,使用循环判断nodeRequestList.Items
是否已经存在一个实例的Name的值等于当前需要监控的Node结点的name的值,若存在,说明之前已经创建过,执行Patch操作:
nodeRequest := &noderequestv1.Noderequest{}
nodeRequest.Name = node.Name
exist := false
for _, item := range nodeRequestList.Items {
if item.Status.NodeName == node.Name {
exist = true
// Ptach the CR
patch := client.MergeFrom(nodeRequest.DeepCopy())
nodeRequest.Status.NodeName = node.Name
nodeRequest.Status.NodeCpu = cpuReqs.String()
nodeRequest.Status.NodeCpuRate = strconv.FormatInt(int64(fractionCpuReqs), 10)
nodeRequest.Status.NodeMem = memoryReqs.String()
nodeRequest.Status.NodeMemRate = strconv.FormatInt(int64(fractionMemoryReqs), 10)
err = r.Client.Status().Patch(ctx, nodeRequest, patch)
if err != nil {
fmt.Println("ERROR[Patch]:", err)
return
}
fmt.Println("update: ", item.Status.NodeName)
break
}
}
Create & Patch
若不存在,需要进行创建Create并且Patch。
在上一个代码中,nodeRequest.Name = node.Name
表示以当前Node结点的name值进行创建的CRD资源,所以创建出来的CRD资源名称将会跟随集群Node的名字。
if exist == false {
err = r.Client.Create(ctx, nodeRequest)
if err != nil {
fmt.Println("ERROR[Create]:", err)
return
}
// Ptach the CR
patch := client.MergeFrom(nodeRequest.DeepCopy())
nodeRequest.Status.NodeName = node.Name
nodeRequest.Status.NodeCpu = cpuReqs.String()
nodeRequest.Status.NodeCpuRate = strconv.FormatInt(int64(fractionCpuReqs), 10)
nodeRequest.Status.NodeMem = memoryReqs.String()
nodeRequest.Status.NodeMemRate = strconv.FormatInt(int64(fractionMemoryReqs), 10)
err = r.Client.Status().Patch(ctx, nodeRequest, patch)
if err != nil {
fmt.Println("ERROR[Patch]:", err)
return
}
fmt.Println("create: ", nodeRequest.Name)
}
注意,Create时不会自动填入Status
字段的内容,需要在Create之后进行Patch,将字段内容补全。
最终结果
在执行make install
之前,集群内查不到这个类型的资源:
$ kubectl get noderequest
Error from server (NotFound): Unable to list "noderequest.example.com/v1alpha1, Resource=noderequests": the server could not find the requested resource (get noderequests.noderequest.example.com)
先执行make install
命令,然后进行查询:
$ kubectl get noderequest
No resources found
当前集群内还未存在资源。将项目ControllerRun起来后,再查询资源:
$ kubectl get noderequest
NAME AGE
10.100.100.130-slave 3s
10.100.100.131-master 3s
10.100.100.144-slave 3s
10.100.100.147-slave 3s
查询下集群信息:
$ kubectl get node
NAME STATUS ROLES AGE VERSION
10.100.100.130-slave Ready <none> 55d v1.21.5-hc.1
10.100.100.131-master Ready control-plane,master 55d v1.21.5-hc.1
10.100.100.144-slave Ready <none> 55d v1.21.5-hc.1
10.100.100.147-slave Ready <none> 55d v1.21.5-hc.1
看到资源和集群node是一一对应的。
再查询详细的信息,检查资源数据是否正确写入CR中:
$ kubectl get noderequest 10.100.100.131-master -oyaml
apiVersion: noderequest.example.com/v1alpha1
kind: Noderequest
metadata:
creationTimestamp: "2022-02-16T02:19:09Z"
generation: 1
name: 10.100.100.131-master
resourceVersion: "26005744"
uid: 5b3b20c1-de86-46ce-8367-472eccf21152
spec: {}
status:
NodeCpu: 2832m
NodeCpuRate: "70"
NodeMem: 2493Mi
NodeMemRate: "33"
NodeName: 10.100.100.131-master
字段数据正确写入。
更多推荐
所有评论(0)