client-go 初步认识与实践
最近本人的一个容器应用管理平台项目需要实现对接 Kubernetes 平台并进行一些相关资源的操作,查阅了官方文档、GitHub 以及相关技术文章,发现有个叫做 client-go 的 go 语言库是非常适合做 Kubernetes 二次开发的,于是就边实践,边学习,对 client-go 这个库有了一定程度的了解。对于其中比较复杂的设计,如 informer 部分,之后有时间的话会结合 kube-controller-manager 相关机制的研究学习过程加以介绍分享。
client-go 是 Kubernetes 项目所采用的编程式交互客户端库,官方从2016年8月份开始,资源交互操作相关的核心源码,也就是 client-go 抽取出来,独立出来作为一个项目。也就是现在所用到的 Kubernetes 内部都是集成有 client-go 的,因此对于这个库的编码质量应该是值得放心的。
client-go 所谓编程式交互客户端库说白了就是可以通过写一些 Go 代码实现对kubernetes集群中资源对象(包括deployment、service、ingress、replicaSet、pod、namespace、node等)的增删改查操作。
源码简介
源码目录简述
- discovery:通过Kubernetes API 进行服务发现;
- kubernetes:提供 ClientSet 客户端,可以对 Kubernetes 内置资源对象进行操作;
- dynamic:提供 DynamicClient 客户端,可以实现对任意 Kubernetes 资源对象操作;
- rest:提供 RESTClient 客户端,可以实现对 kube-apiserver 执行 REST 请求实现资源操作;
- scale:提供 ScaleClient 客户端,主要用于 Deployment 等资源的扩缩容;
- listers:为 Kubernetes 资源提供 Lister 功能,对 Get / List 请求提供只读的缓存数据;
- informers:提供每种 Kubernetes 资源的 Informer 实现;
- transport:用于提供安全的 TCP 连接;
- tools/cache:提供常用工具;提供 Client 查询和缓存机制,以缓解 kube-apiserver 压力;
- util:提供常用方法;
Client 对象
学习 client-go 进行 kubernetes 二次开发的很大一部分工作是学会熟练使用它的几种 client,client-go 有如下 4 种 client 客户端对象,通过 kubeconfig 配置信息连接到指定集群的 kube-apiserver 从而实现对于资源的相关操作。
- RESTClient:client-go 中最基础的客户端,其它 client 都基于 RESTClient 实现,RESTClient 实现了 RESTful 风格的 API 请求封装,可以实现对任意 Kubernetes 资源(包括内置资源及 CRDs)的 RESTful 风格交互,如 Post() / Delete() / Put() / Get(),同时支持 Json 和 protobuf;
- ClientSet:与 Kubernetes 内置资源对象交互最常用的 Client,强调,只能处理 Kubernetes 内置资源,不包括 CRD 自定义资源,使用时需要指定 Group、指定 Version,然后根据 Resource 获取。ClientSet 的操作代码是通过 client-gen 代码生成器自动生成的;
- DynamicClient:DynamicClient 能处理包括 CRD 自定义资源在内的任意 kubernetes 资源。但是要注意,DynamicClient 返回的对象是一个 map[string]interface{},如果一个 controller 中需要控制所有的 API,可以使用dynamic client,DynamicClient 只支持JSON;
- DiscoveryClient:用于发现 kube-apiserver 支持的 Group / Version / Resource 信息;
client-go 客户端初始化
kubeconfig 配置信息
client-go 想要访问 kube-apiserver 进行交互操作,首先要配置相关的连接及身份认证等信息,配置信息由 kubeconfig 文件提供,默认情况下集群的 kubeconfig 文件存放路径在:
$HOME/.kube/config
以本人的测试集群的 kubeconfig 文件为例:
apiVersion: v1
kind: Config
clusters:
- cluster:
name: kubernetes
server: https://node01:6443
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJ...
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDM4akND...
client-key-data: LS0tLS1CFURSBLRVktLS0tLQpNS...
其中主要包含了如下信息:
- apiVersion:配置文件资源的版本
- kind:配置文件资源的种类,即 Config
- clusters:定义 Kubernetes 集群相关信息
- cluster:定义每一个集群的名称、kube-apiserver 地址、证书信息;
- contexts:集群上下文环境
- context:定义具体每个集群的命令空间及用户信息,用于将请求发送到指定的集群;
- users:定义用户身份验证信息,客户端凭据;
client-go 读取 kubeconfig 配置信息生成 config 对象:
...
config, err := clientcmd.BuildConfigFromFlags("","./configs/kubeconfig.conf")
...
kubeconfig, _ = ioutil.ReadFile("./configs/kubeconfig.conf")
restConf, _ = clientcmd.RESTConfigFromKubeConfig(kubeconfig)
由 config 对象进一步生成需要的 client 对象,之后才能与 kube-apiserver 通信进行资源的交互操作,每种 client 对象都有自己的生成方式。
常用的 Client 初始化及资源操作示例
介绍一下 RESTClient / ClientSet / DynamicClient 三种 Client 的用法,DiscoveryClient 暂不做介绍
RESTClient
RESTClient 初始化
config, err := clientcmd.BuildConfigFromFlags("","./configs/kubeconfig.conf")
if err != nil{
panic(err)
}
config.APIPath = "api"
config.GroupVersion = &corev1.SchemeGroupVersion
config.NegotiatedSerializer = scheme.Codecs
restClient, err := rest.RESTClient(config)
if err != nil{
panic(err)
}
RESTClient 资源操作示例
pods := &corev1.PodList{}
err = restClient.Get()
.Namespace("yingchi")
.Resource("pods")
.VersionedParams(&metav1.ListOptions{}, scheme.ParameterCodec)
.Do()
.Into(pods)
if err != nil {
panic(err)
}
ClientSet
client-go 中最常用的 client 对象,原因就是 ClientSet 相比 RESTClient 封装的更加易用,尤其是仅仅是去操作 Kubernetes 内置资源时,ClientSet 应该是首选 Client 对象。ClientSet 在 RESTClient 基础上封装了对 Version 和 Resource 的管理方法,每个 Resource 都可以理解为一个 client,这也是 ClientSet 名称的由来。
ClientSet 初始化
config, err := clientcmd.BuildConfigFromFlags("","./configs/kubeconfig.conf")
if err != nil{
panic(err)
}
clientSet, err := kubernetes.NewForConfig(config)
if err != nil{
panic(err)
}
ClientSet 资源操作示例
pods, err := clientSet.CoreV1()
.Pods("yingchi")
.List(metav1.ListOptions{})
if err != nil{
panic(err)
}
DynamicClient
DynamicClient 也是基于 RESTClient 封装的一种动态客户端,与 ClientSet 不同的是,DynamicClient 能处理包括 CRD 自定义资源在内的任意 kubernetes 资源。
能处理 CRD 的原因是 DynamicClient 内部实现了 Unstructured,用于处理非结构化的或者说未知结构的数据结构,这是处理 CRD 类型资源的关键,而 ClientSet 内部的数据都是结构化的,即知道每种 Resource 和 Version 对应的具体资源数据类型。
需要注意的是,DynamicClient 不是类型安全的,使用 DynamicClient 对资源进行交互时尤其要注意指针问题,操作不当可能会导致程序崩溃。
DynamicClient 初始化
config, err := clientcmd.BuildConfigFromFlags("", "./configs/kubeconfig.conf")
if err != nil{
panic(err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil{
panic(err)
}
DynamicClient 资源操作示例
resource := schema.GroupVersionResource{
Version: "v1",
Resource: "pods"
}
obj, err := dynamicClient.Resource(resource)
.Namespace("yingchi")
.List(metav1.ListOptions{})
if err != nil{
panic(err)
}
pods := &corev1.PodList{}
err = runtime.DefaultUnstructuredConverter
.FromUnstructured(obj.UnstructuredContent(), pods)
Informer 机制
Client-go 包中一个相对较为高端的设计在于 Informer 的设计,如果想要资源交互更优雅的模式,就要用到 Informer 的机制。
通过之前的例子我们知道可以直接通过 Kubernetes API 交互,但是考虑一点就是交互的形式,Informer设计为List/Watch 的方式。Informer 在初始化的时先通过 List 去从 Kubernetes API 中取出资源的全部 object 对象,并同时缓存,然后后面通过 Watch 的机制去监控资源,这样的话,通过 Informer 及其缓存,我们就可以直接和Informer 交互而不是每次都和 Kubernetes API 交互,避免了轮询 kube-apiserver 等方式对 apiserver 带来的访问压力。
Informer 提供了事件 handler 机制,并会触发回调,这样上层应用如 Controller 就可以基于异步回调的方式进行具体业务逻辑的处理。因为 Informer 通过 List、Watch 机制可以监控到所有资源的所有事件,因此只要给Informe r添加 ResourceEventHandler 实例的回调函数实例取实现 OnAdd(obj interface{})
OnUpdate(oldObj, newObj interface{})
和 OnDelete(obj interface{})
这三个方法,就可以处理好资源的创建、更新和删除操作。Kubernetes 中各种controller都会用到Informer。
Informer 是 client-go 中比较精髓的