从零开始设计一个CRD
前言
经过前面的铺垫,相信现在对kubebuilder的工作模式已初具了解,那么从本篇开始,正式设计一个CRD。本文对于Unit的设计,是基于我的个人场景下的需求提炼出来的,不一定适合你,但着重点在于思路,希望能有帮助。
面临的现状
在我的场景下,应该也是大多数人的场景下,通常一个运行服务(姑且这么称呼),使用一系列build-in 类型资源进行组合,来保障运行和提供服务,例如,最常用的组合有:StatefulSet/Deployment/Ingress/Service/Ingress这几种资源的按需组合,如下图:
![](http://mycloudn.upweto.top/20200521172548.jpg)
这些资源类型每一种都是可选项,根据使用需求的不同,来灵活(弱绑定?)进行组合。
例如:
- 非web服务不需要Ingress资源
- 自发现和注册的应用不需要Service
- 无状态的应用选用Deployment,有状态的应用选用StatefulSet
- 有的应用无需持久存储,有的应用需求PVC来实现持久存储
按需组合,不一而同
这样的弱绑定关系在个人认为管理上不足够的友好,每种资源的增删改查等逻辑,需要分别操作,没有整体的连贯性和协同性,需要分而治之。
想要实现什么
与市面上大多数专属于某个应用或者某类应用的CRD不同,我这里想要实现的CRD目标是:每一个运行服务,所用到的的各类资源,以强申明将它们绑定在一起,进行统一的生命周期管理
因此,我给这个CRD的命名是Unit,这意味着,StatefulSet/Deployment/Ingress/Service/Ingress这些可选的build-in资源,由原本松散组合构成一个运行服务的模式,变为集合在一个Unit单元里,形成统一的管理生命周期。如下图:
![](http://mycloudn.upweto.top/20200522103739.jpg)
设计的目标已经设定了,下面就开始实际设计和填充Unit结构体。
结构设计
如同所有的build-in GVR一样,结构体的构成除了主要分为两个部分,一个是Spec,一个是Status,声明式的目标即是Status满足Spec的声明要求。因此,Unit的结构体设计也围绕Spec和Status两部分来。下面直接贴代码,代码中会有注释。
按照上面的描述,Unit总共可管理这5种资源:StatefulSet/Deployment/Ingress/Service/Ingress,为了结构清晰,将它们各自的结构体分别放在不同的文件中:
![](http://mycloudn.upweto.top/20200521191425.jpg)
原本的build-in资源结构体,以Deployment举例,Deployment结构体字段非常多,不够精简,因此我不想每种资源的每个字段都完整声明,而是在前端以最精简地方式声明,只提供必要的字段,而最后填充出完整的Deployment的结构体的方法放在后端来进行,尽可能地保证前端使用用户的友好度。
Spec
直接贴代码
UnitSpec
// UnitSpec defines the desired state of Unit
type UnitSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Category 支持两种: Deployment / StatefulSet ,二者只能选其一,在admission validating webhook里会做校验.
Category string `json:"category"`
// Replicas和Selector这两个字段在mutate webhook里默认会有填充
Replicas *int32 `json:"replicas,omitempty"`
Selector *metav1.LabelSelector `json:"selector,omitempty"`
// Template describes the pods that will be created.
Template corev1.PodTemplateSpec `json:"template"`
RelationResource UnitRelationResourceSpec `json:"relationResource,omitempty"`
}
UnitRelationResourceSpec
type UnitRelationResourceSpec struct {
Service *OwnService `json:"serviceInfo,omitempty"`
PVC *OwnPVC `json:"pvcInfo,omitempty"`
Ingress *OwnIngress `json:"ingressInfo,omitempty"`
}
这里指定OwnService/OwnIngress/OwnPVC,至于具体的ownDeployment / ownStatefulSet,由于限制二种部署方式只能取其一,因此会结合UnitSpec.Category和UnitSpec.Template来动态生成,怎么生成放在后面说,先来看看结构体。
OwnStatefulSet
type OwnStatefulSet struct {
Spec appsv1.StatefulSetSpec
}
OwnDeployment
type OwnDeployment struct {
Spec appsv1.DeploymentSpec `json:"spec"`
}
OwnService
// svc的端口映射关系
type ServicePort struct {
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
Protocol string `json:"protocol,omitempty" protobuf:"bytes,2,opt,name=protocol,casttype=Protocol"`
Port int32 `json:"port" protobuf:"varint,3,opt,name=port"`
TargetPort intstr.IntOrString `json:"targetPort,omitempty" protobuf:"bytes,4,opt,name=targetPort"`
NodePort int32 `json:"nodePort,omitempty" protobuf:"varint,5,opt,name=nodePort"`
}
type OwnService struct {
Ports []v1.ServicePort `json:"ports,omitempty" patchStrategy:"merge" patchMergeKey:"port" protobuf:"bytes,1,rep,name=ports"`
ClusterIP string `json:"clusterIP,omitempty" protobuf:"bytes,3,opt,name=clusterIP"`
}
OwnIngress
// ingress信息
type OwnIngress struct {
Domains []string `json:"domain"`
}
OwnPVC
// pvc声明信息
type OwnPVC struct {
Spec v1.PersistentVolumeClaimSpec` json:"spec"`
}
大部分都是利用原resource.Spec字段,这样在精简的同时,生成resource object的时候也可以比较方便。
Status
Unit.Status设计就比较天马行空了,例如我希望在状态里面看到与Service和Pod自动关联的Endpoints逻辑资源,因此把部分Endpoint的信息也加了进来。
UnitStatus
// UnitStatus defines the observed state of Unit
type UnitStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Replicas *int32 `json:"replicas,omitempty"`
Selector string `json:"selector"`
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
BaseDeployment appsv1.DeploymentStatus `json:"deployment,omitempty"`
BaseStatefulSet appsv1.StatefulSetStatus `json:"statefulSet,omitempty"`
RelationResourceStatus UnitRelationResourceStatus `json:"relationResourceStatus,omitempty"`
}
type UnitRelationResourceStatus struct {
Service UnitRelationServiceStatus `json:"service,omitempty"`
Ingress []v1beta1.IngressRule `json:"ingress,omitempty"`
Endpoint []UnitRelationEndpointStatus `json:"endpoint,omitempty"`
PVC corev1.PersistentVolumeClaimStatus `json:"pvc,omitempty"`
}
UnitRelationServiceStatus
type UnitRelationServiceStatus struct {
Type v1.ServiceType `json:"type,omitempty"`
ClusterIP string `json:"clusterIP,omitempty"`
Ports []ServicePortStatus `json:"ports,omitempty"`
SessionAffinity v1.ServiceAffinity `json:"sessionAffinity,omitempty"`
}
UnitRelationEndpointStatus
type UnitRelationEndpointStatus struct {
PodName string `json:"podName"`
PodIP string `json:"podIP"`
NodeName string `json:"nodeName"`
}
原ServiceStatus信息量太少,就用了一个自定义的status填充了一下,其余的资源类型的Status,如DeploymentStatus/StatefulSetStatus/PersistentVolumeClaimStatus 信息量已经足够了,偷下懒直接利用。
总结
Unit的实现目标确定后,设计和填充了它的结构体,同时为了保持结构体的精简,简化出了一层ownResource,同样也设计和填充ownResources的Spec和Status结构。基础已经铺垫好,下一篇开始设计和实现Unit Reconciler的控制逻辑,以管理Unit和ownResources之间的生命周期的联动。