@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) }
- 结构体
- memcache.go
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} }
- 结构体