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

281 阅读5分钟

@TOC

discovery包

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

cached

  • legacy.go 主要用途是兼容老版本
    // NewMemCacheClient 已弃用。直接使用 memory.NewMemCacheClient 。
    func NewMemCacheClient(delegate discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface {
    	return memory.NewMemCacheClient(delegate)
    }
    
    // ErrCacheNotFound 已弃用。直接使用 memory.ErrCacheNotFound 。
    var ErrCacheNotFound = memory.ErrCacheNotFound
    
  • memory
    • memcache.go
      • 结构体
        // 缓存项  内部使用  不暴露
        type cacheEntry struct {
          // 资源列表
        	resourceList *metav1.APIResourceList
        	err          error
        }
        
        // memCacheClient 可以 调用Invalidate() 保持最新的发现信息。
        type memCacheClient struct {
          // DiscoveryClient 用来动态发现group version resource
        	delegate discovery.DiscoveryInterface
        
        	lock                   sync.RWMutex
        
          // 缓存的对象  key:group value:cacheEntry
        	groupToServerResources map[string]*cacheEntry
        
          // 缓存的group列表
        	groupList              *metav1.APIGroupList
        
          // 缓存是否有效
        	cacheValid             bool
        }
        
        // refreshLocked 刷新缓存状态。调用者写入时必须持有 d.lock。
        func (d *memCacheClient) refreshLocked() error {
          // 获取全都group列表
        	gl, err := d.delegate.ServerGroups()
        	if err != nil || len(gl.Groups) == 0 {
        		utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list: %v", err))
        		return err
        	}
        
        	wg := &sync.WaitGroup{}
        	resultLock := &sync.Mutex{}
        	rl := map[string]*cacheEntry{}
          // 遍历groups
        	for _, g := range gl.Groups {
              // 遍历group中的version列表
        		for _, v := range g.Versions {
        			gv := v.GroupVersion
                  // waitgroup中add 1,等待goruntime执行数加一
        			wg.Add(1)
                  // 开启goruntime 
        			go func() {
        				defer wg.Done()
        				defer utilruntime.HandleCrash()
                      // 执行根据gv获取resources
        				r, err := d.serverResourcesForGroupVersion(gv)
        				if err != nil {
        					utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", gv, err))
        				}
        
        				resultLock.Lock()
        				defer resultLock.Unlock()
                      // 缓存map添加元素
        				rl[gv] = &cacheEntry{r, err}
        			}()
        		}
        	}
        	wg.Wait()
          
          // 重新赋值cacheclient中的缓存数据
        	d.groupToServerResources, d.groupList = rl, gl
          // 设置缓存可用
        	d.cacheValid = true
        	return nil
        }
        
        // 对于memCacheClient实现的DiscoveryInterface的方法,不做分析,和上节分析的DiscoveryClient类似,
        // 唯一区别是,再获取resource时候,会先判断cacheValid是否可用,
        // 不可用就执行refreshLocked,重新获取缓存数据并且设置每项的err(用来判断是否是短暂的err,来决定是否重新获取resource)
        // 例如ServerResourcesForGroupVersion方法
        func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
        	d.lock.Lock()
        	defer d.lock.Unlock()
          // 先判断cacheValid是否可用 
        	if !d.cacheValid {
              // 执行refreshLocked 重新获取缓存数据并且设置每项的err
        		if err := d.refreshLocked(); err != nil {
        			return nil, err
        		}
        	}
        	cachedVal, ok := d.groupToServerResources[groupVersion]
        	if !ok {
        		return nil, ErrCacheNotFound
        	}
          // 判断是否是短暂的err,来决定是否重新获取resource
        	if cachedVal.err != nil && isTransientError(cachedVal.err) {
        		r, err := d.serverResourcesForGroupVersion(groupVersion)
        		if err != nil {
        			utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", groupVersion, err))
        		}
        		cachedVal = &cacheEntry{r, err}
        		d.groupToServerResources[groupVersion] = cachedVal
        	}
        
        	return cachedVal.resourceList, cachedVal.err
        }
        
        // 实现了CachedDiscoveryInterface的Fresh
        func (d *memCacheClient) Fresh() bool {
        	d.lock.RLock()
        	defer d.lock.RUnlock()
        	// 返回是否完全填充缓存。由于暂时性错误而丢失单个条目并且尝试读取该条目将触发重试,这中情况再重试是可能成功的。
        	return d.cacheValid
        }
        
        // Invalidate 强制不使用早于当前时间的缓存数据(重置为原始数据).
        func (d *memCacheClient) Invalidate() {
        	d.lock.Lock()
        	defer d.lock.Unlock()
        	d.cacheValid = false
        	d.groupToServerResources = nil
        	d.groupList = nil
        }
        
      • 函数
        // isTransientConnectionError 检查给定的错误是 连接被拒绝 还是 连接重置 错误,这通常意味着 apiserver 暂时不可用。
        func isTransientConnectionError(err error) bool {
        	var errno syscall.Errno
        	if errors.As(err, &errno) {
        		return errno == syscall.ECONNREFUSED || errno == syscall.ECONNRESET
        	}
        	return false
        }
        
        // 是否是短暂的错误
        func isTransientError(err error) bool {
          // 如果是链接错误,则是短暂的
        	if isTransientConnectionError(err) {
        		return true
        	}
           
          // 如果是APIStatus接口类型,并且状态码大于等于500,怎也是短暂的err
        	if t, ok := err.(errorsutil.APIStatus); ok && t.Status().Code >= 500 {
        		return true
        	}
        
        	return errorsutil.IsTooManyRequests(err)
        }
        

disk

  • cached_discovery.go
    • 接口及结构体
      // CachedDiscoveryClient 实现了发现服务器支持的 API 组、版本和资源的功能,并缓存group列表和资源列表。
      type CachedDiscoveryClient struct {
      	delegate discovery.DiscoveryInterface
      
      	// cacheDirectory 是保存缓存的目录。每个hosts:port组合必须是唯一的才能正常工作。
      	cacheDirectory string
      
      	// ttl 缓存被视为有效的时间间隔
      	ttl time.Duration
      
      	mutex sync.Mutex
      
      	// ourFiles key:进程创建的缓存文件的文件名,value:如果key包含groupversion则是缓存的APIResourceList的byte数组,否则是APIGroupList的字节数组
      	ourFiles map[string]struct{}
      
      	invalidated bool
      
      	// 如果所有使用的缓存文件都是我们的,那么 fresh 为真
      	fresh bool
      }
      
      // 对于CachedDiscoveryClient实现的DiscoveryInterface的方法,
      // 只对ServerResourcesForGroupVersion和ServerGroups做分析,其他的不做分析,和上节分析的DiscoveryClient类似
      func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
          // 构建gv对应的filename,类似于/data/apps/v1/serverresources.json
      	filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
          // 获取缓存文件对应的字节数组,判断对应文件是否在缓存map中,且到目前为止是否缓存过期
      	cachedBytes, err := d.getCachedFile(filename)
          // 读取无错误
      	if err == nil {
              // 创建resource列表
      		cachedResources := &metav1.APIResourceList{}
              // 解码字节数组为上面list
      		if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
      			klog.V(10).Infof("returning cached discovery info from %v", filename)
      			return cachedResources, nil
      		}
      	}
          // 到这里说明解析异常,使用DiscoveryClient读取apiserver获取
      	liveResources, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
      	if err != nil {
      		klog.V(3).Infof("skipped caching discovery info due to %v", err)
      		return liveResources, err
      	}
      	if liveResources == nil || len(liveResources.APIResources) == 0 {
      		klog.V(3).Infof("skipped caching discovery info, no resources found")
      		return liveResources, err
      	}
          // 写入新的resource列表到filename对应文件中
      	if err := d.writeCachedFile(filename, liveResources); err != nil {
      		klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
      	}
      
      	return liveResources, nil
      }
      
      func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
          // 构建gv对应的filename,类似于/data/servergroups.json
      	filename := filepath.Join(d.cacheDirectory, "servergroups.json")
          // 获取缓存文件对应的字节数组,判断对应文件是否在缓存map中,且到目前为止是否缓存过期
      	cachedBytes, err := d.getCachedFile(filename)
      	if err == nil {
              // 创建group列表
      		cachedGroups := &metav1.APIGroupList{}
              // 解码字节数组为上面list
      		if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
      			klog.V(10).Infof("returning cached discovery info from %v", filename)
      			return cachedGroups, nil
      		}
      	}
      
          // 到这里说明解析异常,使用DiscoveryClient读取apiserver获取
      	liveGroups, err := d.delegate.ServerGroups()
      	if err != nil {
      		klog.V(3).Infof("skipped caching discovery info due to %v", err)
      		return liveGroups, err
      	}
      	if liveGroups == nil || len(liveGroups.Groups) == 0 {
      		klog.V(3).Infof("skipped caching discovery info, no groups found")
      		return liveGroups, err
      	}
          // 写入新的group列表到filename对应文件中
      	if err := d.writeCachedFile(filename, liveGroups); err != nil {
      		klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
      	}
      
      	return liveGroups, nil
      }
      
  • round_tripper.go
    • 结构体
      // httpcache.Transport的包装结构体
      type cacheRoundTripper struct {
      	rt *httpcache.Transport
      }
      
      // 执行内部http.Transport,响应request
      func (rt *cacheRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
      	return rt.rt.RoundTrip(req)
      }
      
      // 实现http.cancel接口,取消request
      func (rt *cacheRoundTripper) CancelRequest(req *http.Request) {
      	type canceler interface {
      		CancelRequest(*http.Request)
      	}
      	if cr, ok := rt.rt.Transport.(canceler); ok {
      		cr.CancelRequest(req)
      	} else {
      		klog.Errorf("CancelRequest not implemented by %T", rt.rt.Transport)
      	}
      }
      // 实现kubernetes/apimachinery中的RoundTripperWrapper接口
      func (rt *cacheRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt.Transport }
      
    • 函数
      // 新建一个带有dist缓存的cacheRoundTripper(Transport 是 http.RoundTripper 的一个实现,它会在可能的情况下从缓存返回值(避免网络请求),
      // 并将额外添加验证器(etag/if-modified-since)到重复的请求,允许服务器返回 304 /未修改)
      func newCacheRoundTripper(cacheDir string, rt http.RoundTripper) http.RoundTripper {
      	d := diskv.New(diskv.Options{
      		PathPerm: os.FileMode(0750),
      		FilePerm: os.FileMode(0660),
      		BasePath: cacheDir,
      		TempDir:  filepath.Join(cacheDir, ".diskv-temp"),
      	})
      	t := httpcache.NewTransport(diskcache.NewWithDiskv(d))
      	t.Transport = rt
      
      	return &cacheRoundTripper{rt: t}
      }