简介
之前介绍过sigs.k8s.io controller-runtime系列之三 controller分析sigs.k8s.io controller-runtime-controller 。
本文主要介绍pkg/client的源码分析。
目录结构
- 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 }
- 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 }
- 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...) } }
- 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 }
- typed_client.go struct 对应的client 实现了client接口
- unstructured_client.go unstructured对应的client 实现了client接口
- metadata_client.go 仅仅操作metadata对应的client
- namespaced_client.go 包装了client和namespace(str类型)的client,强制改变了要操作的namespace
- 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) }
- 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) }
- options.go 包含用于执行CRUD时的选项(查询时的根据label或者field过滤等)
- 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...)
}
- 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方式,具体有三种
- 非结构型
- obj元数据型
- 结构型
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...)
}
}
- 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
}