一文带您了解 Client-Go 的四种客户端

时间:2022-10-10 13:01:17

Client-Go 简介

Client-Go 是负责与 Kubernetes APIServer 服务进行交互的客户端库,利用 Client-Go 与Kubernetes APIServer 进行的交互访问,来对 Kubernetes 中的各类资源对象进行管理操作,包括内置的资源对象及CRD。

Client-Go 不仅被 Kubernetes 项目本身使用,其它围绕着 Kubernetes 的生态,也被大量的使用,例如:kubectl、ETCD-operator等等。

Client-Go 客户端

Client-Go 共提供了 4 种与 Kubernetes APIServer 交互的客户端。分别是 RESTClient、DiscoveryClient、ClientSet、DynamicClient。

  • RESTClient:最基础的客户端,主要是对 HTTP 请求进行了封装,支持 Json 和 Protobuf 格式的数据。
  • DiscoveryClient:发现客户端,负责发现 APIServer 支持的资源组、资源版本和资源信息的。
  • ClientSet:负责操作 Kubernetes 内置的资源对象,例如:Pod、Service等。
  • DynamicClient:动态客户端,可以对任意的 Kubernetes 资源对象进行通用操作,包括 CRD。

一文带您了解 Client-Go 的四种客户端

RESTClient

RESTClient 是所有客户端的父类,这也是为啥前面说,它是最基础的客户端的原因。

它提供了 RESTful 对应的方法的封装,如:Get()、Put()、Post()、Delete() 等。通过这些封装发方法与 Kubernetes APIServer RESTful API 进行交互。

因为它是所有客户端的父类,所以它可以操作 Kubernetes 内置的所有资源对象以及 CRD。

运行以下代码,将得到 default 下的 Pod 部分相关资源信息。

package main

import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

/*
@Author : lanyulei
@Desc :
*/

func main() {
// 加载配置文件,生成 config 对象
config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
if err != nil {
panic(err.Error())
}

// 配置 API 路径
config.APIPath = "api"

// 配置分组版本
config.GroupVersion = &corev1.SchemeGroupVersion

// 配置数据的编解码器
config.NegotiatedSerializer = scheme.Codecs

// 实例化 RESTClient
restClient, err := rest.RESTClientFor(config)
if err != nil {
panic(err.Error())
}

// 定义返回接收值
result := &corev1.PodList{}

err = restClient.Get().
Namespace("default"). // 查询的 Namespace
Resource("pods"). // 查询的资源类型
VersionedParams(&metav1.ListOptions{Limit: 100}, scheme.ParameterCodec). // 参数及序列化工具
Do(context.TODO()). // 发送请求
Into(result) // 写入返回值
if err != nil {
panic(err.Error())
}

// 输出返回结果
for _, d := range result.Items {
fmt.Printf("namespace: %v, name: %v, status: %v\n", d.Namespace, d.Name, d.Status.Phase)
}
}

输出结果

➜  demo1 go run main.go
namespace: default, name: 1231-589c58c8c5-27r94, status: Pending
namespace: default, name: 1231-b6896fd77-7bkgk, status: Pending
namespace: default, name: web-96d5df5c8-rcw6r, status: Running
namespace: default, name: web-96d5df5c8-rdzpr, status: Running

RESTClient 其实就是底层使用 net/http 库,将调用 Kubernetes APIServer 的 API 请求,进行了封装,对参数和返回结果进行了序列化及反序列化,有兴趣的话,可以看下源码,分析一下请求过程。这里就不多介绍了。

ClientSet

上面介绍了 RESTClient,它虽然可以操作 Kubernetes 的所有资源对象,但是使用起来确实比较复杂,需要配置的参数过于繁琐,因此,为了更优雅的更方便的与 Kubernetes APIServer 进行交互,则需要进一步的封装。

前面有过介绍,ClientSet 是基于 RESTClient 的封装,同时 ClientSet 是使用预生成的 API 对象与 APIServer 进行交互的,这样做更方便进行二次开发。

ClientSet 是一组资源对象客户端的集合,例如负责操作 Pods、Services 等资源的 CoreV1Client,负责操作 Deployments、DaemonSets 等资源的 AppsV1Client 等。通过这些资源对象客户端提供的操作方法,即可对 Kubernetes 内置的资源对象进行 Create、Update、Get、List、Delete 等操作。

运行以下代码,将得到 default 下的 Pod 部分相关资源信息。

package main

import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

/*
@Author : lanyulei
@Desc :
*/

func main() {
// 加载配置文件,生成 config 对象
config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
if err != nil {
panic(err.Error())
}

// 实例化 ClientSet
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}

// 查询 default 下的 pods 部门资源信息
pods, err := clientset.
CoreV1(). // 实例化资源客户端,这里标识实例化 CoreV1Client
Pods("default"). // 选择 namespace,为空则表示所有 Namespace
List(context.TODO(), metav1.ListOptions{}) // 查询 pods 列表
if err != nil {
panic(err.Error())
}

// 输出 Pods 资源信息
for _, item := range pods.Items {
fmt.Printf("namespace: %v, name: %v\n", item.Namespace, item.Name)
}
}

输出结果

➜  demo1 go run main.go 
namespace: default, name: 1231-589c58c8c5-27r94
namespace: default, name: 1231-b6896fd77-7bkgk
namespace: default, name: web-96d5df5c8-rcw6r
namespace: default, name: web-96d5df5c8-rdzpr

DynamicClient

DynamicClient 是一种动态客户端,通过动态指定资源组、资源版本和资源等信息,来操作任意的 Kubernetes 资源对象的一种客户端。即不仅仅是操作 Kubernetes 内置的资源对象,还可以操作 CRD。这也是与 ClientSet 最明显的一个区别。

使用 ClientSet 的时候,程序会将所用的版本与类型紧密耦合。而 DynamicClient 使用嵌套的 map[string]interface{} 结构存储 Kubernetes APIServer 的返回值,使用反射机制,在运行的时候,进行数据绑定,这种方式更加灵活,但是却无法获取强数据类型的检查和验证。

此外,在介绍 DynamicClient 之前,还需要了解另外两个重要的知识点,Object.runtime 接口和 Unstructured 结构体。

  • Object.runtime:Kubernetes 中的所有资源对象,都实现了这个接口,其中包含 DeepCopyObject 和 GetObjectKind 的方法,分别用于对象深拷贝和获取对象的具体资源类型。
  • Unstructured:包含 map[string]interface{} 类型字段,在处理无法预知结构的数据时,将数据值存入 interface{} 中,待运行时利用反射判断。该结构体提供了大量的工具方法,便于处理非结构化的数据。

运行以下代码,将得到 default 下的 Pod 部分相关资源信息。

package main

import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
)

/*
@Author : lanyulei
@Desc :
*/

func main() {
// 加载配置文件,生成 config 对象
config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
if err != nil {
panic(err.Error())
}

// 实例化 DynamicClient
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
panic(err.Error())
}

// 设置要请求的 GVR
gvr := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "pods",
}

// 发送请求,并得到返回结果
unStructData, err := dynamicClient.Resource(gvr).Namespace("default").List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err.Error())
}

// 使用反射将 unStructData 的数据转成对应的结构体类型,例如这是是转成 v1.PodList 类型
podList := &corev1.PodList{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(
unStructData.UnstructuredContent(),
podList,
)
if err != nil {
panic(err.Error())
}

// 输出 Pods 资源信息
for _, item := range podList.Items {
fmt.Printf("namespace: %v, name: %v\n", item.Namespace, item.Name)
}
}

输出结果

➜  demo1 go run main.go
namespace: default, name: 1231-589c58c8c5-27r94
namespace: default, name: 1231-b6896fd77-7bkgk
namespace: default, name: web-96d5df5c8-rcw6r
namespace: default, name: web-96d5df5c8-rdzpr

DiscoveryClient

前面咱们介绍了 3 种客户端,都是针对与资源对象管理的。而 DiscoveryClient 则是针对于资源的。用于查看当前 Kubernetes 集群支持那些资源组、资源版本、资源信息。

运行以下代码,将得到 Kubernetes 集群的资源列表。

package main

import (
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/clientcmd"
)

/*
@Author : lanyulei
@Desc :
*/

func main() {
// 加载配置文件,生成 config 对象
config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
if err != nil {
panic(err.Error())
}

// 实例化 DiscoveryClient
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
panic(err.Error())
}

_, apiResources, err := discoveryClient.ServerGroupsAndResources()
if err != nil {
panic(err.Error())
}

for _, list := range apiResources {
gv, err := schema.ParseGroupVersion(list.GroupVersion)
if err != nil {
panic(err.Error())
}
for _, resource := range list.APIResources {
fmt.Printf("name: %v, group: %v, version: %v\n", resource.Name, gv.Group, gv.Version)
}
}
}

输出结果

➜  demo1 go run main.go
name: bindings, group: , version: v1
name: componentstatuses, group: , version: v1
name: configmaps, group: , version: v1
name: endpoints, group: , version: v1
name: events, group: , version: v1
...