Client-go 四种客户端交互对象
Client-go 四种客户端交互对象

RESTClient
是最基础的客户端。RESTClient
对HTTP Request
进⾏了封装,实现了 RESTful ⻛格的 API 。ClientSet
、DynamicClient
及DiscoveryClient
客户端都是基于RESTClient
实现的。
ClientSet
在RESTClient
的基础上封装了对Resource
和Version
的管理⽅法。每⼀个Resource
可以理解为⼀个客户端,⽽ClientSet
则是多个客户端的集合,每⼀个Resource
和Version
都以函数的⽅式暴露给开发者。ClientSet
只能够处理Kubernetes
内 置资源,它是通过client-gen
代码⽣成器⾃动⽣成的。
DynamicClient
与ClientSet
最⼤的不同之处是,ClientSet
仅能访问Kubernetes
⾃带的资源(即 Client 集合内的资源),不能直接访问CRD
⾃定义资源。DynamicClient
能够处理Kubernetes
中的所有资源对象,包括Kubernetes
内置资源与CRD
⾃定义资源。
DiscoveryClient
发现客户端,⽤于发现kube-apiserver
所⽀持的资源组、资源版本、资源信息(即Group、Versions、Resources
)。
以上 4 种客户端: RESTClient
、 ClientSet
、 DynamicClient
、 DiscoveryClient
都可以通过 kubeconfig
配置信息连接到指定的 Kubernetes API Server
。
RestClient 客户端对象
RESTClient
是最基础的客户端。其他的ClientSet
、DynamicClient
及DiscoveryClient
都是基于RESTClient
实现的。RESTClient
对HTTP Request
进⾏了封装,实现了RESTful
⻛格的API
。它具有很⾼的灵活性,数据不依赖于⽅法和资源,因此RESTClient
能够处理多种类型的调⽤,返回不同的数据格式。
类似于 kubectl
命令,通过 RESTClient
列出 kube-system
运⾏的 Pod
资源对象, RESTClient Example
代码⽰例 :
package main
import (
"context"
"flag"
"fmt"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"path/filepath"
)
func main() {
var kubeconfig *string
// home是家目录,如果能取得家目录的值,就可以用来做默认值
if home:=homedir.HomeDir(); home != "" {
// 如果输入了kubeconfig参数,该参数的值就是kubeconfig文件的绝对路径,
// 如果没有输入kubeconfig参数,就用默认路径~/.kube/config
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
// 如果取不到当前用户的家目录,就没办法设置kubeconfig的默认目录了,只能从入参中取
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
// 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
// kubeconfig加载失败就直接退出了
if err != nil {
panic(err.Error())
}
// 参考path : /api/v1/namespaces/{namespace}/pods
config.APIPath = "api"
// pod的group是空字符串
config.GroupVersion = &corev1.SchemeGroupVersion
// 指定序列化工具
config.NegotiatedSerializer = scheme.Codecs
// 根据配置信息构建restClient实例
restClient, err := rest.RESTClientFor(config)
if err!=nil {
panic(err.Error())
}
// 保存pod结果的数据结构实例
result := &corev1.PodList{}
// 指定namespace
namespace := "kube-system"
// 设置请求参数,然后发起请求
// GET请求
err = restClient.Get().
// 指定namespace,参考path : /api/v1/namespaces/{namespace}/pods
Namespace(namespace).
// 查找多个pod,参考path : /api/v1/namespaces/{namespace}/pods
Resource("pods").
// 指定大小限制和序列化工具
VersionedParams(&metav1.ListOptions{Limit:100}, scheme.ParameterCodec).
// 请求
Do(context.TODO()).
// 结果存入result
Into(result)
if err != nil {
panic(err.Error())
}
// 打印名称
fmt.Printf("Namespace\t Status\t\t Name\n")
// 每个pod都打印Namespace、Status.Phase、Name三个字段
for _, d := range result.Items {
fmt.Printf("%v\t %v\t %v\n",
d.Namespace,
d.Status.Phase,
d.Name)
}
}
运⾏以上代码,列出 kube-system
命名空间下的所有 Pod
资源对象的相关信息。⾸先加载 kubeconfig
配置信息,并设置 config.APIPath
请求的 HTTP
路径。然后设置 config.GroupVersion
请求的资源组/资源版本。最后设置 config.NegotiatedSerializer
数据的编解码器。
rest.RESTClientFor
函数通过 kubeconfig
配置信息实例化 RESTClient
对象, RESTClient
对象构建 HTTP
请求参数,例如 Get
函数设置请求⽅法为 get
操作,它还⽀持 Post
、Put
、Delete
、Patch
等请求⽅法。 Namespace
函数设置请求的命名空间。 Resource
函数设置请求的资源名称。 VersionedParams
函数将⼀些查询选项(如 limit、TimeoutSeconds
等)添加到请求参数中。通过 Do
函数执⾏该请求,并将 kube-apiserver
返回的结果( Result 对象
)解析到 corev1.PodList
对象中。最终格式化输出结果。
RESTClient
发送请求的过程对 Go
语⾔标准库 net/http
进⾏了封装,由 Do→request
函数实现,代码⽰例如下:
代码路径: vendor/k8s.io/client-go/rest/request.go
func (r *Request) Do(ctx context.Context) Result {
var result Result
err := r.request(ctx, func(req *http.Request, resp *http.Response) {
result = r.transformResponse(resp, req)
})
if err != nil {
return Result{err: err}
}return result
}
请求发送之前需要根据请求参数⽣成请求的 RESTful URL
,由 r.URL.String
函数完成。例如,在 RESTClient Example
代码⽰例中,根据请求参数⽣成请求的 RESTful URL
为 http://127.0.0.1:8080/api/v1/namespaces/kube-system/pods?limit=500
,其中 api
参数为 v1
,namespace
参数为 system
,请求的资源为 pods
, limit
参数表⽰最多检索出 500
条信息。
最后通过 Go
语⾔标准库 net/http
向 RESTful URL
(即 kube-apiserver
)发送请求,请求得到的结果存放在 http.Response
的 Body
对象中, fn 函数
(即 transformResponse
)将结果转换为资源对象。当函数退出时,会通过 resp.Body.Close
命令进⾏关闭,防⽌内存溢出。
ClientSet客户端对象
RESTClient
是⼀种最基础的客户端,使⽤时需要指定Resource
和Version
等信息,编写代码时需要提前知道Resource
所在的Group
和对应的Version
信息。相⽐RESTClient
,ClientSet
使⽤起来更加便捷,⼀般情况下,开发者对Kubernetes
进⾏⼆次开发时通常使⽤ClientSet
。
ClientSet
在RESTClient
的基础上封装了对Resource
和Version
的管理⽅法。每⼀个Resource
可以理解为⼀个客户端,⽽ClientSet
则是多个客户端的集合,每⼀个Resource
和Version
都以函数的⽅式暴露给开发者,例如,ClientSet
提供的RbacV1
、CoreV1
、NetworkingV1
等接⼝函数
注意: ClientSet
仅能访问 Kubernetes
⾃⾝内置的资源(即客户端集合内的资源),不能直接访问 CRD
⾃定义资源。 如果需要 ClientSet
访问 CRD
⾃定义资源,可以通过 client-gen
代码⽣成器重新⽣成 ClientSet
,在 ClientSet
集合中⾃动⽣成与 CRD
操作相关的接⼝

通过 ClientSet
创建⼀个新的命名空间 xiaoyuan
以及⼀个新的 deployment
, ClientSet Example
代码,类似于 kubectl
命令
⽰例如下:
package main
import (
"context"
"flag"
"fmt"
"path/filepath"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
const (
NAMESPACE = "xiaoyuan"
DEPLOYMENTNAME = "kubecto-deploy"
IMAGE = "nginx:1.13"
PORT = 80
REPLICAS = 2
)
func main() {
var kubeconfig *string
// home是家目录,如果能取得家目录的值,就可以用来做默认值
if home := homedir.HomeDir(); home != "" {
// 如果输入了kubeconfig参数,该参数的值就是kubeconfig文件的绝对路径,
// 如果没有输入kubeconfig参数,就用默认路径~/.kube/config
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) kubeconfig文件的绝对路径")
} else {
kubeconfig = flag.String("kubeconfig", "", "kubeconfig文件的绝对路径")
}
flag.Parse()
// 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
// kubeconfig加载失败就直接退出了
if err != nil {
panic(err)
}
// 实例化clientset对象
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// 引用namespace的函数
createNamespace(clientset)
// 引用deployment的函数
createDeployment(clientset)
}
// 新建namespace
func createNamespace(clientset *kubernetes.Clientset) {
namespaceClient := clientset.CoreV1().Namespaces()
namespace := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: NAMESPACE,
},
}
result, err := namespaceClient.Create(context.TODO(), namespace, metav1.CreateOptions{})
if err!=nil {
panic(err.Error())
}
fmt.Printf("Create namespace %s \n", result.GetName())
}
// 新建deployment
func createDeployment(clientset *kubernetes.Clientset) {
//如果希望在default命名空间下场景可以引用apiv1.NamespaceDefault默认字符
//deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
//拿到deployment的客户端
deploymentsClient := clientset.AppsV1().Deployments(NAMESPACE)
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: DEPLOYMENTNAME,
},
Spec: appsv1.DeploymentSpec{
Replicas: int32Ptr(REPLICAS),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "kubecto",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "kubecto",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "web",
Image: IMAGE,
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: PORT,
},
},
},
},
},
},
},
}
// Create Deployment
fmt.Println("Creating deployment...")
result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
panic(err)
}
fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())
}
//引用replicas带入副本集
func int32Ptr(i int32) *int32 { return &i }
运⾏以上代码,会创建 2
个 nginx
的 deployment
。⾸先加载 kubeconfig
配置信息, kubernetes.NewForConfig
通过 kubeconfig
配置信息实例化 clientset
对象,该对象⽤于管理所有 Resource
的客户端。
DynamicClient客户端对象
DynamicClient
是⼀种动态客户端,它可以对任意Kubernetes
资源进⾏RESTful
操作,包括CRD
⾃定义资源。DynamicClient
与ClientSet
操作类似,同样封装了RESTClient
,同样提供了Create
、Update
、Delete
、Get
、List
、Watch
、Patch
等⽅法
DynamicClient
与ClientSet
最⼤的不同之处是,ClientSet
仅能访问Kubernetes
⾃带的资源(即客户端集合内的资源),不能直接访问CRD
⾃定义资源。ClientSet
需要预先实现每种Resource
和Version
的操作,其内部的数据都是结构化数据(即已知数据结构)。⽽DynamicClient
内部实现了Unstructured
,⽤于处理⾮结构化数据结构(即⽆法提前预知数据结构),这也是DynamicClient
能够处理CRD
⾃定义资源的关键
注意: DynamicClient
不是类型安全的,因此在访问 CRD
⾃定义资源时需要特别注意。例如,在操作指针不当的情况下可能会导致程序崩溃
DynamicClient
的处理过程将Resource
(例如PodList
)转换成Unstructured
结构类型,Kubernetes
的所有Resource
都可以转换为该结构类型。处理完成后,再将Unstructured
转换成PodList
。整个过程类似于Go
语⾔的interface{}
断⾔转换过程。另外,Unstructured
结构类型是通过map[string]interface{}
转换的。
通过
DynamicClient
创建deployment
,并使⽤list
列出当前pod
的名称和数量,类似kubectl
命令 ,DynamicClient Example
代码⽰例:
package main
import (
"bufio"
"context"
"flag"
"fmt"
"os"
"path/filepath"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
func main() {
var kubeconfig *string
// home是家目录,如果能取得家目录的值,就可以用来做默认值
// 如果输入了kubeconfig参数,该参数的值就是kubeconfig文件的绝对路径,
// 如果没有输入kubeconfig参数,就用默认路径~/.kube/config
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
//定义函数内的变量
namespace := "default"
replicas := 2
deployname := "ku"
image := "nginx:1.17"
// 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
// kubeconfig加载失败就直接退出了
if err != nil {
panic(err)
}
// dynamic.NewForConfig实例化clientset对象
client, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
//使用schema的包带入gvr
deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
//定义结构化数据结构
deployment := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": deployname,
},
"spec": map[string]interface{}{
"replicas": replicas,
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "demo",
},
},
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "demo",
},
},
"spec": map[string]interface{}{
"containers": []map[string]interface{}{
{
"name": "web",
"image": image,
"ports": []map[string]interface{}{
{
"name": "http",
"protocol": "TCP",
"containerPort": 80,
},
},
},
},
},
},
},
},
}
// 创建 Deployment
fmt.Println("创建 deployment...")
result, err := client.Resource(deploymentRes).Namespace(namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
panic(err)
}
fmt.Printf("创建 deployment %q.\n", result.GetName())
// 列出 Deployments
prompt()
fmt.Printf("在命名空间中列出deployment %q:\n", apiv1.NamespaceDefault)
list, err := client.Resource(deploymentRes).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err)
}
for _, d := range list.Items {
replicas, found, err := unstructured.NestedInt64(d.Object, "spec", "replicas")
if err != nil || !found {
fmt.Printf("Replicas not found for deployment %s: error=%s", d.GetName(), err)
continue
}
fmt.Printf(" * %s (%d replicas)\n", d.GetName(), replicas)
}
}
func prompt() {
fmt.Printf("--------------> 按回车键继续 <--------------.")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
break
}
if err := scanner.Err(); err != nil {
panic(err)
}
fmt.Println()
}
⾸先加载 kubeconfig
配置信息, dynamic.NewForConfig
通过 kubeconfig
配置信息实例化 dynamicClient
对象,该对象⽤于管理 Kubernetes
的所有 Resource
的客户端,例如对 Resource
执⾏ Create
、Update
、Delete
、Get
、List
、Watch
、Patch
等操作
DiscoveryClient客户端对象
DiscoveryClient
是发现客户端,它主要⽤于发现Kubernetes API Server
所⽀持的资源组、资源版本、资源信息。Kubernetes API Server
⽀持很多资源组、资源版本、资源信息,开发者在开发过程中很难记住所有信息,此时可以通过DiscoveryClient
查看所⽀持的资源组、资源版本、资源信息。
kubectl
的api-versions
和api-resources
命令输出也是通过DiscoveryClient
实现的。另外,DiscoveryClient
同样在RESTClient
的基础上进⾏了封装
DiscoveryClient
除了可以发现Kubernetes API Server
所⽀持的资源组、资源版本、资源信息,还可以将这些信 息存储到本地,⽤于本地缓存( Cache )
,以减轻对Kubernetes API Server
访问的压⼒。在运⾏Kubernetes
组件的机器上,缓存信息默认存储于〜/.kube/cache
和〜/.kube/http-cache
下
类似于
kubectl
命令,通过DiscoveryClient
列出Kubernetes API Server
所⽀持的资源组、资源版本、资源信息,DiscoveryClient Example
代码⽰例如下:
package main
import (
"flag"
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"path/filepath"
)
func main() {
var kubeconfig *string
// home是家目录,如果能取得家目录的值,就可以用来做默认值
if home:=homedir.HomeDir(); home != "" {
// 如果输入了kubeconfig参数,该参数的值就是kubeconfig文件的绝对路径,
// 如果没有输入kubeconfig参数,就用默认路径~/.kube/config
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
// 如果取不到当前用 户的家目录,就没办法设置kubeconfig的默认目录了,只能从入参中取
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
// 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
// kubeconfig加载失败就直接退出了
if err != nil {
panic(err.Error())
}
// 新建discoveryClient实例
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
panic(err.Error())
}
// 获取所有分组和资源数据
APIGroup, APIResourceListSlice, err := discoveryClient.ServerGroupsAndResources()
if err != nil {
panic(err.Error())
}
// 先看Group信息
fmt.Printf("APIGroup :\n\n %v\n\n\n\n",APIGroup)
// APIResourceListSlice是个切片,里面的每个元素代表一个GroupVersion及其资源
for _, singleAPIResourceList := range APIResourceListSlice {
// GroupVersion是个字符串,例如"apps/v1"
groupVerionStr := singleAPIResourceList.GroupVersion
// ParseGroupVersion方法将字符串转成数据结构
gv, err := schema.ParseGroupVersion(groupVerionStr)
if err != nil {
panic(err.Error())
}
fmt.Println("*****************************************************************")
fmt.Printf("GV string [%v]\nGV struct [%#v]\nresources :\n\n", groupVerionStr, gv)
// APIResources字段是个切片,里面是当前GroupVersion下的所有资源
for _, singleAPIResource := range singleAPIResourceList.APIResources {
fmt.Printf("%v\n", singleAPIResource.Name)
}
}
}
运⾏以上代码,列出 Kubernetes API Server
所⽀持的 资源组、资源版本、资源信息。⾸先加载 kubeconfig
配置信息, discovery.NewDiscoveryClientForConfig
通过 kubeconfig
配置信息实例化 discoveryClient
对象,该对象是⽤于发现 Kubernetes API Server
所⽀持的资源组、资源版本、资源信息的客户端
discoveryClient.ServerGroupsAndResources
函数会返回Kubernetes API Server
所⽀持的资源组、资源版本、资源信息(即APIResourceList
),通过遍历APIResourceList
输出信息。
获取
Kubernetes API Server
所⽀持的资源组、资源版本、资源信息Kubernetes API Server
暴露出/api
和/apis
接⼝。DiscoveryClient
通过RESTClient
分别请求/api
和/apis
接⼝,从⽽获取Kubernetes API Server
所⽀持的资源组、资源版本、资源信息。其核⼼实现位于ServerGroupsAndResources→ServerGroups
中,代码⽰例如下:
代码路径: vendor/k8s.io/client-go/discovery/discovery_client.go
// ServerGroups返回⽀持的组,以及⽀持的版本和⾸选版本等信息
func (d *DiscoveryClient) ServerGroups() (apiGroupList *metav1.APIGroupList, err error) {
// 获取在/api公开的groupVersions
v := &metav1.APIVersions{}
err = d.restClient.Get().AbsPath(d.LegacyPrefix).Do(context.TODO()).Into(v)
apiGroup := metav1.APIGroup{}
if err == nil && len(v.Versions) != 0 {
apiGroup = apiVersionsToAPIGroup(v)
}
if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
return nil, err
}
// 获取在/api公开的groupVersions
apiGroupList = &metav1.APIGroupList{}
err = d.restClient.Get().AbsPath("/apis").Do(context.TODO()).Into(apiGroupList)
if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
return nil, err
}
// 为了与v1.0服务器兼容,如果它是403或404,忽略并返回我们从/api得到的内容
if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
apiGroupList = &metav1.APIGroupList{}
}
// 如果不是空的,将从/api中检索到的组前置到列表中
if len(v.Versions) != 0 {
apiGroupList.Groups = append([]metav1.APIGroup{apiGroup}, apiGroupList.Groups...)
}
return apiGroupList, nil
}
⾸先, DiscoveryClient
通过 RESTClient
请求 /api
接⼝,将请求结果存放于 metav1.APIVersions
结构体中。然后,再次通过 RESTClient
请求 /apis
接⼝,将请求结果存放于 metav1.APIGroupList
结构体中。最后,将 /api
接⼝中检索到的资源组信息合并到 apiGroupList
列表中并返回
本地缓存的 DiscoveryClient
DiscoveryClient
可以将资源相关信息存储于本地,默认存储位置为 〜/.kube/cache
和 〜/.kube/http-cache
。缓存可以减轻 client-go
对 Kubernetes API Server
的访问压⼒。默认每 10 分钟
与 Kubernetes API Server
同步⼀次,同步周期较⻓,因为资源组、源版本、资源信息⼀般很少变动。本地缓存的 DiscoveryClient
DiscoveryClient
第⼀次获取资源组、资源版本、资源信息时,⾸先会查询本地缓存,如果数据不存在(没有命中)则请求Kubernetes API Server
接⼝(回源),Cache
将Kubernetes API Server
响应的数据存储在本地⼀份并返回给DiscoveryClient
。当下⼀次DiscoveryClient
再次获取资源信息时,会将数据直接从本地缓存返回(命中)给DiscoveryClient
。本地缓存的默认存储周期为10 分钟
。代码⽰例如下

代码路径: vendor/k8s.io/client-go/discovery/cached/disk/cached_discovery.go