作者:乔克
公众号:运维开发故事
博客:www.coolops.cn(COOLOPS)
知乎:乔克叔叔
大家好,我是乔克。
在Kubernetes中,Pod是最小的调度单元,它由各种各样的Controller管理,比如ReplicaSet Controller,Deployment Controller等。
Kubernetes内置了许多Controller,这些Controller能满足80%的业务需求,但是企业里也难免需要自定义Controller来适配自己的业务需求。
网上自定义Controller的文章很多,基本都差不多。俗话说:光说不练假把式
,本篇文章主要是自己的一个实践归档总结,如果对你有帮助,可以一键三连
!
本文主要从以下几个方面进行介绍,其中包括理论部分和具体实践部分。
Controller的实现逻辑
当我们向kube-apiserver提出创建一个Deployment
需求的时候,首先是会把这个需求存储到Etcd中,如果这时候没有Controller的话,这条数据仅仅是存在Etcd中,并没有产生实际的作用。
所以就有了Deployment Controller,它实时监听kube-apiserver中的Deployment对象,如果对象有增加、删除、修改等变化,它就会做出相应的相应处理,如下:
1 | go复制代码// pkg/controller/deployment/deployment_controller.go 121行 |
其实现的逻辑图如下(图片来自网络):
可以看到图的上半部分都由client-go
实现了,下半部分才是我们具体需要去处理的。
client-go
主要包含Reflector
、Informer
、Indexer
三个组件。
Reflector
会List&Watch
kube-apiserver中的特定资源,然后会把变化的资源放入Delta FIFO
队列中。Informer
会从Delta FIFO
队列中拿取对象交给相应的HandleDeltas
。Indexer
会将对象存储到缓存中。
上面部分不需要我们去开发,我们主要关注下半部分。
当把数据交给Informer
的回调函数HandleDeltas
后,Distribute
会将资源对象分发到具体的处理函数,这些处理函数通过一系列判断过后,把满足需求的对象放入Workqueue
中,然后再进行后续的处理。
code-generator介绍
上一节说到我们只需要去实现具体的业务需求,这是为什么呢?主要是因为kubernetes
为我们提供了code-generator
【1】这样的代码生成器工具,可以通过它自动生成客户端访问的一些代码,比如Informer
、ClientSet
等。
code-generator
提供了以下工具为Kubernetes
中的资源生成代码:
- deepcopy-gen:生成深度拷贝方法,为每个 T 类型生成 func (t* T) DeepCopy() *T 方法,API 类型都需要实现深拷贝
- client-gen:为资源生成标准的 clientset
- informer-gen:生成 informer,提供事件机制来响应资源的事件
- lister-gen:生成 Lister**,**为 get 和 list 请求提供只读缓存层(通过 indexer 获取)
如果需要自动生成,就需要在代码中加入对应格式的配置,如下:
其中:
// +genclient
表示需要创建client// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
表示在需要实现k8s.io/apimachinery/pkg/runtime.Object
这个接口
除此还有更多的用法,可以参考Kubernetes Deep Dive: Code Generation for CustomResources
【2】进行学习。
CRD介绍
CRD
全称CustomResourceDefinition
,中文简称自定义资源
,上面说的Controller主要就是用来管理自定义的资源
。
我们可以通过下面命令来查看当前集群中使用了哪些CRD
,如下:
1 | shell复制代码# kubectl get crd |
但是仅仅是创建一个CRD
对象是不够的,因为它是静态
的,创建过后仅仅是保存在Etcd
中,如果需要其有意义,就需要Controller
配合。
创建CRD
的例子如下:
1 | yaml复制代码apiVersion: apiextensions.k8s.io/v1 |
具体演示
本来准备根据官方的demo
【3】进行讲解,但是感觉有点敷衍,而且这类教程网上一大堆,所以就准备自己实现一个数据库管理的一个Controller。
因为是演示怎么开发Controller,所以功能不会复杂,主要的功能是:
- 创建数据库实例
- 删除数据库实例
- 更新数据库实例
开发环境说明
本次实验环境如下:
软件 | 版本 |
---|---|
kubernetes | v1.22.3 |
go | 1.17.3 |
操作系统 | CentOS 7.6 |
创建CRD
CRD
是基础,Controller
主要是为CRD
服务的,所以我们要先定义好CRD
资源,便于开发。
1 | yaml复制代码apiVersion: apiextensions.k8s.io/v1 |
创建CRD,检验是否能创建成功。
1 | bash复制代码# kubectl apply -f crd.yaml |
自定义一个测试用例,如下:
1 | yaml复制代码apiVersion: coolops.cn/v1alpha1 |
创建后进行查看:
1 | yaml复制代码# kubectl apply -f example-mysql.yaml |
不过现在仅仅是创建了一个静态数据,并没有任何实际的应用,下面来编写Controller来管理这个CRD。
开发Controller
自动生成代码
1、创建项目目录database-manager-controller,并进行go mod 初始化
1 | yaml复制代码# mkdir database-manager-controller |
2、创建源码包目录pkg/apis/databasemanager
1 | yaml复制代码# mkdir pkg/apis/databasemanager -p |
3、在pkg/apis/databasemanager目录下创建register.go文件,并写入一下内容
1 | yaml复制代码package databasemanager |
4、在pkg/apis/databasemanager目录下创建v1alpha1目录,进行版本管理
1 | yaml复制代码# mkdir v1alpha1 |
5、在v1alpha1目录下创建doc.go文件,并写入以下内容
1 | go复制代码// +k8s:deepcopy-gen=package |
其中// +k8s:deepcopy-gen=package
和// +groupName=coolops.cn
都是为了自动生成代码而写的配置。
6、在v1alpha1目录下创建type.go文件,并写入以下内容
1 | yaml复制代码package v1alpha1 |
type.go
主要定义我们的资源类型。
7、在v1alpha1目录下创建register.go文件,并写入以下内容
1 | yaml复制代码package v1alpha1 |
register.go
的作用是通过addKnownTypes
方法使得client
可以知道DatabaseManager
类型的API对象。
至此,自动生成代码的准备工作完成了,目前的代码目录结构如下:
1 | yaml复制代码# tree . |
接下里就使用code-generator进行代码自动生成了。
8、创建生成代码的脚本
以下代码主要参考
sample-controller
【3】
(1)在项目根目录下,创建hack目录,代码生成的脚本配置在该目录下
1 | yaml复制代码# mkdir hack && cd hack |
(2)创建tools.go文件,添加 code-generator 依赖
1 | yaml复制代码//go:build tools |
(3)创建update-codegen.sh文件,用来生成代码
1 | yaml复制代码#!/usr/bin/env bash |
其中以下代码段根据实际情况进行修改。
1 | yaml复制代码bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ |
(4)创建verify-codegen.sh文件,主要用于校验生成的代码是否为最新的
1 | yaml复制代码#!/usr/bin/env bash |
(5)创建boilerplate.go.txt,主要用于为代码添加开源协议
1 | yaml复制代码/* |
(6)配置go vendor依赖目录
从update-codegen.sh
脚本可以看到该代码生成脚本是利用vendor
目录下的依赖进行的,我们项目本身没有配置,执行以下命令进行创建。
1 | yaml复制代码# go mod vendor |
(7)在项目根目录下执行脚本生成代码
1 | yaml复制代码# chmod +x hack/update-codegen.sh |
然后新的目录结构如下:
1 | yaml复制代码# tree pkg/ |
Controller开发
上面已经完成了自动代码的生成,生成了informer
、lister
、clientset
的代码,下面就开始编写真正的Controller
功能了。
我们需要实现的功能是:
- 创建数据库实例
- 更新数据库实例
- 删除数据库实例
(1)在代码根目录创建controller.go文件,编写如下内容
1 | go复制代码package main |
其主要逻辑和文章开头介绍的Controller实现逻辑
一样,其中关键点在于:
- 在
NewController
方法中,定义了DatabaseManager
和Deployment
对象的Event Handler,除了同步缓存外,还将对应的Key放入queue中。 - 实际处理业务的方法是
syncHandler
,可以根据实际请求来编写代码以达到业务需求。
2、在项目根目录下创建main.go,编写入口函数
(1)编写处理系统信号量的Handler
这部分直接使用的demo中的代码【3】
(2)编写入口main函数
1 | yaml复制代码package main |
测试Controller
1、在项目目录下添加一个Makefile
1 | yaml复制代码build: |
2、执行make build进行编译
1 | yaml复制代码# make build |
然后会输出database-manager-controller
一个二进制文件。
3、运行controller
1 | yaml复制代码# chmod +x database-manager-controller |
4、创建一个CRD测试用例,观察日志以及是否创建deployment
(1)测试样例如下
1 | yaml复制代码# cat example-mysql.yaml |
(2)执行以下命令进行创建,观察日志
1 | yaml复制代码# kubectl apply -f example-mysql.yaml |
可以看到对于的deployment和pod已经创建,不过由于Deployment的配置没有配置完全,mysql没有正常启动。
我们其实是可以看到Controller获取到了事件。
如果我们删除对象,也可以从日志里正常看到响应。
总结
上面就是自定义Controller的整个开发过程,相对来说还是比较简单,大部分东西社区都做好了,我们只需要套模子,然后实现自己的逻辑就行。
整个过程主要是参考sample-controller
【3】 ,现在简单整理如下:
- 确定好目的,然后创建CRD,定义需要的对象
- 按规定编写代码,定义好CRD所需要的type,然后使用code-generator进行代码自动生成,生成需要的informer、lister、clientset。
- 编写Controller,实现具体的业务逻辑
- 编写完成后就是验证,看看是否符合预期,根据具体情况再做进一步的调整
引用
【1】 github.com/kubernetes/…
【2】 cloud.redhat.com/blog/kubern…
【3】 github.com/kubernetes/…
【4】 cloud.tencent.com/developer/a…
【5】 www.bookstack.cn/read/source…
本文转载自: 掘金