client-go之DyamicClient
DynamicClient是什么
- Clientset是一系列RESTClient的集合,创建一个 Clientset 实例,其中已经包含了所有kubernetes内置资源的RESTClient,可是对于CustomResource(CR)资源,Clientset就没有操作它们的RESTClient了。
- 因此,client-go特意提供了一种 客户端,即 DynamicClient,可以操作任意的kubernetes资源,包括 内置资源 + CR 资源
DynamicClient结构体分析
- 位于 /client-go/dynamic/simple.go
- DynamicClient结构体
- 可以看到,DynamicClient 中只包含一个RESTClient的字段 client
- client 类型为 rest.Interface,即为 RESTClient实现的那个接口,可以在4.2.2中看到
1
2
3type DynamicClient struct {
client rest.Interface
}
DynamicClient的常用方法
dynamic.NewForConfig()方法
- 位于 /client-go/dynamic/simple.go,是一个函数,位于dynamic包下,所以直接点就可以使用
- 该方法用于创建一个 DynamicClient 实例,入参也是 *rest.Config 类型
1
2
3
4
5
6
7
8
9func NewForConfig(inConfig *rest.Config) (*DynamicClient, error) {
config := ConfigFor(inConfig)
httpClient, err := rest.HTTPClientFor(config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(config, httpClient)
}
DynamicClient 的 实例方法
结论:
- DynamicClient 只有一个实例方法: Resource(resource schema.GroupVersionResource),用于指定当前 DynamicClient 要操作的究竟是什么类型。
下面进行源码分析。
- DynamicClient 只有一个实例方法: Resource(resource schema.GroupVersionResource),用于指定当前 DynamicClient 要操作的究竟是什么类型。
Resource()其实是 DynamicClient 实现了接口 dynamic.Interface
- 接口中只有一个 Resource()
- 参数是一个 GVR,指定group、version、resource,就可以确定到底是哪个资源了
- 返回值为 NamespaceableResourceInterface 接口类型
1
2
3type Interface interface {
Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface
}
DynamicClient 实现了这个接口,自然也实现了Resource这个方法
- 下面代码返回的是 NamespaceableResourceInterface 接口的实现类型 dynamicResourceClient 的对象
- 这个对象就是我们最终可以用于操作 你所指定资源 的 Client 了
1
2
3func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
dynamicResourceClient 类型
- 可见,dynamicResourceClient 包含了DynamicClient对象、要操作的类型的GVR、还有资源的ns
1
2
3
4
5type dynamicResourceClient struct {
client *DynamicClient
namespace string
resource schema.GroupVersionResource
}
- 可见,dynamicResourceClient 包含了DynamicClient对象、要操作的类型的GVR、还有资源的ns
可以调用它的Namespace方法,为namespace字段赋值
- 这个方法就是NamespaceableResourceInterface 接口提供的
1
2
3
4
5func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface {
ret := *c
ret.namespace = ns
return &ret
}
- 这个方法就是NamespaceableResourceInterface 接口提供的
认识kubrenetes 的 Unstructured
为什么需要 Unstructured
- 前面我们使用 RESTClient 或 Clientset,调用的是某个资源的 Get、List 等方法,返回值都是确定的。比如 调用Pod资源的List,我们能够确定返回的一定是PodList,所以Pod的List方法,就写死了,返回值就是PodList
- 可当使用 DynamicClient 时,DynamicClient 没有确定的资源,资源类型是我们使用的时候才会去指定GVR的,所以 开发DynamicClient 的时候,Get 方法自然无法确定返回值类型。
- 那么kubernetes人员应该如何开发这个Get方法?
- 我们自然而然的能够想到,如果有一个通用的数据结构,可以表示所有类型的资源,问题就解决了
- Unstructured 应运而生。它就是一个 可以表示所有资源的类型。
Unstructured源码
位于 staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unsructured/unsructured.go 中
Unstructured 结构1
2
3
4
5
6type Unstructured struct {
// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
// map[string]interface{}
// children.
Object map[string]interface{}
}Unstructured 包含一个 map,键为string,value为interface{},因此value可以为任意类型
dynamicResourceClient对于 Unstructured 的应用
- 我们最终操作资源,其实使用的是 dynamicResourceClient,dynamicResourceClient 实现了 ResourceInterface 接口的所有方法,这些方法就是我们最终操作资源所调用的。下面我们看一下 ResourceInterface 接口的所有方法签名
1
2
3
4
5
6
7
8
9
10
11
12
13type ResourceInterface interface {
Create(ctx context.Context, obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error)
Update(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error)
UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error)
Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error
DeleteCollection(ctx context.Context, options metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error)
List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error)
Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error)
ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error)
} - Create方法因为不知道要创建什么资源,所以参数接收的是 obj *unstructured.Unstructured
- Get方法因为不知道返回的是什么资源,所以返回的是 *unstructured.Unstructured
- List方法因为不知道返回的是什么资源列表,所以返回的是 *unstructured.UnstructuredList。这个结构里面包含一个 []unstructured.Unstructured
Unstructured 与 资源对象的相互转换
既然 dynamicResourceClient 的方法 接收和返回的,很多都是 Unstructured 类型,那么我们就需要实现 真正的资源对象 与 Unstructured 的 相互转换
runtime包下,给我们提供了一个 UnstructuredConverter 接口,接口中提供了两个方法,分别用于 资源对象–>Unstructured 和 Unstructured–>资源对象
- 位于 staging/src/k8s.io/apimachinery/pkg/runtime/converter.go
1
2
3
4type UnstructuredConverter interface {
ToUnstructured(obj interface{}) (map[string]interface{}, error)
FromUnstructured(u map[string]interface{}, obj interface{}) error
}
- 位于 staging/src/k8s.io/apimachinery/pkg/runtime/converter.go
UnstructuredConverter 接口只有一个实现类 unstructuredConverter
- 位于 staging/src/k8s.io/apimachinery/pkg/runtime/converter.go
1
2
3
4
5
6
7
8type unstructuredConverter struct {
// If true, we will be additionally running conversion via json
// to ensure that the result is true.
// This is supposed to be set only in tests.
mismatchDetection bool
// comparison is the default test logic used to compare
comparison conversion.Equalities
}
- 位于 staging/src/k8s.io/apimachinery/pkg/runtime/converter.go
unstructuredConverter 实现了UnstructuredConverter 接口的 两个方法
1
2func (c *unstructuredConverter) ToUnstructured(obj interface{}) (map[string]interface{}, error):资源对象–>Unstructured
func (c *unstructuredConverter) FromUnstructured(u map[string]interface{}, obj interface{}) error:Unstructured–>资源对象不过,unstructuredConverter 类型开头小写,我们无法直接使用,kubernetes 创建了一个全局变量 DefaultUnstructuredConverter,类型就是unstructuredConverter,用以供外界使用;位于 staging/src/k8s.io/apimachinery/pkg/runtime/converter.go
1
2
3
4
5
6
7
8
9
10
11
12var (
......
// DefaultUnstructuredConverter performs unstructured to Go typed object conversions.
DefaultUnstructuredConverter = &unstructuredConverter{
mismatchDetection: parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR")),
comparison: conversion.EqualitiesOrDie(
func(a, b time.Time) bool {
return a.UTC() == b.UTC()
},
),
}
)总结:我们直接使用 runtime.DefaultUnstructuredConverter,调用它的 ToUnstructured 或 FromUnstructured 方法,就可以实现 Unstructured 与 资源对象的相互转换 了
DynamicClient使用示例
- 需求:获取 kube-system 命名空间下 name=coredns 的 deploy 对象
- 步骤
- 同样是先 创建一个客户端配置config
- 使用 dynamic.NewForConfig(),创建一个 DynamicClient 对象
- 使用 DynamicClient.Resource(),指定要操作的资源对象,获取到该资源的 Client
- 先为该Client指定ns,然后调用 Client 的 Get() 方法,获取到该资源对象
- 调用 runtime.DefaultUnstructuredConverter.FromUnstructured(),将 unstructured 反序列化成 Deployment 对象
代码编写输出结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51package main
import (
"context"
"fmt"
appsv1 "k8s.io/api/apps/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"
)
func main() {
// 同样是先 创建一个客户端配置config
config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
if err != nil {
panic(err)
}
// 使用 dynamic.NewForConfig(),创建一个 DynamicClient 对象
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
// 使用 DynamicClient.Resource(),指定要操作的资源对象,获取到该资源的 Client
dynamicResourceClient := dynamicClient.Resource(schema.GroupVersionResource{
Group: "apps",
Version: "v1",
Resource: "deployments",
})
// 先为该Client指定ns,然后调用 Client 的 Get() 方法,获取到该资源对象
unstructured, err := dynamicResourceClient.
Namespace("kube-system").
Get(context.TODO(), "coredns", metav1.GetOptions{})
if err != nil {
panic(err)
}
// 调用 runtime.DefaultUnstructuredConverter.FromUnstructured(),将 unstructured 反序列化成 Deployment 对象
deploy := &appsv1.Deployment{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.UnstructuredContent(), deploy)
if err != nil {
panic(err)
}
// 打印 deploy 名称和命名空间
fmt.Printf("deploy.Name: %s\ndeploy.namespace: %s", deploy.Name, deploy.Namespace)
}1
2deploy.Name: coredns
deploy.namespace: kube-system