新聞中心
APiServer源碼分析之路由注冊(cè)
作者:陽(yáng)明 2023-03-17 17:51:30
云計(jì)算
云原生 先通過(guò) GenericConfig 創(chuàng)建一個(gè)名為 Apiextensions-Apiserver 的 GenericServer,GenericServer 提供了一個(gè)通用的 Http server,定義了通用的模板,例如地址、端口、認(rèn)證、授權(quán)、健康檢查等等通用功能。

創(chuàng)新互聯(lián)建站專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站建設(shè)、做網(wǎng)站、河北網(wǎng)絡(luò)推廣、微信小程序開(kāi)發(fā)、河北網(wǎng)絡(luò)營(yíng)銷、河北企業(yè)策劃、河北品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)建站為所有大學(xué)生創(chuàng)業(yè)者提供河北建站搭建服務(wù),24小時(shí)服務(wù)熱線:13518219792,官方網(wǎng)址:www.cdcxhl.com
前面我們對(duì) Kube APIServer 的入口點(diǎn)和 go-restful 有一個(gè)基礎(chǔ)了解后,我們就可以開(kāi)始來(lái)了解下 APIExtensionServer 是如何實(shí)例化的了。
APIExtensionServer
APIExtensionServer 的創(chuàng)建流程大致包含以下幾個(gè)步驟:
- 創(chuàng)建 GeneriAPIServer
- 實(shí)例化 CustomResourceDefinitions
- 實(shí)例化 APIGroupInfo
- InstallAPIGroup
三種類型的 Server 底層都需要依賴 GeneriAPIServer,第二步創(chuàng)建的 CustomResourceDefinitions 就是當(dāng)前類型的 Server 對(duì)象,用于后續(xù)進(jìn)行路由注冊(cè)。APIGroupInfo 是用于每個(gè)版本、每個(gè)資源類型對(duì)應(yīng)的存儲(chǔ)對(duì)象。最后調(diào)用 ??InstallAPIGroup?? 進(jìn)行路由注冊(cè),把每一個(gè)資源的版本,類型映射到一個(gè) URI 地址中。代碼如下所示:
// cmd/kube-apiserver/app/apiextensions.go
func createAPIExtensionsServer(apiextensionsConfig *apiextensionsapiserver.Config, delegateAPIServer genericapiserver.DelegationTarget) (*apiextensionsapiserver.CustomResourceDefinitions, error) {
return apiextensionsConfig.Complete().New(delegateAPIServer)
}
// 真正的代碼位于:/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go
// New 從指定的配置返回 CustomResourceDefinitions 的新實(shí)例
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*CustomResourceDefinitions, error) {
// APIExtensionsServer 依賴 GenericAPIServer
// 通過(guò) GenericConfig 創(chuàng)建一個(gè)名為 apiextensions-apiserver 的 genericServer
genericServer, err := c.GenericConfig.New("apiextensions-apiserver", delegationTarget)
// 實(shí)例化 CustomResourceDefinitions 對(duì)象
s := &CustomResourceDefinitions{
GenericAPIServer: genericServer,
}
apiResourceConfig := c.GenericConfig.MergedResourceConfig
// 實(shí)例化 APIGroupInfo 對(duì)象,APIGroup 指該 server 需要暴露的 API
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Scheme, metav1.ParameterCodec, Codecs)
// 如果開(kāi)啟了 v1 版本,將資源版本、資源、資源存儲(chǔ)存放在APIGroupInfo的map中
if apiResourceConfig.VersionEnabled(v1.SchemeGroupVersion) {
storage := map[string]rest.Storage{}
customResourceDefinitionStorage, err := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
storage["customresourcedefinitions"] = customResourceDefinitionStorage
storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefinitionStorage)
apiGroupInfo.VersionedResourcesStorageMap[v1.SchemeGroupVersion.Version] = storage
}
// 注冊(cè)API
if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
return nil, err
}
// 初始化 crd clientset 和 informers,用于初始化控制器
crdClient, err := clientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
s.Informers = externalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute)
delegateHandler := delegationTarget.UnprotectedHandler()
if delegateHandler == nil {
delegateHandler = http.NotFoundHandler()
}
versionDiscoveryHandler := &versionDiscoveryHandler{
discovery: map[schema.GroupVersion]*discovery.APIVersionHandler{},
delegate: delegateHandler,
}
groupDiscoveryHandler := &groupDiscoveryHandler{
discovery: map[string]*discovery.APIGroupHandler{},
delegate: delegateHandler,
}
// 初始化控制器
establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
// 申請(qǐng)handler處理器
crdHandler, err := NewCustomResourceDefinitionHandler(
versionDiscoveryHandler,
groupDiscoveryHandler,
s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
// ......
)
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)
// 初始化其他控制器
discoveryController := NewDiscoveryController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
namingController := status.NewNamingConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().V1().CustomResourceDefinitions(), crdClient.ApiextensionsV1())
finalizingController := finalizer.NewCRDFinalizer(
s.Informers.Apiextensions().V1().CustomResourceDefinitions(),
crdClient.ApiextensionsV1(),
crdHandler,
)
// 初始化openapi控制器
openapiController := openapicontroller.NewController(s.Informers.Apiextensions().V1().CustomResourceDefinitions())
var openapiv3Controller *openapiv3controller.Controller
if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) {
openapiv3Controller = openapiv3controller.NewController(s.Informers.Apiextensions().V1().CustomResourceDefinitions())
}
// 將 informer 以及 controller 添加到 PostStartHook 中
s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error {
s.Informers.Start(context.StopCh)
return nil
})
// 注冊(cè)hook函數(shù),啟動(dòng)前面實(shí)例化的各種controller
s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-controllers", func(context genericapiserver.PostStartHookContext) error {
if s.GenericAPIServer.OpenAPIVersionedService != nil && s.GenericAPIServer.StaticOpenAPISpec != nil {
go openapiController.Run(s.GenericAPIServer.StaticOpenAPISpec, s.GenericAPIServer.OpenAPIVersionedService, context.StopCh)
if utilfeature.DefaultFeatureGate.Enabled(features.OpenAPIV3) {
go openapiv3Controller.Run(s.GenericAPIServer.OpenAPIV3VersionedService, context.StopCh)
}
}
// 啟動(dòng)各種控制器
go namingController.Run(context.StopCh)
go establishingController.Run(context.StopCh)
go nonStructuralSchemaController.Run(5, context.StopCh)
go apiApprovalController.Run(5, context.StopCh)
go finalizingController.Run(5, context.StopCh)
discoverySyncedCh := make(chan struct{})
go discoveryController.Run(context.StopCh, discoverySyncedCh)
select {
case <-context.StopCh:
case <-discoverySyncedCh:
}
return nil
})
// ....
return s, nil
}
先通過(guò) GenericConfig 創(chuàng)建一個(gè)名為 apiextensions-apiserver 的 genericServer,genericServer 提供了一個(gè)通用的 http server,定義了通用的模板,例如地址、端口、認(rèn)證、授權(quán)、健康檢查等等通用功能。無(wú)論是 APIServer 還是 APIExtensionsServer 都依賴于 genericServer,實(shí)現(xiàn)方式如下所示:
// vendor/k8s.io/apiserver/pkg/server/config.go
// New 創(chuàng)建了一個(gè)新的服務(wù)器,該服務(wù)器將處理鏈與傳遞的服務(wù)器邏輯地結(jié)合在一起。
// name被用來(lái)區(qū)分日志記錄
func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*GenericAPIServer, error) {
// ...
handlerChainBuilder := func(handler http.Handler) http.Handler {
// BuildHandlerChainFunc 允許你通過(guò)裝飾 apiHandler 來(lái)構(gòu)建自定義處理程序鏈
// 目前默認(rèn)的處理鏈函數(shù)為 DefaultBuildHandlerChain:里面包含了大量默認(rèn)的處理方式
return c.BuildHandlerChainFunc(handler, c.Config)
}
apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler())
s := &GenericAPIServer{
Handler: apiServerHandler,
listedPathProvider: apiServerHandler,
// ......
}
// ......
// 安裝一些額外的路由,比如索引全部接口、metrics接口等
installAPI(s, c.Config)
return s, nil
}
// vendor/k8s.io/apiserver/pkg/server/handler.go
// HandlerChainBuilderFn 被用來(lái)包裝正在使用提供的處理器鏈 GoRestfulContainer 處理器
// 它通常用于應(yīng)用過(guò)濾,如身份驗(yàn)證和授權(quán)
type HandlerChainBuilderFn func(apiHandler http.Handler) http.Handler
// 該函數(shù)就是用來(lái)按照 go-restful 的模式初始化 Container
func NewAPIServerHandler(name string, s runtime.NegotiatedSerializer, handlerChainBuilder HandlerChainBuilderFn, notFoundHandler http.Handler) *APIServerHandler {
nonGoRestfulMux := mux.NewPathRecorderMux(name)
if notFoundHandler != nil {
nonGoRestfulMux.NotFoundHandler(notFoundHandler)
}
gorestfulContainer := restful.NewContainer()
gorestfulContainer.ServeMux = http.NewServeMux()
gorestfulContainer.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
gorestfulContainer.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
logStackOnRecover(s, panicReason, httpWriter)
})
gorestfulContainer.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
serviceErrorHandler(s, serviceErr, request, response)
})
director := director{
name: name,
goRestfulContainer: gorestfulContainer,
nonGoRestfulMux: nonGoRestfulMux,
}
return &APIServerHandler{
FullHandlerChain: handlerChainBuilder(director),
GoRestfulContainer: gorestfulContainer,
NonGoRestfulMux: nonGoRestfulMux,
Director: director,
}
}
然后實(shí)例化 CRD 和 APIGroupInfo,其中的 APIGroupInfo 對(duì)象用于描述資源組信息,一個(gè)資源對(duì)應(yīng)一個(gè) APIGroupInfo 對(duì)象,每個(gè)資源對(duì)應(yīng)一個(gè)資源存儲(chǔ)對(duì)象:
// /vendor/k8s.io/apiextensions-apiserver/pkg/server/genericapiserver.go
func NewDefaultAPIGroupInfo(group string, scheme *runtime.Scheme, parameterCodec runtime.ParameterCodec, codecs serializer.CodecFactory) APIGroupInfo {
return APIGroupInfo{
PrioritizedVersions: scheme.PrioritizedVersionsForGroup(group),
// 這個(gè)map用于存儲(chǔ)資源、資源存儲(chǔ)對(duì)象的映射關(guān)系
// 格式:資源版本/資源/資源存儲(chǔ)對(duì)象
// 資源存儲(chǔ)對(duì)象 RESTStorage,負(fù)責(zé)資源的CRUD
// 后續(xù)將 RESTStorage 轉(zhuǎn)換為 http 的 handler 函數(shù)
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"},
Scheme: scheme,
ParameterCodec: parameterCodec,
NegotiatedSerializer: codecs,
}
}
然后需要注冊(cè) APIGroupInfo,通過(guò) s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo) 來(lái)實(shí)現(xiàn)的,將 APIGroupInfo 中的資源對(duì)象注冊(cè)到APIExtensionServerHandler 函數(shù)。其過(guò)程是:
- 遍歷 APIGroupInfo。
- 將資源組、資源版本、資源名稱映射到 http path 請(qǐng)求路徑。
- 通過(guò) InstallREST 函數(shù)將資源存儲(chǔ)對(duì)象作為資源的 handlers 方法。
- 最后用 go-restfu l的 ws.Route 將定義好的請(qǐng)求路徑和 handlers 方法添加路由到 go-restful。
詳細(xì)的代碼如下所示:
// /vendor/k8s.io/apiextensions-apiserver/pkg/server/genericapiserver.go
// 在API中暴露指定的 APIGroup
func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
return s.InstallAPIGroups(apiGroupInfo)
}
func (s *GenericAPIServer) InstallAPIGroups(apiGroupInfos ...*APIGroupInfo) error {
// ...
// 獲取 OpenAPI 模型
openAPIModels, err := s.getOpenAPIModels(APIGroupPrefix, apiGroupInfos...)
// 遍歷所有的資源信息,一次安裝資源版本處理器
for _, apiGroupInfo := range apiGroupInfos {
if err := s.installAPIResources(APIGroupPrefix, apiGroupInfo, openAPIModels); err != nil {
return fmt.Errorf("unable to install api resources: %v", err)
}
apiVersionsForDiscovery := []metav1.GroupVersionForDiscovery{}
// ...
apiGroup := metav1.APIGroup{
Name: apiGroupInfo.PrioritizedVersions[0].Group,
Versions: apiVersionsForDiscovery,
PreferredVersion: preferredVersionForDiscovery,
}
s.DiscoveryGroupManager.AddGroup(apiGroup)
s.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(s.Serializer, apiGroup).WebService())
}
return nil
}
// installAPIResources 用于安裝 RESTStorage 以支持每個(gè) api groupversionresource
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels openapiproto.Models) error {
var resourceInfos []*storageversion.ResourceInfo
for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
apiGroupVersion := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
// ...
apiGroupVersion.MaxRequestBodyBytes = s.maxRequestBodyBytes
// 核心就是調(diào)用 InstallREST,參數(shù)為go-restful的container對(duì)象
r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer)
// ...
resourceInfos = append(resourceInfos, r...)
}
// ...
return nil
}
// InstallREST 將 REST handlers (storage, watch, proxy and redirect) 注冊(cè)到 restful 容器中。
// 預(yù)期提供的路徑 root 前綴將服務(wù)于所有操作,root 不能以斜線結(jié)尾。
func (g *APIGroupVersion) InstallREST(container *restful.Container) ([]*storageversion.ResourceInfo, error) {
// 比如從 InstallAPI 調(diào)用鏈下來(lái)這里的 g.Root 為 /apis,這樣就可以確定 handler 的前綴為 /apis/{goup}/{version}
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
// 實(shí)例化 APIInstaller 對(duì)象
installer := &APIInstaller{
group: g,
prefix: prefix,
minRequestTimeout: g.MinRequestTimeout,
}
// 調(diào)用 Install 函數(shù):注冊(cè) api,返回 go-restful 的 WebService 對(duì)象
apiResources, resourceInfos, ws, registrationErrors := installer.Install()
versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, staticLister{apiResources})
versionDiscoveryHandler.AddToWebService(ws)
// 將 WebService 添加到 Container 中,這需要了解 go-restful 框架的知識(shí)
container.Add(ws)
return removeNonPersistedResources(resourceInfos), utilerrors.NewAggregate(registrationErrors)
}
上面整個(gè)過(guò)程都是為了注冊(cè) API 來(lái)做的準(zhǔn)備,核心是在 installer.Install() 函數(shù)中,該函數(shù)就是將 API 資源添加處理器的。
// /vendor/k8s.io/apiserver/pkg/endpoints/installer.go
// 使用api installer 的前綴和版本創(chuàng)建一個(gè)新的 restful webservice 對(duì)象
// 這部分都屬于 go-restful 框架的用法
func (a *APIInstaller) newWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(a.prefix)
// a.prefix 包含 "prefix/group/version"
ws.Doc("API at " + a.prefix)
ws.Consumes("*/*")
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
ws.Produces(append(mediaTypes, streamMediaTypes...)...)
ws.ApiVersion(a.group.GroupVersion.String())
return ws
}
func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error) {
var apiResources []metav1.APIResource
var resourceInfos []*storageversion.ResourceInfo
var errors []error
// 新建一個(gè) WebService 對(duì)象(go-restful框架中的)
ws := a.newWebService()
// 將 paths 排序
paths := make([]string, len(a.group.Storage))
var i int = 0
for path := range a.group.Storage {
paths[i] = path
i++
}
sort.Strings(paths)
// 獲取swagger spec規(guī)范
for _, path := range paths {
// 將 Storage 轉(zhuǎn)換成 Router,然后將路由注冊(cè)到 webservice
apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
if apiResource != nil {
apiResources = append(apiResources, *apiResource)
}
if resourceInfo != nil {
resourceInfos = append(resourceInfos, resourceInfo)
}
}
return apiResources, resourceInfos, ws, errors
}
這里最重要的就是 registerResourceHandlers 函數(shù)了,這個(gè)方法很長(zhǎng),核心功能是根據(jù) storage 構(gòu)造 handler,再將 handler 和 path 構(gòu)造成 go-restful 框架的 Route 對(duì)象,最后 Route 添加到 webservice。
// /vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
// ......
// 是否是命名空間級(jí)別
var namespaceScoped bool
// ......
// storage 支持哪些 verbs 操作,用于了解每個(gè) path 所支持的操作。
creater, isCreater := storage.(rest.Creater)
namedCreater, isNamedCreater := storage.(rest.NamedCreater)
lister, isLister := storage.(rest.Lister)
getter, isGetter := storage.(rest.Getter)
// ......
// 獲取指定范圍的操作列表
switch {
case !namespaceScoped:
// 處理非命名空間范圍的資源,如節(jié)點(diǎn)
// ......
// 添加 actions 到資源路徑:/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)
// ......
default:
// 命名空間級(jí)別的資源對(duì)象
namespaceParamName := "namespaces"
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}
// ......
// 構(gòu)造 action 列表
actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
// ......
}
// 為 actions 創(chuàng)建 Routes 路由
// 配置go-restful產(chǎn)生的MIME類型
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
allMediaTypes := append(mediaTypes, streamMediaTypes...)
ws.Produces(allMediaTypes...)
// ...
for _, action := range actions {
// ......
// 構(gòu)造 go-restful 的 RouteBuilder 對(duì)象
routes := []*restful.RouteBuilder{}
// 如果是子資源,kind應(yīng)該是prent的kind
if isSubresource {
parentStorage, ok := a.group.Storage[resource]
fqParentKind, err := GetResourceKind(a.group.GroupVersion, parentStorage, a.group.Typer)
kind = fqParentKind.Kind
}
// 根據(jù)不同的 Verb,注冊(cè)到不同的 handler 中去
switch action.Verb {
case "GET":
var handler restful.RouteFunction // go-restful的處理器
// 初始化handler
if isGetterWithOptions {
handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
} else {
handler = restfulGetResource(getter, reqScope)
}
//...
// 構(gòu)造 route(這都屬于go-restful框架的使用方法)
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)
// 將route添加到routes
addParams(route, action.Params)
routes = append(routes, route)
// ... 其他verb處理方式基本一致
case "POST": // Create a resource.
var handler restful.RouteFunction
if isNamedCreater {
handler = restfulCreateNamedResource(namedCreater, reqScope, admit)
} else {
handler = restfulCreateResource(creater, reqScope, admit)
}
// ......
}
// 遍歷路由,加入到 WebService 中
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))
// 將route加入到WebService中去
ws.Route(route)
}
}
// ......
return &apiResource, resourceInfo, nil
}
registerResourceHandlers 函數(shù)很長(zhǎng),但是我們可以先拋開(kāi)細(xì)節(jié),整體上了解下,可以看到就是先通過(guò) Storage 判斷支持哪些 Verbs 操作,然后生成 actions 列表,然后將每個(gè) action 構(gòu)造路由列表,最后也是將這些路由添加到 go-restful 的 WebService 中去,這里構(gòu)造的路由綁定的處理器實(shí)現(xiàn)方式路由不同,比如 GET 方式的 handler 是通過(guò) restfulGetResource 來(lái)實(shí)例化的,POST 方式的是通過(guò) restfulCreateResource 來(lái)實(shí)例化的,實(shí)現(xiàn)方式基本差不多。
GET 方式 handler 函數(shù) restfulGetResource 實(shí)現(xiàn)如下所示:
// /vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func restfulGetResource(r rest.Getter, scope handlers.RequestScope) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
handlers.GetResource(r, &scope)(res.ResponseWriter, req.Request)
}
}
restfulGetResource 函數(shù)得到的就是 restful.RouteFunction,這是 go-restful 的方式,真正去處理的是 ??handlers.GetResource?? 這個(gè)函數(shù),這個(gè)函數(shù)里面調(diào)用的是 getResourceHandler,該函數(shù)返回的就是一個(gè) http 標(biāo)準(zhǔn)庫(kù) handler 函數(shù),處理對(duì)應(yīng)的路由請(qǐng)求。
GET 請(qǐng)求的處理過(guò)程比較簡(jiǎn)單,通過(guò)請(qǐng)求的查詢構(gòu)造出一個(gè) metav1.GetOptions,然后交給 Getter 接口處理,最后將查詢結(jié)果轉(zhuǎn)換后返回給請(qǐng)求者。
//vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
// GetResource 返回一個(gè)函數(shù),該函數(shù)處理從 rest.Storage 對(duì)象中檢索單個(gè)資源的操作。
func GetResource(r rest.Getter, scope *RequestScope) http.HandlerFunc {
return getResourceHandler(scope,
func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) {
// 初始化需要的 GetOptions
options := metav1.GetOptions{}
// 獲取查詢的參數(shù)
if values := req.URL.Query(); len(values) > 0 {
// ...
// 將查詢的參數(shù)進(jìn)行解碼,編程 GetOptions 對(duì)象
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil {
err = errors.NewBadRequest(err.Error())
return nil, err
}
}
// 然后使用 Getter 接口來(lái)處理
return r.Get(ctx, name, &options)
})
}
// getResourceHandler 是用于獲取請(qǐng)求的 HTTP Handler函數(shù)
// 它委托給傳入的 getterFunc 來(lái)執(zhí)行實(shí)際的 get
func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
// ...
namespace, name, err := scope.Namer.Name(req)
/// ...
ctx := req.Context()
ctx = request.WithNamespace(ctx, namespace)
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
// ...
// 使用 getterFunc 來(lái)執(zhí)行實(shí)際的 get 操作,
result, err := getter(ctx, name, req, trace)
// ...
// 將結(jié)果轉(zhuǎn)換為用戶需要的格式返回給用戶
transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
}
}
POST 的處理器也是類似的,對(duì)應(yīng)的邏輯在 restfulCreateResource 中:
// /vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
handlers.CreateResource(r, &scope, admit)(res.ResponseWriter, req.Request)
}
}
同樣真正去處理的是 handlers.CreateResource 這個(gè)函數(shù),這個(gè)函數(shù)里面調(diào)用的是 createHandler,該函數(shù)返回的就是一個(gè) http 標(biāo)準(zhǔn)庫(kù) handler 函數(shù),處理對(duì)應(yīng)的路由請(qǐng)求。
//vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
// CreateNamedResource returns a function that will handle a resource creation with name.
func CreateNamedResource(r rest.NamedCreater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
return createHandler(r, scope, admission, true)
}
// CreateResource 返回將處理資源創(chuàng)建的函數(shù)
func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
}
createHandler 的實(shí)現(xiàn)代碼比較長(zhǎng),主要做了一下幾件事:
- 對(duì)查詢串進(jìn)行解碼生成 metav1.CreateOptions 。
- 對(duì)請(qǐng)求的 body 體中的數(shù)據(jù)進(jìn)行解碼,生成資源對(duì)象。解碼的對(duì)象版本是 internal 版本,internal 版本是該資源對(duì)象所有版本字段的全集。針對(duì)不同版本的對(duì)象內(nèi)部可以使用相同的代碼進(jìn)行處理。
- 對(duì)對(duì)象進(jìn)行修改的準(zhǔn)入控制,判斷是否修需要修改對(duì)象。
- 交給 creater 接口創(chuàng)建資源對(duì)象。
- 將數(shù)據(jù)轉(zhuǎn)換為期望的格式寫(xiě)入 response 中,調(diào)用 creater 接口返回的結(jié)果仍然是 internal 版本,編碼時(shí),會(huì)編碼成用戶請(qǐng)求的版本返回給用戶。
//vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
// 返回一個(gè) http handler 函數(shù),處理對(duì)應(yīng)的路由請(qǐng)求
func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
// 標(biāo)準(zhǔn) http handler 函數(shù)
return func(w http.ResponseWriter, req *http.Request) {
// ...
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
// 找到合適的 Serializer
gv := scope.Kind.GroupVersion()
s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
// 將請(qǐng)求解碼成 CreateOptions
options := &metav1.CreateOptions{}
values := req.URL.Query()
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
// ...
}
// ...
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
defaultGVK := scope.Kind
original := r.New()
// ...
// 找到合適的解碼器
decoder := scope.Serializer.DecoderToVersion(decodeSerializer, scope.HubGroupVersion)
// 請(qǐng)請(qǐng)求體 body 進(jìn)行解碼
obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
ctx = request.WithNamespace(ctx, namespace)
// 審計(jì)、準(zhǔn)入、請(qǐng)求日志記錄
ae := audit.AuditEventFrom(ctx)
admit = admission.WithAudit(admit, ae)
audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
userInfo, _ := request.UserFrom(ctx)
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
requestFunc := func() (runtime.Object, error) {
return r.Create(
ctx,
name,
obj,
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
options,
)
}
// 處理請(qǐng)求
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
// ...
// 執(zhí)行準(zhǔn)入控制的mutate操作,就是在創(chuàng)建對(duì)象的時(shí)候進(jìn)行修改
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
return nil, err
}
}
// ......
// 調(diào)用創(chuàng)建方法
result, err := requestFunc()
return result, err
})
code := http.StatusCreated
status, ok := result.(*metav1.Status)
if ok && 標(biāo)題名稱:APIServer源碼分析之路由注冊(cè)
文章出自:http://fisionsoft.com.cn/article/cosdchi.html


咨詢
建站咨詢
