kubernetes源码阅读笔记——API Server(之二)
本篇将着重分析InstallLegacyAPIGroup方法。
vendor/k8s.io/apiserver/pkg/server/genericapiserver.go 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()) } if err := s.installAPIResources(apiPrefix, apiGroupInfo); 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 }
首先,判断传入的前缀是否合法。
其次,调用installAPIResources方法。
最后,在/api路径下生成一个WebService,并添加进Container。
进入installAPIResources方法:
vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error { openAPIGroupModels, err := s.getOpenAPIModelsForGroup(apiPrefix, apiGroupInfo) if err != nil { return fmt.Errorf("unable to get openapi models for group %v: %v", apiPrefix, err) } 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 := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix) if apiGroupInfo.OptionsExternalVersion != nil { apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion } apiGroupVersion.OpenAPIModels = openAPIGroupModels if err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer); err != nil { return fmt.Errorf("unable to setup API %v: %v", apiGroupInfo, err) } } return nil }
前面的逻辑类似于对传入的apiGroupInfo进行标准格式化,并循环地遍历所有可用version,包装成为APIGroupVersion对象,并对其调用InstallREST方法,为对象注册Handler。核心在于InstallREST方法。
一、InstallREST
进入InstallREST方法:
vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
func (g *APIGroupVersion) InstallREST(container *restful.Container) error { prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version) installer := &APIInstaller{ group: g, prefix: prefix, minRequestTimeout: g.MinRequestTimeout, enableAPIResponseCompression: g.EnableAPIResponseCompression, } apiResources, ws, registrationErrors := installer.Install() versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, staticLister{apiResources}) versionDiscoveryHandler.AddToWebService(ws) container.Add(ws) return utilerrors.NewAggregate(registrationErrors) }
方法逻辑是,基于这个GroupVersion对象的元素新建一个前缀字符串,然后建立一个APIInstaller结构体,调用Install方法,然后将返回的WebService添加进Container中。
进入Install方法:
vendor/k8s.io/apiserver/pkg/endpoints/installer.go // Install handlers for API resources. func (a *APIInstaller) Install() ([]metav1.APIResource, *restful.WebService, []error) { var apiResources []metav1.APIResource 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, 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) } } return apiResources, ws, errors }
这个方法的逻辑是,先创建一个WebService,然后将APIInstaller结构体中保存的所有API路径存入数组paths,对数组排序,遍历数组所有元素并调用registerResourceHandlers方法,将路径、对应的storage和WebService建立对应关系。最后,将这些对应关系存入apiResources数组中,并返回APIResource和WebService。
二、registerResourceHandlers
registerResourceHandlers方法长达数百行,是创建API的核心方法。节选最重要的部分如下:
vendor/k8s.io/apiserver/pkg/endpoints/installer.go func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, error) { ... 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) if !isMetadata { storageMeta = defaultStorageMetadata{} } ... // 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) // 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) actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter) actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath) ... routes := []*restful.RouteBuilder{} case "GET": // Get a resource. var handler restful.RouteFunction if isGetterWithOptions { handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource) } else { handler = restfulGetResource(getter, exporter, reqScope) } if needOverride { // need change the reported verb handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler) } else { handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, handler) } if a.enableAPIResponseCompression { handler = genericfilters.RestfulWithCompression(handler) } doc := "read the specified " + kind if isSubresource { doc = "read " + subresource + " of the specified " + kind } route := ws.GET(action.Path).To(handler).Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("read"+namespaced+kind+strings.Title(subresource)+operationSuffix). Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). Returns(http.StatusOK, "OK", producedObject).Writes(producedObject) ... routes = append(routes, 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) } ... return &apiResource, nil }
代码虽长,逻辑很清晰。大致的执行逻辑是:
1.判断storage是否支持create、list、get等方法,并对所有支持的方法进行进一步的处理,如if !isMetadata这一块一样,内容过多不一一贴出;
2.将所有支持的方法存入actions数组中;
3.遍历actions数组,在一个switch语句中,为所有元素定义路由。如贴出的case "GET"这一块,首先创建并包装一个handler对象,然后调用WebService的一系列方法,创建一个route对象,将handler绑定到这个route上。后面还有case "PUT"、case "DELETE"等一系列case,不一一贴出。最后,将route加入routes数组中。
4.遍历routes数组,将route加入WebService中。
5.最后,返回一个APIResource结构体。
这样,Install方法就通过调用registerResourceHandlers方法,完成了WebService与APIResource的绑定。
至此,InstallLegacyAPI方法的逻辑就分析完了。总的来说,这个方法遵循了go-restful的设计模式,在/api路径下注册了WebService,并将WebService加入Container中。
三、Container.Add
回到InstallREST方法,下一步调用container包的Add方法,将WebService添加到container中。
该方法位于外部包中,功能很简单,就是检查错误后,将传入的WebService加入传进来的restful Container中。
vendor/github.com/emicklei/go-restful/container.go // Add a WebService to the Container. It will detect duplicate root paths and exit in that case. func (c *Container) Add(service *WebService) *Container { c.webServicesLock.Lock() defer c.webServicesLock.Unlock() // if rootPath was not set then lazy initialize it if len(service.rootPath) == 0 { service.Path("/") } // cannot have duplicate root paths for _, each := range c.webServices { if each.RootPath() == service.RootPath() { log.Printf("[restful] WebService with duplicate root path detected:['%v']", each) os.Exit(1) } } // If not registered on root then add specific mapping if !c.isRegisteredOnRoot { c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux) } c.webServices = append(c.webServices, service) return c }
至此,InstallLegacyAPIGroup方法的核心“installAPIResources”就分析完了。后面还有一行s.Handler.GoRestfulContainer.Add(...),同样调用了Container包里的Add方法,作用是为/<apiPrefix>路径添加一个可枚举出所有可用version的Handler。
总结一下,InstallLegacyAPIGroup方法虽然复杂,但是作用就是一条,即为传入的路径注册Handler。