sigs.k8s.io controller-runtime系列之四 client分析

简介

之前介绍过sigs.k8s.io controller-runtime系列之三 controller分析sigs.k8s.io controller-runtime-controller
本文主要介绍pkg/client的源码分析。

目录结构

  1. interfaces.go
    • Patch接口 是可以应用于Kubernetes对象的补丁
    type Patch interface {
    	// Patch的类型(字符串形式 "application/json-patch+json"/"application/merge-patch+json"/"application/strategic-merge-patch+json"/"application/apply-patch+yaml").
    	Type() types.PatchType
    	// 补丁的原始数据(Object -> []byte)
    	Data(obj Object) ([]byte, error)
    }
    
    • Reader接口 提供获取单个和list object接口
    type Reader interface {
    	// Get从Kubernetes集群中检索给定object key的obj.
    	// obj必须是一个结构指针,以便可以使用服务器返回的响应来更新obj。
    	Get(ctx context.Context, key ObjectKey, obj Object) error
    
    	// 通过ListOption查找obj,list来接收查询的结果.
    	List(ctx context.Context, list ObjectList, opts ...ListOption) error
    }
    
    • Writer接口 提供增、删、改(补丁)的接口
    type Writer interface {
    	// 保存obj到k8s集群.
    	Create(ctx context.Context, obj Object, opts ...CreateOption) error
    
    	// 从k8s集群删除obj.
    	Delete(ctx context.Context, obj Object, opts ...DeleteOption) error
    
    	// 在k8s集群中更新给定的obj. obj必须是一个指针对象,以便可以使用服务器返回的响应来更新obj。
    	Update(ctx context.Context, obj Object, opts ...UpdateOption) error
    
    	// 在k8s集群中补丁给定的obj. obj必须是一个指针对象,以便可以使用服务器返回的响应来更新obj.
    	Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error
    
    	// 删除与给定选项匹配的给定类型的所有对象
    	DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error
    }
    
    • StatusClient接口 提供如何创建一个可以更新Kubernetes对象状态子资源的客户端
    type StatusClient interface {
    	Status() StatusWriter
    }
    
    type StatusWriter interface {
    	// Update updates the fields corresponding to the status subresource for the
    	// 更新与给定obj相对应的status子资源的fields. obj必须是一个指针对象,以便可以使用服务器返回的响应来更新obj.
    	Update(ctx context.Context, obj Object, opts ...UpdateOption) error
    
    	// 补丁该obj对应的status字段. obj必须是一个指针对象,以便可以使用服务器返回的响应来更新obj.
    	Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error
    }
    
    • Client接口 提供对k8s的CRUD操作
    type Client interface {
        // 提供读取k8s的obj
    	Reader
        // 提供增删改k8s的obj(不包括status子资源)
    	Writer
        // 提供修改k8s的obj的status子资源
    	StatusClient
    
    	// 获取该客户端使用的scheme。
    	Scheme() *runtime.Scheme
    	// 获取该客户端使用的rest mapper
    	RESTMapper() meta.RESTMapper
    }
    
    • WithWatch接口 相当于Client套了一个可以监视even事件的函数,便于实现一个带有watch功能的Client
    type WithWatch interface {
    	Client
    	Watch(ctx context.Context, obj ObjectList, opts ...ListOption) (watch.Interface, error)
    }
    
    • IndexerFunc函数接口 把obj中某些fileds对应的值组成的切片
    type IndexerFunc func(Object) []string  
    
    • FieldIndexer接口 在特定的field上建立索引,以便以后可以由字段选择器使用
    type FieldIndexer interface {
    	// 如果要和Kubernetes API server兼容, IndexerFunc使用给定的函数提取给定field对应obj的值,
        // 否则  你可以使用IndexerFunc返回obj中多个field对应的值。
    	// FieldIndexer 自动处理对namespace的索引,并支持有效的all-namespace查询。
    	IndexField(ctx context.Context, obj Object, field string, extractValue IndexerFunc) error
    }
    
  2. object.go
    • Object接口 Kubernetes对象,由可序列化(runtime.Object)和可识别(metav1.Object)组成
    type Object interface {
        // 元数据信息(可识别)
    	metav1.Object
        // 类型信息 kind和apiversion(可序列化)
    	runtime.Object
    }
    
    • ObjectList接口 Kubernetes对象列表,由可序列化(runtime.Object)和包含了列表相关数据(metav1.ListInterface)组成
    type ObjectList interface {
        // 包含了列表相关数据
    	metav1.ListInterface
        // 类型信息 kind和apiversion(可序列化)
    	runtime.Object
    }
    
  3. client.go
    • Options结构体
    type Options struct {
    	// 用于映射go的struct到GroupVersionKinds
    	Scheme *runtime.Scheme
    
    	// 用于映射GroupVersionKinds到Resources
    	Mapper meta.RESTMapper
    }
    
    • New函数 使用提供的config和Options生成新的Client,返回的客户端直接从服务器读取和写入 (它不使用对象缓存)
      它知道如何普通类型(自定义资源和聚合/内置资源)以及非结构化类型
      对于GVK,普通类型是本身提供,而非机构化类型需要提取相应字段的值
    func New(config *rest.Config, options Options) (Client, error) {
    	return newClient(config, options)
    }
    
    func newClient(config *rest.Config, options Options) (*client, error) {
    	if config == nil {
    		return nil, fmt.Errorf("must provide non-nil rest.Config to client.New")
    	}
    
    	// 如果scheme没有提供,则默认
    	if options.Scheme == nil {
    		options.Scheme = scheme.Scheme
    	}
    
    	// 如果Mapper没有提供,则默认是一个动态的GVK和resource匹配器,既在runtime时,动态匹配
    	if options.Mapper == nil {
    		var err error
    		options.Mapper, err = apiutil.NewDynamicRESTMapper(config)
    		if err != nil {
    			return nil, err
    		}
    	}
        // 生成缓存client的一些基本信息
    	clientcache := &clientCache{
    		config: config,
    		scheme: options.Scheme,
    		mapper: options.Mapper,
            // 编码和解码 
    		codecs: serializer.NewCodecFactory(options.Scheme),
    
    		structuredResourceByType:   make(map[schema.GroupVersionKind]*resourceMeta),
    		unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
    	}
        // 生成可以获取任何obj的metadata的client
    	rawMetaClient, err := metadata.NewForConfig(config)
    	if err != nil {
    		return nil, fmt.Errorf("unable to construct metadata-only client for use as part of client: %w", err)
    	}
        // 生成client  包括typedClient:它在使用新客户端时懒惰地初始化(既在使用时),并缓存该客户端。直接与api server交互。
        // unstructuredClient:它在使用新客户端时懒惰地初始化(既在使用时),并缓存该客户端。直接与api server交互。
        // metadataClient:直接与api server交互,仅仅交互关于metadata的信息。
    	c := &client{
    		typedClient: typedClient{
    			cache:      clientcache,
    			paramCodec: runtime.NewParameterCodec(options.Scheme),
    		},
    		unstructuredClient: unstructuredClient{
    			cache:      clientcache,
    			paramCodec: noConversionParamCodec{},
    		},
    		metadataClient: metadataClient{
    			client:     rawMetaClient,
    			restMapper: options.Mapper,
    		},
    		scheme: options.Scheme,
    		mapper: options.Mapper,
    	}
    
    	return c, nil
    }
    
    • client结构体 它在使用新客户端时懒惰地初始化(既在使用typedClient/unstructuredClient的cache中获取metadata时,如果没有,则会在对应的resourceByType中加入对应的key是gvk,value是对应的resource),
      并缓存该客户端。直接与api server交互。
    type client struct {
    	typedClient        typedClient
    	unstructuredClient unstructuredClient
    	metadataClient     metadataClient
    	scheme             *runtime.Scheme
    	mapper             meta.RESTMapper
    }
    
    • Create方法 在k8s集群中创建一个obj
    func (c *client) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
    	switch obj.(type) {
        // Unstructured类型
    	case *unstructured.Unstructured:
            // 1.转化为Unstructured类型的obj 2.获取gvk和metaobj 3. 调用ApplyOptions 4. 调用client-go中的rest/Client的Post方法 5.设置gvk
    		return c.unstructuredClient.Create(ctx, obj, opts...)
        // PartialObjectMetadata类型  只包含structured的TypeMeta和ObjectMeta信息
    	case *metav1.PartialObjectMetadata:
    		return fmt.Errorf("cannot create using only metadata")
    	default:
            // 1.获取metaobj 2. 调用ApplyOptions 3. 调用client-go中的rest/Client的Post方法
    		return c.typedClient.Create(ctx, obj, opts...)
    	}
    }
    
    • Update方法 在k8s集群中更新一个obj
    func (c *client) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
        // 如果obj对应的GVK 不为空,则更新obj对应的GVK
    	defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
    	switch obj.(type) {
    	case *unstructured.Unstructured:
    	    // 1.转化为Unstructured类型的obj 2.获取gvk和metaobj 3. 调用ApplyOptions 4. 调用client-go中的rest/Client的Put方法 5.设置gvk
    		return c.unstructuredClient.Update(ctx, obj, opts...)
    	// PartialObjectMetadata类型  只包含structured的TypeMeta和ObjectMeta信息	
    	case *metav1.PartialObjectMetadata:
    		return fmt.Errorf("cannot update using only metadata -- did you mean to patch?")
    	default:
    	    // 1.获取metaobj 2. 调用ApplyOptions 3. 调用client-go中的rest/Client的Put方法
    		return c.typedClient.Update(ctx, obj, opts...)
    	}
    }
    
    • Delete方法 在k8s集群中删除一个obj
    func (c *client) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
    	switch obj.(type) {
    	case *unstructured.Unstructured:
    	    // 1.转化为Unstructured类型的obj 2.获取gvk和metaobj 3. 调用ApplyOptions 4. 调用client-go中的rest/Client的Delete方法
    		return c.unstructuredClient.Delete(ctx, obj, opts...)
    	case *metav1.PartialObjectMetadata:
    		return c.metadataClient.Delete(ctx, obj, opts...)
    	default:
    	     // 1.获取metaobj 2. 调用ApplyOptions 3. 调用client-go中的rest/Client的Delete方法
    		return c.typedClient.Delete(ctx, obj, opts...)
    	}
    }   
    
    • Patch方法 在k8s集群中补丁一个obj
    func (c *client) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
        // 如果obj对应的GVK 不为空,则更新obj对应的GVK
    	defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
    	switch obj.(type) {
    	case *unstructured.Unstructured:
    	    // 1.转化为Unstructured类型的obj 2.获取gvk和metaobj 3. 获取需要patch的data(四种patch的方式是不一样的) 4. 调用ApplyOptions 5. 调用client-go中的rest/Client的Patch方法
    		return c.unstructuredClient.Patch(ctx, obj, patch, opts...)
    	case *metav1.PartialObjectMetadata:
    	    // 1.转化为PartialObjectMetadata类型的metaobj 2.获取gvk和resInt(用于处理metadata的接口) 3. 获取需要patch的data(四种patch的方式是不一样的) 4. 调用ApplyOptions 5. 调用步骤3中的resInt.Patch 6.设置metadata的GVK
    		return c.metadataClient.Patch(ctx, obj, patch, opts...)
    	default:
    	    // 1.转化为structured类型的obj 2. 获取需要patch的data(四种patch的方式是不一样的) 3. 调用ApplyOptions 4. 调用client-go中的rest/Client的Patch方法
    		return c.typedClient.Patch(ctx, obj, patch, opts...)
    	}
    }   
    
    • Get方法 在k8s集群中获取一个obj
    func (c *client) Get(ctx context.Context, key ObjectKey, obj Object) error {
    	switch obj.(type) {
    	case *unstructured.Unstructured:
    	    // 1.转化为Unstructured类型的obj 2.获取gvk和resource 3. resource的Get方法
    		return c.unstructuredClient.Get(ctx, key, obj)
    	case *metav1.PartialObjectMetadata:
    	    // 1.转化为PartialObjectMetadata类型的metadata 2.获取gvk和resInt 3. resInt的Get方法 5.设置GVK
    		return c.metadataClient.Get(ctx, key, obj)
    	default:
    	    // 1.转化structured类型的resource 2. 调用resource的Get方法
    		return c.typedClient.Get(ctx, key, obj)
    	}
    }
    // 对于typedClient类型的内部逻辑中有一个思考问题
    func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
        // 这里为什么不使用c.cache.getObjMeta(obj)?  
        // 因为最终只需要调用resourceMeta的方法即可,不需要objectMeta,而getObjMeta获取的是ObjMeta,包含了resourceMeta和objectMeta
    	r, err := c.cache.getResource(obj)
    	if err != nil {
    		return err
    	}
    	return r.Get().
    		NamespaceIfScoped(key.Namespace, r.isNamespaced()).
    		Resource(r.resource()).
    		Name(key.Name).Do(ctx).Into(obj)
    }
    
    • List方法 在k8s集群中获取obj的列表
    func (c *client) List(ctx context.Context, obj ObjectList, opts ...ListOption) error {
    	switch obj.(type) {
    	case *unstructured.UnstructuredList:
    	    // 1.转化为Unstructured类型的obj 2.获取gvk和resource 3. resource的Get方法(这里和Get方法的区别在于,Get方法指定了ObjectKey的name)
    		return c.unstructuredClient.List(ctx, obj, opts...)
    	case *metav1.PartialObjectMetadataList:
    	    // 1.转化为PartialObjectMetadata类型的metadata 2.获取gvk和resInt 3. resInt的Get方法(这里和Get方法的区别在于,Get方法指定了ObjectKey的name) 5.设置GVK
    		return c.metadataClient.List(ctx, obj, opts...)
    	default:
    	    // 1.转化structured类型的resource 2. 调用resource的Get方法(这里和Get方法的区别在于,Get方法指定了ObjectKey的name)
    		return c.typedClient.List(ctx, obj, opts...)
    	}
    }
    
    • Status方法 表示client实现了client.StatusClient接口 写入status子资源
    func (c *client) Status() StatusWriter {
    	return &statusWriter{client: c}
    }
    
    • statusWriter结构体 实现了client.StatusWriter接口
    type statusWriter struct {
        client *client
    }
    
    // 更新
    func (sw *statusWriter) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
        // 如果obj对应的GVK 不为空,则更新obj对应的GVK
    	defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
    	switch obj.(type) {
    	case *unstructured.Unstructured:
    	    // 1.转化为Unstructured类型的obj 2.获取gvk和metaobj 3. 调用client-go中的rest/Client的Put方法(和client的Update方法的区别在于指定了子资源status)
    		return sw.client.unstructuredClient.UpdateStatus(ctx, obj, opts...)
    	case *metav1.PartialObjectMetadata:
    		return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?")
    	default:
    	    // 1.转化为structured类型的obj 2. 调用client-go中的rest/Client的Put方法(和client的Update方法的区别在于指定了子资源status)
    		return sw.client.typedClient.UpdateStatus(ctx, obj, opts...)
    	}
    }
    
    // 对子资源status 补丁
    func (sw *statusWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
        // 如果obj对应的GVK 不为空,则更新obj对应的GVK
    	defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
    	switch obj.(type) {
    	case *unstructured.Unstructured:
    	    // 1.转化为unstructured类型的obj 2. 获取gvk和objmeta 3.获取需要patch的data(四种patch的方式是不一样的) 4. 调用client-go中的rest/Client的Patch方法(和client的Patch方法的区别在于指定了子资源status)
    		return sw.client.unstructuredClient.PatchStatus(ctx, obj, patch, opts...)
    	case *metav1.PartialObjectMetadata:
    	    // 1.转化为PartialObjectMetadata类型的metadata 2. 获取gvk和resInt 3.获取需要patch的data(四种patch的方式是不一样的) 4. 调用resInt的Patch方法(和client的Patch方法的区别在于指定了子资源status)
    		return sw.client.metadataClient.PatchStatus(ctx, obj, patch, opts...)
    	default:
    	    // 1.转化为structured类型的obj 2.获取需要patch的data(四种patch的方式是不一样的) 3. 调用client-go中的rest/Client的Patch方法(和client的Patch方法的区别在于指定了子资源status)
    		return sw.client.typedClient.PatchStatus(ctx, obj, patch, opts...)
    	}
    }
    
  4. client_cache.go
    • clientCache 结构体 缓存client和api server交互的参数
    type clientCache struct {
    	// 和apiserver交互的config
    	config *rest.Config
    
    	// 映射structs 到 GroupVersionKinds
    	scheme *runtime.Scheme
    
    	// 映射 GroupVersionKinds 到 Resources
    	mapper meta.RESTMapper
    
    	// 编解码 gvk
    	codecs serializer.CodecFactory
    
    	// 缓存了struct的map  key: gvk  value:resourceMeta
    	structuredResourceByType map[schema.GroupVersionKind]*resourceMeta
    	// 缓存了unstruct的map  key: gvk  value:resourceMeta
    	unstructuredResourceByType map[schema.GroupVersionKind]*resourceMeta
     // 锁  并发控制
    	mu                         sync.RWMutex
    }
    
    • newResource方法 将obj映射到Kubernetes资源并为该资源构造一个与apiserver交互客户端
    func (c *clientCache) newResource(gvk schema.GroupVersionKind, isList, isUnstructured bool) (*resourceMeta, error) {
    // 判断后缀是否是"List",如果是就截取	
    if strings.HasSuffix(gvk.Kind, "List") && isList {
    		// if this was a list, treat it as a request for the item's resource
    		gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
    	}
     // 构造出一个与apiserver交互的client
    	client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, c.codecs)
    	if err != nil {
    		return nil, err
    	}
     // mapper产生一个gvk与k8s资源的映射关系
    	mapping, err := c.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
    	if err != nil {
    		return nil, err
    	}
    	return &resourceMeta{Interface: client, mapping: mapping, gvk: gvk}, nil
    }
    
    • getResource方法 获取obj对应的resourceMeta信息
    func (c *clientCache) getResource(obj runtime.Object) (*resourceMeta, error) {
     // 获取obj对应的gvk
    	gvk, err := apiutil.GVKForObject(obj, c.scheme)
    	if err != nil {
    		return nil, err
    	}
     // 判断是否是Unstructured类型
    	_, isUnstructured := obj.(*unstructured.Unstructured)
    	_, isUnstructuredList := obj.(*unstructured.UnstructuredList)
    	isUnstructured = isUnstructured || isUnstructuredList
    
    	// 加锁  并发安全获取gvk对应的resourceMeta
    	c.mu.RLock()
    	resourceByType := c.structuredResourceByType
    	if isUnstructured {
    		resourceByType = c.unstructuredResourceByType
    	}
    	r, known := resourceByType[gvk]
    	c.mu.RUnlock()
     // 如果存在就返回
    	if known {
    		return r, nil
    	}
    
    	// newResource创建一个
    	c.mu.Lock()
    	defer c.mu.Unlock()
    	r, err = c.newResource(gvk, meta.IsListType(obj), isUnstructured)
    	if err != nil {
    		return nil, err
    	}
     // 添加到resourceByType中
    	resourceByType[gvk] = r
    	return r, err
    }
    
    • getObjMeta方法 获取obj对应的object和type的meta
    func (c *clientCache) getObjMeta(obj runtime.Object) (*objMeta, error) {
     // 获取resourceMeta
    	r, err := c.getResource(obj)
    	if err != nil {
    		return nil, err
    	}
     // 获取object meta
    	m, err := meta.Accessor(obj)
    	if err != nil {
    		return nil, err
    	}
    	return &objMeta{resourceMeta: r, Object: m}, err
    }
    
    • resourceMeta结构体 type对应meta
    type resourceMeta struct {
    	// 与apiserver交互的client
    	rest.Interface
    	// GVK
    	gvk schema.GroupVersionKind
    	// GVK和resource的映射
    	mapping *meta.RESTMapping
    }
    
  5. typed_client.go struct 对应的client 实现了client接口
  6. unstructured_client.go unstructured对应的client 实现了client接口
  7. metadata_client.go 仅仅操作metadata对应的client
  8. namespaced_client.go 包装了client和namespace(str类型)的client,强制改变了要操作的namespace
  9. codec.go 不进行version转换的参数编解码器,并且不能解码(decode),只能编码(encode)
    type noConversionParamCodec struct{}
    
    // 这里的encode和
    func (noConversionParamCodec) EncodeParameters(obj runtime.Object, to schema.GroupVersion) (url.Values, error) {
    	return queryparams.Convert(obj)
    }
    
    // 没有实现decode(报错)
    func (noConversionParamCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into runtime.Object) error {
    	return errors.New("DecodeParameters not implemented on noConversionParamCodec")
    }
    
    //k8s.io/apimachinery的runtime/codec.go的parameterCodec的encode和上面对比
    func (c *parameterCodec) EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) {
     // ********多了version的转化  start********
    	gvks, _, err := c.typer.ObjectKinds(obj)
    	if err != nil {
    		return nil, err
    	}
    	gvk := gvks[0]
    	if to != gvk.GroupVersion() {
    		out, err := c.convertor.ConvertToVersion(obj, to)
    		if err != nil {
    			return nil, err
    		}
    		obj = out
    	}
     // ********多了version的转化  end********
    	return queryparams.Convert(obj)
    }
    
  10. dryrun.go 空运行client,添加了一个DryRunOption,client在增删改查时可以通过option.DryRun的值来判断,如果是Get/List/Delete操作,可以执行,但是涉及obj的Update/Patch等操作时,直接忽略
    由此我们可以根据option中特定的值来实现不同种类的client。
    • 可以参考fake/client.go
    func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
    	createOptions := &client.CreateOptions{}
    	createOptions.ApplyOptions(opts)
     //判断options中是否存在DryRunAll,如果存在return
    	for _, dryRunOpt := range createOptions.DryRun {
    		if dryRunOpt == metav1.DryRunAll {
    			return nil
    		}
    	}
    
    	gvr, err := getGVRFromObject(obj, c.scheme)
    	if err != nil {
    		return err
    	}
    	accessor, err := meta.Accessor(obj)
    	if err != nil {
    		return err
    	}
    
    	if accessor.GetName() == "" && accessor.GetGenerateName() != "" {
    		base := accessor.GetGenerateName()
    		if len(base) > maxGeneratedNameLength {
    			base = base[:maxGeneratedNameLength]
    		}
    		accessor.SetName(fmt.Sprintf("%s%s", base, utilrand.String(randomLength)))
    	}
    
    	return c.tracker.Create(gvr, obj, accessor.GetNamespace())
    }
    
    func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
    	gvr, err := getGVRFromObject(obj, c.scheme)
    	if err != nil {
    		return err
    	}
    	accessor, err := meta.Accessor(obj)
    	if err != nil {
    		return err
    	}
    	delOptions := client.DeleteOptions{}
    	delOptions.ApplyOptions(opts)
    
    	return c.deleteObject(gvr, accessor)
    }
    
  11. options.go 包含用于执行CRUD时的选项(查询时的根据label或者field过滤等)
  12. split.go 用来构建代理client
  • NewDelegatingClientInput结构体 封装创建代理client的参数
type NewDelegatingClientInput struct {
 // 从缓存中读取数据的client
	CacheReader       Reader
 // 和apiserver交互的client
	Client            Client
 // 表示不经过cache client,而是通过client获取obj
	UncachedObjects   []Object
 // 是否缓存unstructured的obj
	CacheUnstructured bool
}
  • NewDelegatingClient函数 通过单独聚合reader(cache)、writer、statusclient创建一个代理client
func NewDelegatingClient(in NewDelegatingClientInput) (Client, error) {
 // 新建一个map  表示还未被cache的gvk对应的obj
	uncachedGVKs := map[schema.GroupVersionKind]struct{}{}
 // 初始化一些不经过cache client,而是通过client获取obj的Object
	for _, obj := range in.UncachedObjects {
		gvk, err := apiutil.GVKForObject(obj, in.Client.Scheme())
		if err != nil {
			return nil, err
		}
		uncachedGVKs[gvk] = struct{}{}
	}

	return &delegatingClient{
		scheme: in.Client.Scheme(),
		mapper: in.Client.RESTMapper(),
		Reader: &delegatingReader{
			CacheReader:       in.CacheReader,
			ClientReader:      in.Client,
			scheme:            in.Client.Scheme(),
			uncachedGVKs:      uncachedGVKs,
			cacheUnstructured: in.CacheUnstructured,
		},
		Writer:       in.Client,
		StatusClient: in.Client,
	}, nil
}

type delegatingClient struct {
	Reader//读客户端
	Writer//写客户端
	StatusClient//状态子资源客户端

	scheme *runtime.Scheme
	mapper meta.RESTMapper
}

// 对于读操作,unstructured类型的obj,使用ClientReader。(因为cacheUnstructured默认为false)
// 对于读操作,其他类型的obj,使用CacheReader。(因为cacheUnstructured默认为false)
type delegatingReader struct {
	CacheReader  Reader
	ClientReader Reader

	uncachedGVKs      map[schema.GroupVersionKind]struct{}
	scheme            *runtime.Scheme
	cacheUnstructured bool
}
// 根据obj选择哪个reader的关键方法
func (d *delegatingReader) shouldBypassCache(obj runtime.Object) (bool, error) {
	gvk, err := apiutil.GVKForObject(obj, d.scheme)
	if err != nil {
		return false, err
	}
	// TODO: this is producing unsafe guesses that don't actually work,
	// but it matches ~99% of the cases out there.
	if meta.IsListType(obj) {
		gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
	}
	if _, isUncached := d.uncachedGVKs[gvk]; isUncached {
		return true, nil
	}
	if !d.cacheUnstructured {
		_, isUnstructured := obj.(*unstructured.Unstructured)
		_, isUnstructuredList := obj.(*unstructured.UnstructuredList)
		return isUnstructured || isUnstructuredList, nil
	}
	return false, nil
}
 
func (d *delegatingReader) Get(ctx context.Context, key ObjectKey, obj Object) error {
	if isUncached, err := d.shouldBypassCache(obj); err != nil {
		return err
	} else if isUncached {
		return d.ClientReader.Get(ctx, key, obj)
	}
	return d.CacheReader.Get(ctx, key, obj)
}

func (d *delegatingReader) List(ctx context.Context, list ObjectList, opts ...ListOption) error {
	if isUncached, err := d.shouldBypassCache(list); err != nil {
		return err
	} else if isUncached {
		return d.ClientReader.List(ctx, list, opts...)
	}
	return d.CacheReader.List(ctx, list, opts...)
}
  1. watch.go
  • NewWithWatch函数 创建一个带有witch功能的client,实现了WithWitch接口
type watchingClient struct {
  *client
  dynamic dynamic.Interface
}

func NewWithWatch(config *rest.Config, options Options) (WithWatch, error) {
 // client的创建(和apiserver交互)
	client, err := newClient(config, options)
	if err != nil {
		return nil, err
	}
 // 动态的client创建,为什么叫dynamic,这个dynamic和正常的client有什么区别?带着问题看源码。
	dynamicClient, err := dynamic.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	return &watchingClient{client: client, dynamic: dynamicClient}, nil
}

//关键分析 dynamicClient和client的关键区别
// 这是client在使用client_cache.go的newResource中调用newResource中调用apiutil.RESTClientForGVK
func RESTClientForGVK(gvk schema.GroupVersionKind, isUnstructured bool, baseConfig *rest.Config, codecs serializer.CodecFactory) (rest.Interface, error) {
 // 在baseConfig的基础上包装了GVR信息,只能操作关于该GVR的信息
	cfg := createRestConfig(gvk, isUnstructured, baseConfig)
	if cfg.NegotiatedSerializer == nil {
		cfg.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: codecs}
	}
	return rest.RESTClientFor(cfg)
}
// 而dynamicClient可以使用Resource动态指定GVK信息,参考client-go中dynamic/simple
type dynamicResourceClient struct {
	client    *dynamicClient
	namespace string
	resource  schema.GroupVersionResource
}

func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
	return &dynamicResourceClient{client: c, resource: resource}
}
  • Watch方法 根据不同类型的obj list,选择不同的watch方式,具体有三种
  1. 非结构型
  2. obj元数据型
  3. 结构型
func (w *watchingClient) Watch(ctx context.Context, list ObjectList, opts ...ListOption) (watch.Interface, error) {
	switch l := list.(type) {
	case *unstructured.UnstructuredList:
		return w.unstructuredWatch(ctx, l, opts...)
	case *metav1.PartialObjectMetadataList:
		return w.metadataWatch(ctx, l, opts...)
	default:
		return w.typedWatch(ctx, l, opts...)
	}
}
  1. apiutil
  • apimachinery.go 主要封装了一些k8s.io/apimachinery依赖
func init() {
	// 仅仅针对 built-in resources 并且实现了 Protocol Buffers.
 // 对于crd资源不支持Protocol Buffers,但是通过Aggregated API 可以实现
 // clientgoscheme这个是只包含了built-in resources的scheme
	if err := clientgoscheme.AddToScheme(protobufScheme); err != nil {
		panic(err)
	}
}

// 该方法可以添加到protobufScheme中
func AddToProtobufScheme(addToScheme func(*runtime.Scheme) error) error {
	protobufSchemeLock.Lock()
	defer protobufSchemeLock.Unlock()
	return addToScheme(protobufScheme)
}

// 获取RESTMapper,用来discovery GVR
func NewDiscoveryRESTMapper(c *rest.Config) (meta.RESTMapper, error) {
	// 获取DiscoveryClient 获取一个RESTClient和一个LegacyPrefix = "/api"(核心群组)
	dc, err := discovery.NewDiscoveryClientForConfig(c)
	if err != nil {
		return nil, err
	}
 // 获取一个slice,数据是APIGroup和map组成,其中map的key是version,value是一个APIResource的slice
	gr, err := restmapper.GetAPIGroupResources(dc)
	if err != nil {
		return nil, err
	}
 // 为GroupResource slice生成对应得DefaultRESTMapper,并最终包装成一个PriorityRESTMapper返回
	return restmapper.NewDiscoveryRESTMapper(gr), nil
}
  • dynamicrestmapper.go 可在运行时动态发现GVR得RestMapper
type dynamicRESTMapper struct {
	mu           sync.RWMutex //保证并发安装
	staticMapper meta.RESTMapper// 静态得RESTMapper,一般调用先调用
	limiter      *rate.Limiter// checkAndReload时得速率限制器
	newMapper    func() (meta.RESTMapper, error)// 如果调用staticMapper没有,则调用,返回RESTMapper再次查询,并赋值staticMapper

	lazy bool // 如果true,初始化时就生成staticMapper
	// 在初始化staticMapper时使用
	initOnce sync.Once
}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值