client-go dynamicClient解析

1. 前言

clientset客户端在处理deployment、service这些内置资源的时候很方便,每个资源都有其专属方法,配合官方API文档和数据结构定义,开发起来很高效。

但是处理k8s非内置资源时,比如CRD等,就得需要使用dynamic Client了

2. 知识储备

了解dynanicClient前,需要知道object.runtime和Unstructured。

2.1 Object.runtime

在kubernetes中创建的deployment、pod等都是资源对象,对应着具体的数据结构 ,这些数据结构都实现了统一接口,即Object.runtime,源码位置:

staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go,定义如下:

type Object interface {
	GetObjectKind() schema.ObjectKind
	DeepCopyObject() Object
}
  • DeepCopyObject():将内存中的对象克隆出来一个新对象。

  • GetObjectKind():在处理Object.runtime类型的变量时,只要调用器GetObjectKind()方法就知道其的具体的资源对象类型,资源对象都是Object.runtime的实现。

2.2 Unstructured

在实际环境中,可能会遇到一些无法预知的数据结构,只有在真正运行的时候才知道,此时会采用interface{}进行接收。在k8s中代码也是这样实现的:

staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go:

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结合其下面关联的方法,就可以灵活处理非结构化数据
在这里插入图片描述

Unstructured数据与资源对象转换

// 实例化一个PodList数据结构,用于接收从unstructObj转换后的结果
podList := &apiv1.PodList{}

// unstructObj
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)

具体转化过程:

FromUnstructured将对象从map[string]接口{}表示转换为具体类型。如果对象实现了它,则使用encodingjsonUnmarshalr;如果没有实现,则使用反射
func (c *unstructuredConverter) FromUnstructured(u map[string]interface{}, obj interface{}) error {
	return c.FromUnstructuredWithValidation(u, obj, false)
}
//FromUnstructuredWIthValidation将对象从map[string]接口{}表示转换为具体类型。如果对象实现了它,则使用encodingjsonUnmarshalr;如果没有实现,则使用反射。
// 它需要一个validationDirective,指示遇到未知字段时的行为。
func (c *unstructuredConverter) FromUnstructuredWithValidation(u map[string]interface{}, obj interface{}, returnUnknownFields bool) error {
	// 通过反射知道具体要转化的类型
	t := reflect.TypeOf(obj)
	value := reflect.ValueOf(obj)
	if t.Kind() != reflect.Pointer || value.IsNil() {
		return fmt.Errorf("FromUnstructured requires a non-nil pointer to an object, got %v", t)
	}

	fromUnstructuredContext := &fromUnstructuredContext{
		returnUnknownFields: returnUnknownFields,
	}
	// 这里面是具体设置
	err := fromUnstructured(reflect.ValueOf(u), value.Elem(), fromUnstructuredContext)
	if c.mismatchDetection {
		newObj := reflect.New(t.Elem()).Interface()
		newErr := fromUnstructuredViaJSON(u, newObj)
		if (err != nil) != (newErr != nil) {
			klog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err)
		}
		if err == nil && !c.comparison.DeepEqual(obj, newObj) {
			klog.Fatalf("FromUnstructured mismatch\nobj1: %#v\nobj2: %#v", obj, newObj)
		}
	}
	if err != nil {
		return err
	}
	if returnUnknownFields && len(fromUnstructuredContext.unknownFieldErrors) > 0 {
		sort.Slice(fromUnstructuredContext.unknownFieldErrors, func(i, j int) bool {
			return fromUnstructuredContext.unknownFieldErrors[i].Error() <
				fromUnstructuredContext.unknownFieldErrors[j].Error()
		})
		return NewStrictDecodingError(fromUnstructuredContext.unknownFieldErrors)
	}
	return nil
}
// 具体利用反射实现转化过程
func fromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error {
	sv = unwrapInterface(sv)
	if !sv.IsValid() {
		dv.Set(reflect.Zero(dv.Type()))
		return nil
	}
	st, dt := sv.Type(), dv.Type()

	switch dt.Kind() {
	case reflect.Map, reflect.Slice, reflect.Pointer, reflect.Struct, reflect.Interface:
		// Those require non-trivial conversion.
	default:
		// This should handle all simple types.
		if st.AssignableTo(dt) {
			dv.Set(sv)
			return nil
		}
		// We cannot simply use "ConvertibleTo", as JSON doesn't support conversions
		// between those four groups: bools, integers, floats and string. We need to
		// do the same.
		if st.ConvertibleTo(dt) {
			switch st.Kind() {
			case reflect.String:
				switch dt.Kind() {
				case reflect.String:
					dv.Set(sv.Convert(dt))
					return nil
				}
			case reflect.Bool:
				switch dt.Kind() {
				case reflect.Bool:
					dv.Set(sv.Convert(dt))
					return nil
				}
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
				reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				switch dt.Kind() {
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
					reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
					dv.Set(sv.Convert(dt))
					return nil
				case reflect.Float32, reflect.Float64:
					dv.Set(sv.Convert(dt))
					return nil
				}
			case reflect.Float32, reflect.Float64:
				switch dt.Kind() {
				case reflect.Float32, reflect.Float64:
					dv.Set(sv.Convert(dt))
					return nil
				}
				if sv.Float() == math.Trunc(sv.Float()) {
					dv.Set(sv.Convert(dt))
					return nil
				}
			}
			return fmt.Errorf("cannot convert %s to %s", st.String(), dt.String())
		}
	}

	// Check if the object has a custom JSON marshaller/unmarshaller.
	entry := value.TypeReflectEntryOf(dv.Type())
	if entry.CanConvertFromUnstructured() {
		return entry.FromUnstructured(sv, dv)
	}
	switch dt.Kind() {
	case reflect.Map:
		return mapFromUnstructured(sv, dv, ctx)
	case reflect.Slice:
		return sliceFromUnstructured(sv, dv, ctx)
	case reflect.Pointer:
		return pointerFromUnstructured(sv, dv, ctx)
	case reflect.Struct:
		return structFromUnstructured(sv, dv, ctx)
	case reflect.Interface:
		return interfaceFromUnstructured(sv, dv)
	default:
		return fmt.Errorf("unrecognized type: %v", dt.Kind())
	}

}
    // fromUnstructured只处理原始类型,对于数据结构会调用对应的xxxFromUnstructured方法处理,处理数据结构中每个字段又会调用fromUnstructured进行迭代,例如处理pointerFromUnstructured
func pointerFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error {
	st, dt := sv.Type(), dv.Type()

	if st.Kind() == reflect.Pointer && sv.IsNil() {
		dv.Set(reflect.Zero(dt))
		return nil
	}
	dv.Set(reflect.New(dt.Elem()))
	switch st.Kind() {
	case reflect.Pointer, reflect.Interface:
		return fromUnstructured(sv.Elem(), dv.Elem(), ctx)
	default:
		return fromUnstructured(sv, dv.Elem(), ctx)
	}
}

2.3 dynamicClient

定义:

type DynamicClient struct {
	client rest.Interface
}

关联Resource()方法,入参为GVR,返回的是另一个数据结构dynamicResourceClient

func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
	return &dynamicResourceClient{client: c, resource: resource}
}

通过上面代码可知,DynamicClient的关键是数据结构dynamicResourceClient及其关联方法,dynamicClient所有和资源相关的操作都是dynamicResourceClient在做,序列化和反序列化都交给unstructured.UnstructuredJSONScheme,与kubernetes的交互交给Restclient
在这里插入图片描述

2.4 总结

  • dynamicClient为各种类型的资源都提供统一的操作API,资源需要包装为Unstructured数据结构

  • 内部使用了Restclient与kubernetes交互;

3. 示例

使用dynamicClient实现查询指定namespace下的所有pod;

package main

import (
 	"context"
	"flag"
	"fmt"
 	apiv1 "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"
	"k8s.io/client-go/util/homedir"
	"log"
	"path/filepath"
 )

func main() {
	var kubeconfig *string
	if home := homedir.HomeDir(); home != "" {
		kubeconfig = flag.String("kubeconfig", filepath.Join(home,".kube", "config_local"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeconfig = flag.String("kubeconfig","","absolute path to the kubeconfig file")
	}
	flag.Parse()

	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		log.Fatal(err)
	}

	dynamicClient, err := dynamic.NewForConfig(config)
	if err != nil {
		log.Fatal(err)
 	}

	// dynamicClient的唯一关联方法所需的入参
	gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}

	// 使用dynamicClient的查询列表方法,查询指定namespace下的所有pod,
	// 注意此方法返回的数据结构类型是UnstructuredList
	unstructObj, err := dynamicClient.Resource(gvr).Namespace("kube-system").List(context.TODO(),metav1.ListOptions{Limit: 100})
	if err != nil {
		log.Fatal(err)
	}

	podList := &apiv1.PodList{}
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("namespace\t Status\t\t name")
	for _, item := range podList.Items {
		fmt.Printf("%v\t %v\t %v\n",
			item.Namespace,
			item.Status.Phase,
			item.Name,
		)
	}


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值