前一篇文章(《如何使用 CRD 拓展 Kubernetes 集群》)通过一个 Demo 讲解 CRD 是什么,以及可以提供什么能力,本文继续基于这个 Demo(https://github.com/Coderhypo/KubeService ),来讲解一下如何构建一个 CRD Controller。
CRD Controller
对于 CRD(CustomResourceDefinition)本身来说,你把它理解为只是一个 OpenApi 的 Schema 一点也不过分,因为这也是它唯一的能力和作用,而对于广义的说法:“利用 CRD 实现了 xx 功能”,真正承担功能实现的,其实说的是 CRD Controller。
Kubernetes 本身就自带了一堆 Controller,Master 节点上的三大核心组件之一:Controller Manager,其实就是一堆 Controller 的集合。
Controller Manger 里有不少 Controller 和我们要实现的 CRD Controller 本质上做的事其实是一样,就是对特定资源进行管理。
而且 Kubernetes 中不同的 Controller 间的通讯方式也非常有意思,以通过 Deployment 创建 Pod 举例:
用户通过 Kubectl 创建 Deployment,APIServer 会对该请求进行权限、准入认证,然后将 Deployment 的资源存储到 ETCD 中,因为 Kubernetes 通过 ETCD 实现了 List-Watch 机制,因此对 Deployment 相关事件感兴趣的 Deployment Controller 会受到资源的 ADD 事件并处理,即为该 Deployment 创建 RS。
RS 创建请求在被 APIServer 接收之后,RS 的 ADD 事件将被发布,因此 ReplicaSet Controller 将会接收到该事件,并进行后续处理:即创建 Pod。
因此可以看到,得益于 Kubernetes 基于事件的工作方式,创建受 Deployment 管理的 Pod 这个动作,Deployment Controller 和 ReplicaSet Controller 都参与执行,但是这两个 Controller 之间并没有直接通讯。
也正是因为其基于事件的工作方式,我们可以自定义 Controller 来处理感兴趣的事件,包括但不局限于 CR 的创建、修改等。
Kubebuilder 和 Operator-SDK
对于 CRD Controller 的构建,有几个主流的工具,一个是 coreOS 开源的 Operator-SDK(https://github.com/operator-framework/operator-sdk ),另一个是 K8s 兴趣小组维护的 Kubebuilder(https://github.com/kubernetes-sigs/kubebuilder )。
Operator-SDK 是 Operator 框架的一部分,Operator 社区比较成熟而且活跃,甚至还有自己的 Hub(https://operatorhub.io/ ) 来让大家探索、分享有趣的 Operator。
Kubebuilder 与其说是个 SDK 不如说是一个代码生成器,通过符合格式的注释和数据结构来生成一个可运行的 Controller。在使用下来最大的感觉是基础设施还不是很完善,比如文档,在写这个 Demo 很多场景还是靠翻代码才找到怎么解决。
Kubebuilder quick start
可能得益于 Kubebuilder 偏代码生成器,所以使用 Kubebuilder 从零到一创建一个 Controller 是非常简单,想必会有很多文章或 topic 会以 “x 分钟创建 CRD Controller” 为题吧。
官方入门文档可见:https://book.kubebuilder.io/quick-start.html
创建项目
首先通过 Kubebuilder init 命令初始化项目,--domain
flag arg 来指定 api group。
kubebuilder init --domain o0w0o.cn --owner "Hypo"
当项目创建好之后,会提醒你是否下载依赖,然后你会发现大半个 Kubernetes 的代码已经在你 GOPATH 里了 ┑( ̄Д  ̄)┍。
创建 Api
当项目创建好就可以创建 api:
kubebuilder create api --group app --version v1 --kind App
kubebuilder create api --group app --version v1 --kind MicroService
创建 api 的同时你会发现 Kubebuilder 会帮你创建一些目录和源代码文件:
- 在
pkg.apis
里面包含了资源App
和MicroService
的默认数据结构 - 在
pkg.controller
里面App
和MicroService
的两个默认 Controller
resource type
Kubebuilder 已经帮你创建和默认的结构:
// MicroService is the Schema for the microservices API
// +k8s:openapi-gen=true
type MicroService struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MicroServiceSpec `json:"spec,omitempty"`
Status MicroServiceStatus `json:"status,omitempty"`
}
你要做的只是拓展,以 MicroService 举例:
type Canary struct {
// +kubebuilder:validation:Maximum=100
// +kubebuilder:validation:Minimum=1
Weight int `json:"weight"`
// +optional
CanaryIngressName string `json:"canaryIngressName,omitempty"`
// +optional
Header string `json:"header,omitempty"`
// +optional
HeaderValue string `json:"headerValue,omitempty"`
// +optional
Cookie string `json:"cookie,omitempty"`
}
type DeployVersion struct {
Name string `json:"name"`
Template appsv1.DeploymentSpec `json:"template"`
// +optional
ServiceName string `json:"serviceName,omitempty"`
// +optional
Canary *Canary `json:"canary,omitempty"`
}
type ServiceLoadBalance struct {
Name string `json:"name"`
Spec corev1.ServiceSpec `json:"spec"`
}
type IngressLoadBalance struct {
Name string `json:"name"`
Spec extensionsv1beta1.IngressSpec `json:"spec"`
}
type LoadBalance struct {
// +optional
Service *ServiceLoadBalance `json:"service,omitempty"`
// +optional
Ingress *IngressLoadBalance `json:"ingress,omitempty"`
}
// MicroServiceSpec defines the desired state of MicroService
type MicroServiceSpec struct {
// +optional
LoadBalance *LoadBalance `json:"loadBalance,omitempty"`
Versions []DeployVersion `json:"versions"`
CurrentVersionName string `json:"currentVersionName"`
}
完整的代码见:https://github.com/Coderhypo/KubeService/blob/master/pkg/apis/app/v1/microservice_types.go
controller 逻辑
如果之前没有了解过 Kubernetes 控制器的代码,可能会对默认生成的 Controller 很奇怪,默认 MicroService Controller 名为 ReconcileMicroService
,其只有一个主要方法就是:
func (r *ReconcileMicroService) Reconcile(request reconcile.Request) (reconcile.Result, error)
在使这个 Controller Work 之前,需要 pkg.controller.microservice.micriservice_controller.go
中的 add 方法中注册要关注的事件,当任何感兴趣的事件发生时,Reconcile
便会被调用,这个函数的职责,就像 Deployment Controller 的 syncHandler
一样,当有事件发生时,去比对当前资源的状态和预期的状态是否一致,如果不一致,就去矫正。
比如 MicroService 通过 Deployment 管理版本,Reconcile
就要判断每个版本的 Deployment 是否存在,是否符合预期,不过不存在,就去创建,如果不符合,就去纠正。
具体 ReconcileMicroService
代码可见:https://github.com/Coderhypo/KubeService/blob/master/pkg/controller/microservice/microservice_controller.go
运行
当 CR 的结构已经确定和 Controller 代码完成之后,便可以尝试试运行一下。Kubebuilder 可以通过本地 kubeconfig 配置的集群试运行(对于快速创建一个开发集群推荐 minikube)。
首先记得在 main.go 中的 init 方法中添加 schema:
func init() {
_ = corev1.AddToScheme(scheme)
_ = appsv1.AddToScheme(scheme)
_ = extensionsv1beta1.AddToScheme(scheme)
_ = apis.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
然后使 Kubebuilder 重新生成代码:
make
然后将 config/crd 下的 CRD yaml 应用到当前集群:
make install
在本地运行 CRD Controller(直接执行 main 函数也可以):
make run