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,
)
}
}