client-go之discovery包(上篇)源码分析

510 阅读8分钟

discovery包

用于发现服务器支持的API 组、版本和资源的方法及服务端支持的swagger api

discovery_client.go

  • discovery_client.go
    • 定义全局变量
      const (
         // 如果动态获取resource失败,重试的次数
         defaultRetries = 2
         // protobuf mime 类型
         mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf"
         // defaultTimeout 是未在 RESTClient 上设置超时时每个请求的最长时间。默认为 32 秒,以便相对于存在的其他超时具有可区分的时间长度。
         defaultTimeout = 32 * time.Second
      )
      
    • 接口
      // DiscoveryInterface 动态发现服务器支持的API groups,versions and resources.
      type DiscoveryInterface interface {
          RESTClient() restclient.Interface
          ServerGroupsInterface
          ServerResourcesInterface
          ServerVersionInterface
          OpenAPISchemaInterface
      }
      
      // CachedDiscoveryInterface 是一个具有缓存失效和刷新的 DiscoveryInterface。
      // 注意:如果ServerResourcesForGroupVersion方法返回缓存未命中错误,
      // 用户需要显式调用Invalidate清除缓存,否则下次会返回同样的缓存未命中错误。
      type CachedDiscoveryInterface interface {
      	DiscoveryInterface
      	// 如果在缓存未能找到,Fresh 应该告诉调用者是否重试(false = 重试,true = 不需要重试)。
      	Fresh() bool
      	// Invalidate 强制不使用早于当前时间的缓存数据
      	Invalidate()
      }
      
      // ServerGroupsInterface 具有获取 API 服务器上支持的group的方法
      type ServerGroupsInterface interface {
      	// ServerGroups 返回支持的组,包括支持的版本和首选版本等信息。
      	ServerGroups() (*metav1.APIGroupList, error)
      }
      
      // ServerResourcesInterface 具有获取 API 服务器上支持的resource的方法
      type ServerResourcesInterface interface {
      	// ServerResourcesForGroupVersion 返回组和版本支持的资源。
      	ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)
      
      	// ServerResources 返回全部支持的资源。
      	// 注意: 即使在非 nil 错误的情况下,返回的资源列表也可能是非 nil 和部分结果。
      	// 不推荐使用:改用 ServerGroupsAndResources。
      	ServerResources() ([]*metav1.APIResourceList, error)
      
      	// ServerGroupsAndResources 返回所有组和版本支持的组和资源。
      	// 注意:返回的组和资源列表可能是非 nil 的,即使在非 nil 错误的情况下也是如此。
      	ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)
      
      	// ServerPreferredResources 返回具有服务器首选版本的受支持资源。
      	// 注意: 即使在非 nil 错误的情况下,返回的资源列表也可能是非 nil 和部分结果。
      	ServerPreferredResources() ([]*metav1.APIResourceList, error)
      
      	// ServerPreferredNamespacedResources 返回受支持命名空间的服务器首选的版本资源。
      	// 注意: 即使在非 nil 错误的情况下,返回的资源列表也可能是非 nil 和部分结果。
      	ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error)
      }
      
      // ServerVersionInterface 有一个检索服务器版本的方法。
      type ServerVersionInterface interface {
      	// ServerVersion 检索并解析服务器的版本(git 版本)。
      	ServerVersion() (*version.Info, error)
      }
      
      // OpenAPISchemaInterface 有一个方法来检索开放的 API 架构。
      type OpenAPISchemaInterface interface {
      	// OpenAPISchema 检索并解析服务器支持的 swagger API 模式。
      	OpenAPISchema() (*openapi_v2.Document, error)
      }
      
    • 关于DiscoveryClient执行内部方法的函数
      // fetchServerResourcesForGroupVersions 使用DiscoveryClient并行获取指定组列表的资源
      func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroupList) (map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error) {
      	groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList)
      	failedGroups := make(map[schema.GroupVersion]error)
      
      	wg := &sync.WaitGroup{}
      	resultLock := &sync.Mutex{}
          // 遍历
      	for _, apiGroup := range apiGroups.Groups {
              // 遍历每个group中的versions
      		for _, version := range apiGroup.Versions {
      			groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
      			wg.Add(1)
                  // 开启goruntime执行
      			go func() {
      				defer wg.Done()
      				defer utilruntime.HandleCrash()
                      // 执行DiscoveryClient具体实现的ServerResourcesForGroupVersion
      				apiResourceList, err := d.ServerResourcesForGroupVersion(groupVersion.String())
      
      				resultLock.Lock()
      				defer resultLock.Unlock()
      
      				if err != nil {
      					failedGroups[groupVersion] = err
      				}
      				if apiResourceList != nil {
      					// 即使出现错误,也可能会返回一些返回值
      					groupVersionResources[groupVersion] = apiResourceList
      				}
      			}()
      		}
      	}
      	wg.Wait()
      
      	return groupVersionResources, failedGroups
      }
      
      // ServerResources 使用提供的发现接口来查找所有组和版本支持的资源
      // 不推荐使用:改用 ServerGroupsAndResources。
      func ServerResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
      	_, rs, err := ServerGroupsAndResources(d)
      	return rs, err
      }
      
      func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
          // 获取apiGroupList
      	sgs, err := d.ServerGroups()
      	if sgs == nil {
      		return nil, nil, err
      	}
          // 获取sgs中所有apiGroup的地址
       	resultGroups := []*metav1.APIGroup{}
      	for i := range sgs.Groups {
      		resultGroups = append(resultGroups, &sgs.Groups[i])
      	}
          // DiscoveryClient并行获取指定组列表的资源
      	groupVersionResources, failedGroups := fetchGroupVersionResources(d, sgs)
      
      	// 按discoveryclient发现的组/版本顺序排列结果
      	result := []*metav1.APIResourceList{}
      	for _, apiGroup := range sgs.Groups {
      		for _, version := range apiGroup.Versions {
      			gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
      			if resources, ok := groupVersionResources[gv]; ok {
      				result = append(result, resources)
      			}
      		}
      	}
      
      	if len(failedGroups) == 0 {
      		return resultGroups, result, nil
      	}
      
      	return resultGroups, result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
      }
      
      // ServerPreferredResources 使用提供的DiscoveryClient来查找首选资源
      func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
         // 获取apiGroupList
      	serverGroupList, err := d.ServerGroups()
      	if err != nil {
      		return nil, err
      	}
          // DiscoveryClient并行获取指定组列表的资源
      	groupVersionResources, failedGroups := fetchGroupVersionResources(d, serverGroupList)
      
      	result := []*metav1.APIResourceList{}
      	grVersions := map[schema.GroupResource]string{}                         // GroupResource 的选定版本
      	grAPIResources := map[schema.GroupResource]*metav1.APIResource{}        // selected APIResource GroupResource 的选定APIResource
      	gvAPIResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // 用于稍后分组的 APIResourceList 的蓝图
      
      	for _, apiGroup := range serverGroupList.Groups {
      		for _, version := range apiGroup.Versions {
      			groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
                  // 判断groupVersionResources是否存在
      			apiResourceList, ok := groupVersionResources[groupVersion]
      			if !ok {
      				continue
      			}
      
      			// 创建空列表,稍后在另一个循环中填充
      			emptyAPIResourceList := metav1.APIResourceList{
      				GroupVersion: version.GroupVersion,
      			}
      			gvAPIResourceLists[groupVersion] = &emptyAPIResourceList
      			result = append(result, &emptyAPIResourceList)
                  // 遍历上面获取apiResourceList的APIResources
      			for i := range apiResourceList.APIResources {
      				apiResource := &apiResourceList.APIResources[i]
                      // 判断apiResource是否包含/,因为如果包含,则是子资源,所以舍弃
      				if strings.Contains(apiResource.Name, "/") {
      					continue
      				}
                      // 形成gv
      				gv := schema.GroupResource{Group: apiGroup.Name, Resource: apiResource.Name}
      				if _, ok := grAPIResources[gv]; ok && version.Version != apiGroup.PreferredVersion.Version {
      					// 仅覆盖首选版本,否则舍弃
      					continue
      				}
      				grVersions[gv] = version.Version
      				grAPIResources[gv] = apiResource
      			}
      		}
      	}
      
      	// 根据 GroupVersion 将选定的 APIResources 分组到 APIResourceLists(地址变量,改变值也是改变了result的value)
      	for groupResource, apiResource := range grAPIResources {
      		version := grVersions[groupResource]
      		groupVersion := schema.GroupVersion{Group: groupResource.Group, Version: version}
      		apiResourceList := gvAPIResourceLists[groupVersion]
      		apiResourceList.APIResources = append(apiResourceList.APIResources, *apiResource)
      	}
      
      	if len(failedGroups) == 0 {
      		return result, nil
      	}
      
      	return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
      }
      
      // ServerPreferredNamespacedResources 使用提供的DiscoveryClient来查找首选是命名空间资源
      func ServerPreferredNamespacedResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
          // 获取全部的PreferredResources
      	all, err := ServerPreferredResources(d)
          // 调用helper.go的FilteredBy
      	return FilteredBy(ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {
      		return r.Namespaced
      	}), all), err
      }
      
      // withRetries 重试执行次数给定的恢复函数,直到没有err。方法体省略
      func withRetries(maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)) ([]*metav1.APIGroup, []*metav1.APIResourceList, error)
      
    • DiscoveryClient结构体
      // DiscoveryClient 实现了发现服务器支持的 API 组、版本和资源的功能。
      type DiscoveryClient struct {
          // restClient http.client的封装
      	restClient restclient.Interface
          
          // api path的前缀
      	LegacyPrefix string
      }
      
      // 将 metav1.APIVersions 转换为 metav1.APIGroup。其实是把入参APIVersions中每项的GroupVersion替换为version,相当于group是 ""。
      // 比较简单,不做具体代码展示,可以自行下载源码查看
      func apiVersionsToAPIGroup(apiVersions *metav1.APIVersions) (apiGroup metav1.APIGroup) 
      
      // 实现ServerGroupsInterface接口
      func (d *DiscoveryClient) ServerGroups() (apiGroupList *metav1.APIGroupList, err error) {
      	// 获取在 /api (d.LegacyPrefix = /api,k8s下core group)下公开的 groupVersions
      	v := &metav1.APIVersions{}
      	err = d.restClient.Get().AbsPath(d.LegacyPrefix).Do(context.TODO()).Into(v)
      	apiGroup := metav1.APIGroup{}
      	if err == nil && len(v.Versions) != 0 {
              // 从这里可以看出,旧版k8s中gvk中g为""
      		apiGroup = apiVersionsToAPIGroup(v)
      	}
      	if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
      		return nil, err
      	}
      
      	//  获取在 /apis 下公开的 groupVersions,具有分组概念的
      	apiGroupList = &metav1.APIGroupList{}
      	err = d.restClient.Get().AbsPath("/apis").Do(context.TODO()).Into(apiGroupList)
      	if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
      		return nil, err
      	}
      	// 与 v1.0 服务器兼容,如果是 403 或 404,忽略并返回我们从 /api 获得的任何内容
      	if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
      		apiGroupList = &metav1.APIGroupList{}
      	}
      
      	// 如果不为空,则将从 /api 检索到的组添加到列表中
      	if len(v.Versions) != 0 {
      		apiGroupList.Groups = append([]metav1.APIGroup{apiGroup}, apiGroupList.Groups...)
      	}
      	return apiGroupList, nil
      }
      
      // 实现了ServerResourcesInterface的ServerResourcesForGroupVersion方法  注意获取resource在不同版本下的path
      func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (resources *metav1.APIResourceList, err error) {
      	url := url.URL{}
      	if len(groupVersion) == 0 {
      		return nil, fmt.Errorf("groupVersion shouldn't be empty")
      	}
          // 这里体现了获取core group的resource的path为/api/v1
          // 获取非core group的resource的path为/apis/$GROUP_NAME/$VERSION 
      	if len(d.LegacyPrefix) > 0 && groupVersion == "v1" {
      		url.Path = d.LegacyPrefix + "/" + groupVersion
      	} else {
      		url.Path = "/apis/" + groupVersion
      	}
      	resources = &metav1.APIResourceList{
      		GroupVersion: groupVersion,
      	}
      	err = d.restClient.Get().AbsPath(url.String()).Do(context.TODO()).Into(resources)
      	if err != nil {
      		if groupVersion == "v1" && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
      			return resources, nil
      		}
      		return nil, err
      	}
      	return resources, nil
      }
      
      // 实现了ServerResourcesInterface的ServerResources, 其实内部调用的是ServerGroupsAndResources
      func (d *DiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error)
      
      // 实现了ServerResourcesInterface的ServerGroupsAndResources
      func (d *DiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
      	return withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
              // 调用上面函数部分的ServerGroupsAndResources
      		return ServerGroupsAndResources(d)
      	})
      }
      
      // ServerPreferredResources 返回具有服务器首选版本的受支持资源。
      func (d *DiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
      	_, rs, err := withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
              // 调用上面函数部分的ServerPreferredResources
      		rs, err := ServerPreferredResources(d)
      		return nil, rs, err
      	})
      	return rs, err
      }
      
      // ServerPreferredNamespacedResources 返回受支持的命名空间资源的服务器首选版本。
      func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
      	return ServerPreferredNamespacedResources(d)
      }
      
      //ServerVersion 检索并解析服务器的版本(git 版本)。等效于kubectl version
      func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
          // 获取服务端的version  等效于kubectl version
      	body, err := d.restClient.Get().AbsPath("/version").Do(context.TODO()).Raw()
      	if err != nil {
      		return nil, err
      	}
      	var info version.Info
      	err = json.Unmarshal(body, &info)
      	if err != nil {
      		return nil, fmt.Errorf("unable to parse the server version: %v", err)
      	}
      	return &info, nil
      }
      
      // OpenAPISchema 使用 rest 客户端获取开放 api 模式并解析原型。
      func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) 
      
    • 关于获取DiscoveryClient的函数
      // 设置默认属性
      func setDiscoveryDefaults(config *restclient.Config) error {
      	config.APIPath = "" // 清空APIPath
      	config.GroupVersion = nil  // 清空GroupVersion
          // 如果Timeout == 0 设置为defaultTimeout
      	if config.Timeout == 0 {
      		config.Timeout = defaultTimeout
      	}
      	if config.Burst == 0 && config.QPS < 100 {
      		config.Burst = 100
      	}
          // 用来编解码
      	codec := runtime.NoopEncoder{Decoder: scheme.Codecs.UniversalDecoder()}
          // 生成并设置序列化,用来序列化(输入)和反序列化(输出)
      	config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
      	if len(config.UserAgent) == 0 {
      		config.UserAgent = restclient.DefaultKubernetesUserAgent()
      	}
      	return nil
      }
      
      // NewDiscoveryClientForConfig 为给定的配置创建一个新的 DiscoveryClient。此客户端可用于发现 API 服务器中支持的资源。
      func NewDiscoveryClientForConfig(c *restclient.Config) (*DiscoveryClient, error) {
      	config := *c
      	if err := setDiscoveryDefaults(&config); err != nil {
      		return nil, err
      	}
          // 调用rest包下的client.go中的UnversionedRESTClientFor生成restclient
      	client, err := restclient.UnversionedRESTClientFor(&config)
      	return &DiscoveryClient{restClient: client, LegacyPrefix: "/api"}, err
      }
      
      // 类似于NewDiscoveryClientForConfig ,区别是此方法如果出现err,则panic
      func NewDiscoveryClientForConfigOrDie(c *restclient.Config) *DiscoveryClient
      

helper.go

  • helper.go
    • 接口及结构体
      // ResourcePredicate 有一个方法来检查资源是否匹配给定条件
      type ResourcePredicate interface {
        Match(groupVersion string, r *metav1.APIResource) bool
      }
      
      // 函数类型ResourcePredicateFunc 类似于ResourcePredicate
      type ResourcePredicateFunc func(groupVersion string, r *metav1.APIResource) bool
      
      // Match 是 ResourcePredicateFunc 的包装器
      func (fn ResourcePredicateFunc) Match(groupVersion string, r *metav1.APIResource) bool {
      	return fn(groupVersion, r)
      }
      
      // upportsAllVerbs 是一个匹配资源的谓词,如果所有给定的动词都被支持,则匹配
      type SupportsAllVerbs struct {
        Verbs []string
      }
      
      // 匹配检查资源是否包含所有给定的动词。
      func (p SupportsAllVerbs) Match(groupVersion string, r *metav1.APIResource) bool {
        return sets.NewString([]string(r.Verbs)...).HasAll(p.Verbs...)
      }
      
    • 函数
      // MatchesServerVersion 查询服务器以将客户端的构建版本(git hash) 与服务器的构建版本进行比较。如果无法链接服务器或版本不完全匹配,则返回错误。
      func MatchesServerVersion(clientVersion apimachineryversion.Info, client DiscoveryInterface) error {
      	sVer, err := client.ServerVersion()
      	if err != nil {
      		return fmt.Errorf("couldn't read version from server: %v", err)
      	}
      	// GitVersion includes GitCommit and GitTreeState, but best to be safe?
      	if clientVersion.GitVersion != sVer.GitVersion || clientVersion.GitCommit != sVer.GitCommit || clientVersion.GitTreeState != sVer.GitTreeState {
      		return fmt.Errorf("server version (%#v) differs from client version (%#v)", sVer, clientVersion)
      	}
      
      	return nil
      }
      
      // 如果服务器没有所需的版本,ServerSupportsVersion 将返回错误
      func ServerSupportsVersion(client DiscoveryInterface, requiredGV schema.GroupVersion) error {
          // 获取所有的groups
      	groups, err := client.ServerGroups()
      	if err != nil {
      		// 几乎总是一个连接错误
      		return err
      	}
          // 提取groups中每项的groupversion
      	versions := metav1.ExtractGroupVersions(groups)
          // 去重
      	serverVersions := sets.String{}
      	for _, v := range versions {
      		serverVersions.Insert(v)
      	}
          
          // 判断是否包含
      	if serverVersions.Has(requiredGV.String()) {
      		return nil
      	}
      
      	// 如果serverVersions的没有元素,那么可能是403 Forbidden errors
      	if len(serverVersions) == 0 {
      		return nil
      	}
      
      	return fmt.Errorf("server does not support API version %q", requiredGV)
      }
      
      // GroupVersionResources 将 APIResourceLists 转换为 GroupVersionResources,并作为map的key形成map返回。
      func GroupVersionResources(rls []*metav1.APIResourceList) (map[schema.GroupVersionResource]struct{}, error) {
      	gvrs := map[schema.GroupVersionResource]struct{}{}
          // 遍历
      	for _, rl := range rls {
              // str的gv转化为GroupVersion
      		gv, err := schema.ParseGroupVersion(rl.GroupVersion)
      		if err != nil {
      			return nil, err
      		}
              // 遍历APIResources
      		for i := range rl.APIResources {
                  // 形成gvr并插入map中
      			gvrs[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{}
      		}
      	}
      	return gvrs, nil
      }
      
      // FilteredBy 按给定ResourcePredicate过滤
      func FilteredBy(pred ResourcePredicate, rls []*metav1.APIResourceList) []*metav1.APIResourceList {
      	result := []*metav1.APIResourceList{}
      	for _, rl := range rls {
      		filtered := *rl
      		filtered.APIResources = nil
      		for i := range rl.APIResources {
      			if pred.Match(rl.GroupVersion, &rl.APIResources[i]) {
      				filtered.APIResources = append(filtered.APIResources, rl.APIResources[i])
      			}
      		}
      		if filtered.APIResources != nil {
      			result = append(result, &filtered)
      		}
      	}
      	return result
      }