@TOC
restmapper包
获取实现了RESTMapper(对应一个gv下的resources)接口的结构体
discovery.go
- 结构体
// APIGroupResources 是一个 API 组,具有版本到资源的映射。 type APIGroupResources struct { // kubernetes/apiachinery下的APIGroup结构体,包含了group下的所有verisons Group metav1.APIGroup // 一个map,key:gv的字符串形式 value: kubernetes/apiachinery下的APIResource结构体(包含了group verison resource等信息) VersionedResources map[string][]metav1.APIResource } // DeferredDiscoveryRESTMapper 是一个 RESTMapper,它将推迟RESTMapper 的初始化,直到第一次请求映射。 type DeferredDiscoveryRESTMapper struct { initMu sync.Mutex delegate meta.RESTMapper cl discovery.CachedDiscoveryInterface } // 获取代理(RESTMapper接口的实现类) func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) { d.initMu.Lock() defer d.initMu.Unlock() // 如果delegate不为空,则返回 if d.delegate != nil { return d.delegate, nil } // 到这里说明DeferredDiscoveryRESTMapper内部还没有delegate(RESTMapper接口的实现类) // 上面讲到了该方法,使用discovery client来获取GroupResources groupResources, err := GetAPIGroupResources(d.cl) if err != nil { return nil, err } // 上面讲到了该方法,使用GroupResources构建一个PriorityRESTMapper d.delegate = NewDiscoveryRESTMapper(groupResources) return d.delegate, err } // 重置内部缓存的delegate(RESTMapper接口的实现类),将导致下一次映射请求重新发现(重新获取delegate)。 func (d *DeferredDiscoveryRESTMapper) Reset() { klog.V(5).Info("Invalidating discovery information") d.initMu.Lock() defer d.initMu.Unlock() // 强制使discovery client中缓存(缓存的gvr)的失效 d.cl.Invalidate() // 重置RESTMapper中的delegate为空 d.delegate = nil } // KindFor 获取部分资源并返回单个匹配项。 如果有多个匹配项,则返回错误。 func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) { // 获取代理restMapper del, err := d.getDelegate() // 如果err不为空,则表示有异常,返回 if err != nil { return schema.GroupVersionKind{}, err } // 使用代理restMapper(后续系列文章中会在分析apimachinery模块分析),通过gvr获取gvk gvk, err = del.KindFor(resource) // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取KindFor) if err != nil && !d.cl.Fresh() { // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil d.Reset() // 循环执行 gvk, err = d.KindFor(resource) } return } // KindsFor 获取参数资源对应的所有按优先级顺序排列的gvk列表。 func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) { // 获取代理restMapper del, err := d.getDelegate() if err != nil { return nil, err } // 使用代理restMapper(后续系列文章中会在分析apimachinery模块分析),通过gvr获取gvk的slice gvks, err = del.KindsFor(resource) // 如果gvks长度为0且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取KindsFor) if len(gvks) == 0 && !d.cl.Fresh() { // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil d.Reset() // 循环执行 gvks, err = d.KindsFor(resource) } return } // ResourceFor 获取gvr对应匹配的单个gvr。如果有多个匹配项,则返回错误。 func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) { // 获取代理restMapper del, err := d.getDelegate() if err != nil { return schema.GroupVersionResource{}, err } // 使用代理restMapper(后续系列文章中会在分析apimachinery模块分析),通过gvr获取mapper中唯一的一个gvr gvr, err = del.ResourceFor(input) // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取ResourceFor) if err != nil && !d.cl.Fresh() { // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil d.Reset() // 循环执行 gvr, err = d.ResourceFor(input) } return } // 类似于KindsFor,不具体分析 func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) // RESTMapping 为提供的组类型识别出首选资源映射。 func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) { // 获取代理restMapper del, err := d.getDelegate() if err != nil { return nil, err } // 识别出特定的gk,不同版本的唯一一个首选restmapping(包含了gvk gvr scope) m, err = del.RESTMapping(gk, versions...) // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取RESTMapping) if err != nil && !d.cl.Fresh() { // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil d.Reset() // 循环执行 m, err = d.RESTMapping(gk, versions...) } return } // RESTMappings 以粗略的内部首选顺序返回所提供组种类的 RESTMappings。如果没有找到种类,它将返回一个 NoResourceMatchError。 func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) // ResourceSingularizer 将资源名称从复数转换为单数(例如,从 pods 到 pod)。 func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { // 获取代理restMapper del, err := d.getDelegate() if err != nil { return resource, err } // 代理restmapper转化资源名称从复数转换为单数 singular, err = del.ResourceSingularizer(resource) // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取ResourceSingularizer) if err != nil && !d.cl.Fresh() { // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil d.Reset() // 循环调用 singular, err = d.ResourceSingularizer(resource) } return } // 字符串函数 func (d *DeferredDiscoveryRESTMapper) String() string { del, err := d.getDelegate() if err != nil { return fmt.Sprintf("DeferredDiscoveryRESTMapper{%v}", err) } return fmt.Sprintf("DeferredDiscoveryRESTMapper{\n\t%v\n}", del) } - 函数
// NewDiscoveryRESTMapper 根据gvrs返回 PriorityRESTMapper(gvk -- resource)。 func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper { // 其实是一个RESTMapper数组 unionMapper := meta.MultiRESTMapper{} // 保存Priority(优先,注意其和preferred(首选)的区别,前者包含后者,前者表示apiserver可以识别的,后者是唯一的(前者顺序(这里的顺序是程序添加到scheme的versionPriority和observedVersions先后顺序)排列的第一个))的group var groupPriority []string // /v1 是特殊的。它应该永远排在第一位,因为他是core api resourcePriority := []schema.GroupVersionResource{{Group: "", Version: "v1", Resource: meta.AnyResource}} kindPriority := []schema.GroupVersionKind{{Group: "", Version: "v1", Kind: meta.AnyKind}} // 遍历参数groupResources for _, group := range groupResources { // 追加group的name到优先group groupPriority = append(groupPriority, group.Group.Name) // 首先确保Preferred(首选)版本在Priority(优先)的最前面 if len(group.Group.PreferredVersion.Version) != 0 { preferred := group.Group.PreferredVersion.Version if _, ok := group.VersionedResources[preferred]; ok { resourcePriority = append(resourcePriority, schema.GroupVersionResource{ Group: group.Group.Name, Version: group.Group.PreferredVersion.Version, Resource: meta.AnyResource, }) kindPriority = append(kindPriority, schema.GroupVersionKind{ Group: group.Group.Name, Version: group.Group.PreferredVersion.Version, Kind: meta.AnyKind, }) } } // 遍历group下的所有versions for _, discoveryVersion := range group.Group.Versions { // 获取map下key:discoveryVersion.Version的value(resource) resources, ok := group.VersionedResources[discoveryVersion.Version] // 不存在,则下个循环 if !ok { continue } // 过滤掉首选版本(以为上面已经添加过了) if discoveryVersion.Version != group.Group.PreferredVersion.Version { // 到这里,就是费首选版本的资源和kind的添加 resourcePriority = append(resourcePriority, schema.GroupVersionResource{ Group: group.Group.Name, Version: discoveryVersion.Version, Resource: meta.AnyResource, }) kindPriority = append(kindPriority, schema.GroupVersionKind{ Group: group.Group.Name, Version: discoveryVersion.Version, Kind: meta.AnyKind, }) } // 两层for循环获取的gv gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version} // kubernetes/apimachinery的NewDefaultRESTMapper,获取空的restmapper versionMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{gv}) // 遍历上面在gv下获取的resources for _, resource := range resources { // 定义resource对的作用范围(默认是namespace) scope := meta.RESTScopeNamespace // 判断gv下的resource中Namespaced属性值 if !resource.Namespaced { // 如果不是命名空间级别的,则scope是 root级别(也就是集群级别) scope = meta.RESTScopeRoot } // 如果resource的名称中有斜杠,那么这是一个子资源,我们不应该为它们创建restmapper。 if strings.Contains(resource.Name, "/") { continue } //复数和单数名称 plural := gv.WithResource(resource.Name) singular := gv.WithResource(resource.SingularName) // 针对未列出单数形式和服务器遗留资源(core)。对于这些我们就需要猜测其单数的名称。 if len(resource.SingularName) == 0 { // 获取一个猜测(定义好的逻辑)的单数形式 _, singular = meta.UnsafeGuessKindToResource(gv.WithKind(resource.Kind)) } // 通过参数添加对应具体的versionMapper属性 versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope) versionMapper.AddSpecific(gv.WithKind(resource.Kind), plural, singular, scope) // TODO 这会产生实际上不起作用的不安全猜测,但它与以前的行为相匹配,可以看下源码中直接调用了meta.UnsafeGuessKindToResource(gv.WithKind(resource.Kind)) versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope) } // 对于每个gv的mapper都会添加一个Kind为List(做什么使用?) versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot) // 追加到聚合mapper中 unionMapper = append(unionMapper, versionMapper) } } // 遍历全部可用的group slice for _, group := range groupPriority { // 这里是Version和Resource都设置为*,为了match任意resource resourcePriority = append(resourcePriority, schema.GroupVersionResource{ Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource, }) // 这里是Version和Kind都设置为*,为了match任意Kind kindPriority = append(kindPriority, schema.GroupVersionKind{ Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind, }) } return meta.PriorityRESTMapper{ Delegate: unionMapper, // 用来匹配任意的gvr/gvk ResourcePriority: resourcePriority, // 在先使用Delegate match gvr后,如果出现匹配了多个gvr的情况,在使用resourcePriority循环匹配多个gvr的唯一的gvr KindPriority: kindPriority, // 在先使用Delegate match gvk后,如果出现匹配了多个gvk的情况,在使用resourcePriority循环匹配多个gvk的唯一的gvk } } // GetAPIGroupResources 使用提供的discovery client 动态来收集gr信息并返回APIGroupResources slice。 func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources, error) { // 在本系列文章中discovery中已做分析。主要是用于动态发现ApiGroups和ApiGroupResources gs, rs, err := cl.ServerGroupsAndResources() if rs == nil || gs == nil { return nil, err } // 保存key:groupversion value: apiresourceList rsm := map[string]*metav1.APIResourceList{} for _, r := range rs { rsm[r.GroupVersion] = r } var result []*APIGroupResources // 循环遍历groupList, for _, group := range gs { // 每个group初始化一个groupResources groupResources := &APIGroupResources{ Group: *group, VersionedResources: make(map[string][]metav1.APIResource), } // 根据group对应的versions,循环遍历 for _, version := range group.Versions { // 获取上面map(key:groupversion value: apiresourceList),是否存在对应的key resources, ok := rsm[version.GroupVersion] if !ok { continue } // 填充groupResources对应的VersionedResources groupResources.VersionedResources[version.Version] = resources.APIResources } result = append(result, groupResources) } return result, nil } // NewDeferredDiscoveryRESTMapper 返回一个DeferredDiscoveryRESTMapper,它将使用提供的客户端延迟查询来获取发现信息以进行 REST 映射。 func NewDeferredDiscoveryRESTMapper(cl discovery.CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper { return &DeferredDiscoveryRESTMapper{ cl: cl, } }
shortcut.go
- 结构体
// shortcutExpander 是一个可用于 Kubernetes 资源的 RESTMapper. 它首先扩展资源(调用discovery client获取apiresources和shortcutResoure(却别与其他动态restmapper的地方)),然后调用 wrapped(代理restmapper) type shortcutExpander struct { RESTMapper meta.RESTMapper discoveryClient discovery.DiscoveryInterface } // getShortcutMappings 返回一组包含资源短名称的元组. // 首先,将从 API 服务器中获取潜在资源列表。 // 接下来,我们将附加硬编码的资源列表 - 以向后兼容旧服务器。 func (e shortcutExpander) getShortcutMappings() ([]*metav1.APIResourceList, []resourceShortcuts, error) { res := []resourceShortcuts{} // 获取服务器资源 _, apiResList, err := e.discoveryClient.ServerGroupsAndResources() if err != nil { klog.V(1).Infof("Error loading discovery information: %v", err) } // 遍历获取的资源列表 for _, apiResources := range apiResList { // 解析gv gv, err := schema.ParseGroupVersion(apiResources.GroupVersion) if err != nil { klog.V(1).Infof("Unable to parse groupversion = %s due to = %s", apiResources.GroupVersion, err.Error()) continue } // 遍历该gv下的resource列表 for _, apiRes := range apiResources.APIResources { // 遍历该gvr的所有shortnames for _, shortName := range apiRes.ShortNames { // 组建resourceShortcuts,并追加到res中 rs := resourceShortcuts{ ShortForm: schema.GroupResource{Group: gv.Group, Resource: shortName}, LongForm: schema.GroupResource{Group: gv.Group, Resource: apiRes.Name}, } res = append(res, rs) } } } return apiResList, res, nil } // expandResourceShortcut 如果resource确实是一个快捷方式,将返回资源的扩展版本(pkg/api/meta.RESTMapper 可以理解的内容)。 // 如果未找到匹配项,我们将匹配组前缀。 func (e shortcutExpander) expandResourceShortcut(resource schema.GroupVersionResource) schema.GroupVersionResource { // 获取apiserver的资源列表和快捷资源(包含了long和shortcut resource name) if allResources, shortcutResources, err := e.getShortcutMappings(); err == nil { // for循环中如果与完整资源名称完全匹配,则避免扩展(就是尝试匹配shortcut resource name) for _, apiResources := range allResources { gv, err := schema.ParseGroupVersion(apiResources.GroupVersion) if err != nil { continue } // 如果resource参数的group不为nil且其不等于gv的group,表明不匹配 if len(resource.Group) != 0 && resource.Group != gv.Group { continue } // 遍历资源列表中的资源slice for _, apiRes := range apiResources.APIResources { // 如果参数resource的Resource和遍历item的Name相等,表明匹配 if resource.Resource == apiRes.Name { return resource } // 如果参数resource的Resource和遍历item的SingularName(单数形式)相等,表明匹配 if resource.Resource == apiRes.SingularName { return resource } } } // for循环中如果与short资源名称完全匹配,则返回 for _, item := range shortcutResources { // 如果resource参数的group不为nil且其不等于shortcutResources的ShortForm的group,表明不匹配 if len(resource.Group) != 0 && resource.Group != item.ShortForm.Group { continue } // 如果参数resource的Resource和遍历item的ShortForm.Resource相等,表明匹配 if resource.Resource == item.ShortForm.Resource { resource.Resource = item.LongForm.Resource resource.Group = item.LongForm.Group return resource } } // 我们没有找到完全匹配的匹配项。并且resource的Group为空(表明是core api),那么返回参数resource(因为core api没有所谓的shortname,只有自定义资源有) if len(resource.Group) == 0 { return resource } // 遍历shortcutResources(apiserver自定义资源resource list中定义的shortname集合) for _, item := range shortcutResources { // 判断shortcutResources中item的shortform的Group是否是resource group的前缀 if !strings.HasPrefix(item.ShortForm.Group, resource.Group) { continue } // 如果参数resource的Resource和遍历item的ShortForm.Resource相等,表明匹配 if resource.Resource == item.ShortForm.Resource { resource.Resource = item.LongForm.Resource resource.Group = item.LongForm.Group return resource } } } return resource } // 实现了 meta.RESTMapper的KindFor 方法,其他的类似 func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { // 先调用expandResourceShortcut方法(本文上面已分析)获取gvr,然后调用restmapper的kindFor方法 return e.RESTMapper.KindFor(e.expandResourceShortcut(resource)) } // ResourceShortcuts 表示一个结构,其中包含如何从资源的快捷方式转换为其全名的信息。 type resourceShortcuts struct { ShortForm schema.GroupResource LongForm schema.GroupResource } - 函数
// NewShortcutExpander 将一个 restmapper 包装在一个层(shortcutExpander)中,该层扩展了通过discovery client发现的快捷方式 func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface) meta.RESTMapper { return shortcutExpander{RESTMapper: delegate, discoveryClient: client} }
category_expansion.go
- 接口
// CategoryExpander 将类别字符串映射到 GroupResource。 // Categories 是一组资源的分类或“标签”。 type CategoryExpander interface { Expand(category string) ([]schema.GroupResource, bool) } - 结构体
// SimpleCategoryExpander 使用categories到 GroupResource 映射的静态映射来实现 CategoryExpander 接口. type SimpleCategoryExpander struct { Expansions map[string][]schema.GroupResource } // 实现CategoryExpander接口 func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) { ret, ok := e.Expansions[category] return ret, ok } // discoveryCategoryExpander 结构让 REST 客户端包装 (discoveryClient) 来检索 APIResourceList 列表, 然后转换为 fallbackExpander type discoveryCategoryExpander struct { discoveryClient discovery.DiscoveryInterface } // 实现CategoryExpander接口 func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) { // 从服务器获取所有支持的组和版本资源. _, apiResourceLists, _ := e.discoveryClient.ServerGroupsAndResources() // 如果没有找到资源,返回 if len(apiResourceLists) == 0 { return nil, false } // 保存获取的map(key:category value: []schema.GroupResource) discoveredExpansions := map[string][]schema.GroupResource{} // 遍历从apiserver获取的apiResourceLists for _, apiResourceList := range apiResourceLists { // 解析gv gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion) if err != nil { continue } // 按类别收集 GroupResources for _, apiResource := range apiResourceList.APIResources { // 获取apiResource的Categories if categories := apiResource.Categories; len(categories) > 0 { // 遍历 并追加到discoveredExpansions for _, category := range categories { groupResource := schema.GroupResource{ Group: gv.Group, Resource: apiResource.Name, } discoveredExpansions[category] = append(discoveredExpansions[category], groupResource) } } } } // 从discoveredExpansions中获取参数category对应的[]schema.GroupResource ret, ok := discoveredExpansions[category] return ret, ok } // UnionCategoryExpander 实现了 CategoryExpander 接口. // 它将给定的类别字符串映射到列表中所有 CategoryExpander 返回的扩展的联合。 type UnionCategoryExpander []CategoryExpander // 实现CategoryExpander的Expand方法 func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) { // 定义返回变量 ret := []schema.GroupResource{} ok := false // 为列表中的每个 CategoryExpander 展开类别并合并/组合结果。 for _, expansion := range u { curr, currOk := expansion.Expand(category) for _, currGR := range curr { found := false for _, existing := range ret { if existing == currGR { found = true break } } if !found { ret = append(ret, currGR) } } ok = ok || currOk } return ret, ok } - 函数
// NewDiscoveryCategoryExpander 返回一个类别扩展器,它利用 API 中的“类别”字段(discovery clinet 请求apiserver获取),通过发现客户端找到。 func NewDiscoveryCategoryExpander(client discovery.DiscoveryInterface) CategoryExpander { if client == nil { panic("Please provide discovery client to shortcut expander") } return discoveryCategoryExpander{discoveryClient: client} }