Kubernetes client-go实战应用
[TOC]
Kubernetes client-go实战应用
实战应用
-
sample-controller示例
- 官方的一个简单的Controller示例,可以处理任何CRD资源
- The main.go and controller.go contains the sample code to watch for the CRD and do some task accordingly.
- 采用code-generator 的方式
-
kubernetes-crd-example
- 一个简单的处理任何CRD资源的示例
client-go手动操作CRD
以前创建和管理CRD的client库位于:https://github.com/kubernetes/apiextensions-apiserver,但是现在client-go已经支持CRD
1,定义types类型
比如定义一个目录api/types/v1alpha1,里面是关于types的一些定义文件;
metav1.ObjectMeta 类型会包含典型的任意的Kubernetes资源的metadata属性,如name, namespace, labels, annotations。
2,定义DeepCopy深拷贝方法
被Kubernetes API定义的所有资源对象、类型都需要实现k8s.io/apimachinery/pkg/runtime.Object这个接口定义,这个接口包含两个方法GetObjectKind() 和 DeepCopyObject():
- GetObjectKind():
- 内置的metav1.TypeMeta结构已经实现
- DeepCopyObject():
- 需要我们自己去实现这个方法
- 目的是生成deep copy 对象,C++中有这样的用法,名为:深拷贝,深拷贝意味着会重新生成对象并拷贝对象中的所有字段、地址等数据;浅拷贝仅仅是对象的引用,并没有生成新的对象。
- 需要手动去写各种字段域的赋值
3,通过scheme注册types类型
Scheme定义了序列化和反序列化API对象的方法,用于将group、版本和类型信息转换为Go模式和从Go模式转换为Go模式的类型注册表,以及不同版本的Go模式之间的映射。
当和API Server通信的时候能够处理新的types类型的话就需要先让client能够知道有这个新的types类型存在。
AddToScheme 会利用到反射,因此新定义的types类型的结构体的命名必须要和自定义的Kind的命名(如VirtualService
)保持一致,否则会找不到对应的kind,
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
- AddToScheme: runtime.SchemeBuilder的一个外部方法,当Kubernetes注册新定义的types类型后,就可以在任何地方调用这个方法
4,启动一个HTTP client
当我们定义好一个新的types类型并且添加了一个register注册方法到全局的scheme编译器后,我们就可以创建一个http client去加载我们的自定义资源了。现在就要用到client-go/rest
这个RESTClient去实现了。一般而言,为了更为安全的方式使用API,优雅的姿势是打包这些操作到clientset中,通过rest包中的RESTClientFor
方法进行相关的封装,然后再实现一些普适的interface接口,包含Get、Create、 Delete, Update 、List、Watch等通用接口方法,这个可以参考Pod client set的实现
5,构建Informer
一般优雅的姿势去操作Kubernetes的资源并实时做出响应的方案是采用client-go的informer,它的工作模式是:初始时使用List()去加载资源的所有相关实例,然后使用Watch()进行订阅更新;使用初始对象List列表和从watch订阅更新到的数据会构建一个本地缓存,该缓存可以快速访问任何自定义资源而无需每次都访问API Server。像Pod、Deployment等资源对象都是采用这种方式。
在k8s.io/client-go/tools/cache
包中提供了一个Informer方法cache.NewInformer
,这个cache.NewInformer
返回两个参数Store 和Controller:
- Controller: 控制 List() 和 Watch() 的调用并填充Store
- Store: 返回从API Server获取的资源的最新状态
- 可以通过这个Store去访问我们的自定义资源CRDs
code-generator自动代码生成操作CRD
代码生成相比于前面的手动生成的优势在于不用手动去写一些基础的deepcopy,client,informer,lister
这些方法
code-generation 也是基于client-go,因为client-go 需要实现runtime.Object interface的CustomResources类型 ,这样就要实现诸如DeepCopy深拷贝的一系列方法,code-generation 就是实现了比如深拷贝的代码生成器,关于k8s.io/code-generator,如下:
- deepcopy-gen: 给自定义type类型T创建一个DeepCopy方法
func (t* T) DeepCopy() *T
- client-gen: 给自定义资源的APIGroups创建clientsets
- informer-gen:通过informers给自定义资源创建一个基础接口方法去操作自定义资源
- lister-gen: 为GET和LIST请求创建一个listers监听器
informer和lister相当于是controllers,抽象一层controllers去进行操作资源.
实际项目中,需要引入k8s.io/code-generator这个工程,然后通过一些脚本生成。一个简单的示例是github上的crd-code-generation项目,这个同时也是这篇博文
code-generation-customresources中的示例
官方的sample-controller项目也是代码生成的,并且有关于自定义资源CRD的一些操作。
1,定义好相关目录和文件
-
创建好工程目录
-
pkg/apis/{Groupame}/{Version}
,如pkg/apis/networking.istio.io/v1
-
-
在version目录下创建文件
- doc.go
- types.go : 资源对象的定义
- regsiter.go:Scheme和register type
-
文件里面的注释,格式,都有要求
-
- 将code-generator项目引入的vendor下
-
创建自动生成脚本
- 调用
vendor/k8s.io/code-generator/generate-groups.sh
生成
- 调用
注意点:types不能有interface{} ,否则自动生成的时候会生成出错,因此其实也不建议通过代码自动生成,还是手动去编写会更好。
type VirtualServiceSpec struct {
Hosts []string `json:"hosts"`
Gateways []string `json:"gateways"`
Http interface{} `json:"http"`
}
报错如下:
/v1/zz_generated.deepcopy.go" (107:37: expected ';', found '{' (and 5 more errors)).
2,生成代码
然后通过如下命令
generate-groups.sh <generators> <output-package> <apis-package> <groups-versions> ...
vendor/k8s.io/code-generator/generate-groups.sh all github.com/openshift-evangelists/crd-code-generation/pkg/client github.com/openshift-evangelists/crd-code-generation/pkg/apis example.com:v1 ...
可以同时指定多个输出目录
vendor/k8s.io/code-generator/generate-groups.sh all \
base-code-example/istio/crd-code-generation-service/pkg/client base-code-example/istio/crd-code-generation-service/pkg/apis \
"example.com:v1 networking.istio.io:v1" \
--go-header-file ${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt
可以生成client的所有方法
3,自动生成的优劣
优势:
-
减少很大一部分的通用实现,如
deepcopy,client,informer,lister
这些方法,这个相对来说是通用的,各个CRD都要实现一套 -
避免自己编写出一些不完善或者错误的实现
劣势:
- 非常严格的定义,包括注释,都要按照要求去实现
- type定义的struct,暂时不支持interface类型的转换,相对来说不方便,需要严格知道spec的所有域和字段的准确类型
istio中client-go的处理
试想一下,istio中的资源、对象都是Kubernetes的CRDs,那么必然,istio中肯定有处理好Kubernetes CRD的方式,我们知道目前都是采用client-go,那么istio中必然会有大量的client-go的引用和使用,通过源码可以发现确实如此并且都是采用RESTClient,clientset是包含RESTClient的。
在源码路径/Users/meitu/Documents/work_Meitu/goDev/Applications/src/istio.io/istio/pilot/pkg/config/kube/crd
中就是相关CRD的处理,然后/Users/meitu/Documents/work_Meitu/goDev/Applications/src/istio.io/istio/pilot/pkg/config/kube/crd/types.go
是istio中所有一些常用资源的定义如DestinationRule、Gateway、VirtualService等。这样的话,我们自己要通过client-go去实现后端服务,去开发的话,就可以参考istio源码中的一些定义和基本方法,然后结合client-go的一般性处理去实现后端服务程序。
参考
Accessing Kubernetes CRDs from the client-go package