Kubernetes-apiServer源码深度分析(五)API Resource的装载

224 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情 记得看代码中的注释哈,理解都在里面 源码基于v1.24

本篇文章介绍API Resource的装载,把Apigroup对象装载到系统中,底层的存储,响应,注册机制

在哪里开始装载API Resource呢?

在InstallLegacyAPI中调用了GenericAPIServer.InstallLegacyAPIGroup

func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
   if !s.legacyAPIGroupPrefixes.Has(apiPrefix) {
      return fmt.Errorf("%q is not in the allowed legacy API prefixes: %v", apiPrefix, s.legacyAPIGroupPrefixes.List())
   }

   openAPIModels, err := s.getOpenAPIModels(apiPrefix, apiGroupInfo)
   if err != nil {
      return fmt.Errorf("unable to get openapi models: %v", err)
   }

   if err := s.installAPIResources(apiPrefix, apiGroupInfo, openAPIModels); err != nil {
      return err
   }

   // Install the version handler.
   // Add a handler at /<apiPrefix> to enumerate the supported api versions.
   s.Handler.GoRestfulContainer.Add(discovery.NewLegacyRootAPIHandler(s.discoveryAddresses, s.Serializer, apiPrefix).WebService())

   return nil
}


func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels openapiproto.Models) error {
   var resourceInfos []*storageversion.ResourceInfo
   for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
      if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
         klog.Warningf("Skipping API %v because it has no resources.", groupVersion)
         continue
      }

      apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
      if err != nil {
         return err
      }
      if apiGroupInfo.OptionsExternalVersion != nil {
         apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
      }
      apiGroupVersion.OpenAPIModels = openAPIModels

      if openAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
         typeConverter, err := fieldmanager.NewTypeConverter(openAPIModels, false)
         if err != nil {
            return err
         }
         apiGroupVersion.TypeConverter = typeConverter
      }

      apiGroupVersion.MaxRequestBodyBytes = s.maxRequestBodyBytes

      r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer)
      if err != nil {
         return fmt.Errorf("unable to setup API %v: %v", apiGroupInfo, err)
      }
      resourceInfos = append(resourceInfos, r...)
   }

   s.RegisterDestroyFunc(apiGroupInfo.destroyStorage)

   if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionAPI) &&
      utilfeature.DefaultFeatureGate.Enabled(features.APIServerIdentity) {
      // API installation happens before we start listening on the handlers,
      // therefore it is safe to register ResourceInfos here. The handler will block
      // write requests until the storage versions of the targeting resources are updated.
      s.StorageVersionManager.AddResourceInfo(resourceInfos...)
   }

   return nil
}

怎么装载的?

func (g *APIGroupVersion) InstallREST(container *restful.Container) ([]*storageversion.ResourceInfo, error) {
   prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
   installer := &APIInstaller{
      group:             g,
      prefix:            prefix,
      minRequestTimeout: g.MinRequestTimeout,
   }

   apiResources, resourceInfos, ws, registrationErrors := installer.Install()
   versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, staticLister{apiResources})
   versionDiscoveryHandler.AddToWebService(ws)
   container.Add(ws)
   return removeNonPersistedResources(resourceInfos), utilerrors.NewAggregate(registrationErrors)
}

func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error) {
   var apiResources []metav1.APIResource
   var resourceInfos []*storageversion.ResourceInfo
   var errors []error
   ws := a.newWebService()

   // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
   paths := make([]string, len(a.group.Storage))
   var i int = 0
   for path := range a.group.Storage {
      paths[i] = path
      i++
   }
   sort.Strings(paths)
   for _, path := range paths {
      apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
      if err != nil {
         errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
      }
      if apiResource != nil {
         apiResources = append(apiResources, *apiResource)
      }
      if resourceInfo != nil {
         resourceInfos = append(resourceInfos, resourceInfo)
      }
   }
   return apiResources, resourceInfos, ws, errors
}
/*
    代码过长留下了核心部分
*/
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
   admit := a.group.Admit

   optionsExternalVersion := a.group.GroupVersion
   if a.group.OptionsExternalVersion != nil {
      optionsExternalVersion = *a.group.OptionsExternalVersion
   }

   resource, subresource, err := splitSubresource(path)
   if err != nil {
      return nil, nil, err
   }

   group, version := a.group.GroupVersion.Group, a.group.GroupVersion.Version

   fqKindToRegister, err := GetResourceKind(a.group.GroupVersion, storage, a.group.Typer)
   if err != nil {
      return nil, nil, err
   }

   versionedPtr, err := a.group.Creater.New(fqKindToRegister)
   if err != nil {
      return nil, nil, err
   }
   defaultVersionedObject := indirectArbitraryPointer(versionedPtr)
   kind := fqKindToRegister.Kind
   isSubresource := len(subresource) > 0

   // If there is a subresource, namespace scoping is defined by the parent resource
   var namespaceScoped bool
   if isSubresource {
      parentStorage, ok := a.group.Storage[resource]
      if !ok {
         return nil, nil, fmt.Errorf("missing parent storage: %q", resource)
      }
      scoper, ok := parentStorage.(rest.Scoper)
      if !ok {
         return nil, nil, fmt.Errorf("%q must implement scoper", resource)
      }
      namespaceScoped = scoper.NamespaceScoped()

   } else {
      scoper, ok := storage.(rest.Scoper)
      if !ok {
         return nil, nil, fmt.Errorf("%q must implement scoper", resource)
      }
      namespaceScoped = scoper.NamespaceScoped()
   }

   // what verbs are supported by the storage, used to know what verbs we support per path
   creater, isCreater := storage.(rest.Creater)
   namedCreater, isNamedCreater := storage.(rest.NamedCreater)
   lister, isLister := storage.(rest.Lister)
   getter, isGetter := storage.(rest.Getter)
   getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
   gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
   collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
   updater, isUpdater := storage.(rest.Updater)
   patcher, isPatcher := storage.(rest.Patcher)
   watcher, isWatcher := storage.(rest.Watcher)
   connecter, isConnecter := storage.(rest.Connecter)
   storageMeta, isMetadata := storage.(rest.StorageMetadata)
   storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)
   gvAcceptor, _ := storage.(rest.GroupVersionAcceptor)
   if !isMetadata {
      storageMeta = defaultStorageMetadata{}
   }
   ...
// Get the list of actions for the given scope.
/*
    生成actions
*/
switch {
case !namespaceScoped:
   // Handle non-namespace scoped resources like nodes.
...
   // Handler for standard REST verbs (GET, PUT, POST and DELETE).
   // Add actions at the resource path: /api/apiVersion/resource
   actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
   actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
   actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
   // DEPRECATED in 1.11
   actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList)

   // Add actions at the item path: /api/apiVersion/resource/{name}
   actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter)
   if getSubpath {
      actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter)
   }
   actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
   actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
   actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
   // DEPRECATED in 1.11
   actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher)
   actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter)
   actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath)
default:
   namespaceParamName := "namespaces"
   // Handler for standard REST verbs (GET, PUT, POST and DELETE).
   namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string")
   namespacedPath := namespaceParamName + "/{namespace}/" + resource
   namespaceParams := []*restful.Parameter{namespaceParam}

   resourcePath := namespacedPath
   resourceParams := namespaceParams
   itemPath := namespacedPath + "/{name}"
   nameParams := append(namespaceParams, nameParam)
   proxyParams := append(nameParams, pathParam)
   itemPathSuffix := ""
   if isSubresource {
      itemPathSuffix = "/" + subresource
      itemPath = itemPath + itemPathSuffix
      resourcePath = itemPath
      resourceParams = nameParams
   }
   apiResource.Name = path
   apiResource.Namespaced = true
   apiResource.Kind = resourceKind
   namer := handlers.ContextBasedNaming{
      Namer:         a.group.Namer,
      ClusterScoped: false,
   }

   actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
   actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
   actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
   // DEPRECATED in 1.11
   actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList)

   actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter)
   if getSubpath {
      actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter)
   }
   actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
   actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
   actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
   // DEPRECATED in 1.11
   actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher)
   actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter)
   actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath)

...
/*
    route的注入
*/
for _, route := range routes {
   route.Metadata(ROUTE_META_GVK, metav1.GroupVersionKind{
      Group:   reqScope.Kind.Group,
      Version: reqScope.Kind.Version,
      Kind:    reqScope.Kind.Kind,
   })
   route.Metadata(ROUTE_META_ACTION, strings.ToLower(action.Verb))
   ws.Route(route)
}

Summary

  1. 函数之间的调用逻辑 image.png
  2. 注册资源对象的逻辑 image.png