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
      3
      type 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
    9
    func 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 要操作的究竟是什么类型。
      下面进行源码分析。
  • Resource()其实是 DynamicClient 实现了接口 dynamic.Interface

    • 接口中只有一个 Resource()
    • 参数是一个 GVR,指定group、version、resource,就可以确定到底是哪个资源了
    • 返回值为 NamespaceableResourceInterface 接口类型
      1
      2
      3
      type Interface interface {
      Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface
      }
  • DynamicClient 实现了这个接口,自然也实现了Resource这个方法

    • 下面代码返回的是 NamespaceableResourceInterface 接口的实现类型 dynamicResourceClient 的对象
    • 这个对象就是我们最终可以用于操作 你所指定资源 的 Client 了
      1
      2
      3
      func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
      return &dynamicResourceClient{client: c, resource: resource}
      }
  • dynamicResourceClient 类型

    • 可见,dynamicResourceClient 包含了DynamicClient对象、要操作的类型的GVR、还有资源的ns
      1
      2
      3
      4
      5
      type dynamicResourceClient struct {
      client *DynamicClient
      namespace string
      resource schema.GroupVersionResource
      }
  • 可以调用它的Namespace方法,为namespace字段赋值

    • 这个方法就是NamespaceableResourceInterface 接口提供的
      1
      2
      3
      4
      5
      func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface {
      ret := *c
      ret.namespace = ns
      return &ret
      }

认识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
    6
    type 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
    13
    type 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
      4
      type UnstructuredConverter interface {
      ToUnstructured(obj interface{}) (map[string]interface{}, error)
      FromUnstructured(u map[string]interface{}, obj interface{}) error
      }
  • UnstructuredConverter 接口只有一个实现类 unstructuredConverter

    • 位于 staging/src/k8s.io/apimachinery/pkg/runtime/converter.go
      1
      2
      3
      4
      5
      6
      7
      8
      type 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
      }
  • unstructuredConverter 实现了UnstructuredConverter 接口的 两个方法

    1
    2
    func (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
    12
    var (
    ......
    // 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
      51
      package 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
      2
      deploy.Name: coredns
      deploy.namespace: kube-system