精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Kubernetes API Server handler 注冊過程分析

云計算
本文以內置資源的 handler 注冊過程為線索介紹了 APiServer 的啟動過程和 handler 注冊過程。
作者|韓偉森,就職于中國移動云能力中心,專注于云原生領域。

前言

K8s提供 Aggregated APIServer? 的擴展方式,編寫 Aggregated APIServer? 本質上和K8s構建方式類似,理解 APiServer 資源的加載方式,能更好好的理解如何開發Aggregated APIServer。本文以內置資源的 handler 注冊過程為線索介紹了 APiServer 的啟動過程和 handler 注冊過程。使用k8s代碼commit id為c6970e64528ba78b74bf77b86f9b78b7b61bd0cd

APIServer啟動過程介紹

圖片

圖1 APIServer啟動流程

圖1給出了 ApiServer 的初始化流程,首先通過 CreateServerChain 構造出3個APIServer:

  • AggregatorServer:攔截Aggregated APIServer? 中定義的資源對象請求,并轉發給相關的Aggregated APIServer 處理。
  • KubeAPIServer:用于處理 k8s 的內建資源,如:Deployment,ConfigMap 等。
  • APIExtensionServer:負責處理用戶自定義資源。

它們之間的處理順序為如下圖所示,當用戶請求進來,先判斷 AggregatorServer? 能否處理,否則代理給 kubeApiServer? ,如果 kubeApiServer? 不能處代理給 ApiExtensionServer 處理,如果都不能處理則交給 notFoundHandler 處理。

圖片

圖2 三種 APIServer 請求順序

限于篇幅原因,本文主要分析 kubeapiserver 的啟動過程。

CreateApiServerConfig? 通過調用 buildGenericConfig? 構建 genericapiserver.Config。genericapiserver.Config? 中包含了啟動Genericapiserver? 所需要的配置信息,比如:RequestTimeout? 定義了請求的超時時間,AdmissionControl? 對象進行準入控制。buildGenericConfig? 中需要注意的是 BuildHandlerChainFunc?,請求在路由給資源對象的handler前先經過的BuildHandlerChainFunc? 中定義的 Filter? 。參考圖1,通過深入 buildGenericConfig? 可以發現 BuildHandlerChainFunc? 傳入的是 DefaultBuildHandlerChain? ,其中 Filter 先定義的后調用。

// k8s.io/apiserver/pkg/server/config.go

func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler := filterlatency.TrackCompleted(apiHandler)
// 構造權限檢查filter
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
...
// 構造認證filter
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences)
...
// 構造請求超時filter, LongRunningFunc會判斷該請求是否是需要LongRunning的,比如watch的請求,如果是,該filter不會對這類請求生效
// WithTimeoutForNonLongRunningRequests will call the rest of the request handling in a go-routine with the
// context with deadline. The go-routine can keep running, while the timeout logic will return a timeout to the client.
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc)
handler = genericapifilters.WithRequestDeadline(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator,
c.LongRunningFunc, c.Serializer, c.RequestTimeout)

handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup)
...
// 初始化RequestInfo的filter并將其放入context中,后續的處理邏輯可以從context直接獲取RequestInfo
handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver)
....
return handler
}

CreateKubeAPIServer? 中調用了kubeAPIServerConfig.Complete().New?構造出了 kubeAPIServer? 的 GenericServer。kubeAPIServerConfig.Complete().New?中通過調用 m.InstallLegacyAPI? 初始化核心資源并添加進路由中,對應的是以 api 開頭的資源,如:Pod,ConfigMap 等。調用 m.InstallAPI 初始化以 apis 開頭的內置資源如:Deployment。

handler的注冊過程

從圖1可以看出 InstallAPI? 與 InstallLegacyAPI? 的創建過程基本類似,本文主要介紹 InstallAPI 的初始化過程。

在調用 InstallAPI? 之前kubeAPIServerConfig.Complete().New?會先創建內置資源對象的RESTStorageProvider? 作為 InstallAPI 的入參

//pkg/controlplane/instance.go

func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*Instance, error) {
...
// 構造內置資源的RESTStorageProvider
restStorageProviders := []RESTStorageProvider{
apiserverinternalrest.StorageProvider{},
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator, APIAudiences: c.GenericConfig.Authentication.APIAudiences},
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver},
autoscalingrest.RESTStorageProvider{},
batchrest.RESTStorageProvider{},
certificatesrest.RESTStorageProvider{},
coordinationrest.RESTStorageProvider{},
discoveryrest.StorageProvider{},
networkingrest.RESTStorageProvider{},
noderest.RESTStorageProvider{},
policyrest.RESTStorageProvider{},
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer},
schedulingrest.RESTStorageProvider{},
storagerest.RESTStorageProvider{},
flowcontrolrest.RESTStorageProvider{InformerFactory: c.GenericConfig.SharedInformerFactory},
// keep apps after extensions so legacy clients resolve the extensions versions of shared resource names.
// See https://github.com/kubernetes/kubernetes/issues/42392
appsrest.StorageProvider{},
admissionregistrationrest.RESTStorageProvider{},
eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL},
}
if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...); err != nil {
return nil, err
}
...
}

RESTStorageProvider? 是一個接口,通過其 NewRESTStorage? 構造出 APIGroupInfo? ,APIGroupInfo? 包含注冊資源所需的基本信息比如編解碼器,組下所有資源的 Storage 對象VersionedResourcesStorageMap。

//k8s.io/apiserver/pkg/server/genericapiserver.go

// Info about an API group.
type APIGroupInfo struct {
PrioritizedVersions []schema.GroupVersion
// Info about the resources in this group. It's a map from version to resource to the storage.
VersionedResourcesStorageMap map[string]map[string]rest.Storage
...
// NegotiatedSerializer controls how this group encodes and decodes data
NegotiatedSerializer runtime.NegotiatedSerializer
// ParameterCodec performs conversions for query parameters passed to API calls
ParameterCodec runtime.ParameterCodec
...
}

VersionedResourcesStorageMap? 需要重點注意,編寫 Aggregated APIServer?主要邏輯是通過 NewDefaultAPIGroupInfo? 初始化 APIGroupInfo? 以后設置 VersionedResourcesStorageMap? 屬性。VersionedResourcesStorageMap?的簽名是 map[string]map[string]rest.Storage?。第一個key是版本號,第二個key是資源名稱,資源名稱可以是 deployment 這種資源,同時也能是子資源如 pod/status? , pod/log? 等是pod的子資源有單獨的storage。最終構建handler的請求路徑是基于 VersionedResourcesStorageMap? 中提供的版本號和資源名稱確定的 。rest.Storage 用于處理具體的請求,其聲明如下:

// k8s.io/apiserver/pkg/registry/rest/rest.go

// Storage is a generic interface for RESTful storage services.
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
// that objects may implement any of the below interfaces.
type Storage interface {
// New returns an empty object that can be used with Create and Update after request data has been put into it.
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
New() runtime.Object

// Destroy cleans up its resources on shutdown.
// Destroy has to be implemented in thread-safe way and be prepared
// for being called more than once.
Destroy()
}

實現 rest.Storage? 的接口最基本的,如果需要支持不同的請求,還需要實現其他的接口,相關定義在 k8s.io/apiserver/pkg/registry/rest/rest.go中,如:

// k8s.io/apiserver/pkg/registry/rest/rest.go

// 資源對象支持POST請求,例入通過kubectl create一個資源對象。
// Creater is an object that can create an instance of a RESTful object.
type Creater interface {
// New returns an empty object that can be used with Create after request data has been put into it.
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
New() runtime.Object

// Create creates a new version of a resource.
Create(ctx context.Context, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error)
}
// 資源對象支持GET請求,例如通過kubectl get 一個資源對象。
// Getter is an object that can retrieve a named RESTful resource.
type Getter interface {
// Get finds a resource in the storage by name and returns it.
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
// returned error value err when the specified resource is not found.
Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error)
}
// 支持對資源對象進行watch操作 例如通過kubectl get 資源對象 -w。
type Watcher interface {
// 'label' selects on labels; 'field' selects on the object's fields. Not all fields
// are supported; an error should be returned if 'field' tries to select on a field that
// isn't supported. 'resourceVersion' allows for continuing/starting a watch at a
// particular version.
Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error)
}

后續的處理中會依據 Creater? ,Getter? 和 Watcher? 等接口生成對應請求的handler,后文會進行具體的分析。k8s的內置資源存儲都使用 etcd,因此內置資源的 Storage 是通過 Store? 構建。Store? 定義在 /k8s.io/apiserver/pkg/registry/generic/registry/store.go?文件中,已經實現 Creater? , Getter?, Watcher?等接口,其他的資源只需在初始化 Store 時傳入一些必須的參數即可,無需編寫存儲層的交互代碼。下面給出了構造 deployment 的 store 的過程,其他內置資源大同小異。

// NewREST returns a RESTStorage object that will work against deployments.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *RollbackREST, error) {
// 創建一個deployments的genericregistry.Store
store := &genericregistry.Store{
// 初始化一個空資源對象,這里使用的是internal的版本,下面定義的各種strategy操作的對象也是internal版本,這樣就不用為每一種版本編寫一個strategy策略
NewFunc: func() runtime.Object { return &apps.Deployment{} },
// 初始化一個空資源對象列表
NewListFunc: func() runtime.Object { return &apps.DeploymentList{} },

DefaultQualifiedResource: apps.Resource("deployments"),
// 創建更新刪除策略 主要是做校驗及控制那些字段不能被用戶覆蓋用
CreateStrategy: deployment.Strategy,
UpdateStrategy: deployment.Strategy,
DeleteStrategy: deployment.Strategy,
ResetFieldsStrategy: deployment.Strategy,

TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
// 繼續完成store其他屬性的初始化,比如初始化store.Storage屬性。Storage主要用于和底層存儲層交互
if err := store.CompleteWithOptions(options); err != nil {
return nil, nil, nil, err
}

statusStore := *store
// deployment的status子資源也是使用store, 區別是更新策略不一樣, 即在update時會用舊對象的spec和lable覆蓋新對象的,防止非status字段被用戶意外覆蓋
statusStore.UpdateStrategy = deployment.StatusStrategy
statusStore.ResetFieldsStrategy = deployment.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, &RollbackREST{store: store}, nil
}

InstallAPIs? 調用鏈條比較深。參考圖1,最終會來到k8s.io/apiserver/pkg/endpoints/groupversion.go?的 InstallREST? 方法。InstallREST? 方法構造出 handler 的前綴,創建APIInstaller?,然后調用installer.Install()方法繼續handler的注冊

// k8s.io/apiserver/pkg/endpoints/groupversion.go

func (g *APIGroupVersion) InstallREST(container *restful.Container) ([]*storageversion.ResourceInfo, error) {
// 從InstallAPI調用鏈下來這里的g.Root為/apis,這樣就可以確定handler的前綴為/apis/{goup}/{version}
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)
}

installer.Install()? 方法會調用registerResourceHandlers? 方法,真正開始創建和注冊處理請求的 handler,需要說明的是a.group.Storage? 是上文提到的VersionedResourcesStorageMap? 傳入版本號后獲得的 map。讀者可以自行參考圖1的調用鏈進行分析。a.registerResourceHandlers? 就是為每一種Storage注冊handlers

// Install handlers for API resources.
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
// a.goup.Storage的簽名是 map[string]Storage, for循環的path是map的key,即資源名稱
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)
...
}

registerResourceHandlers? 會依據rest.Storage實現的接口生成相關的action。最終根據action生成handler并注冊到rest容器中。

// k8s.io/apiserver/pkg/endpoints/installer.go
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
...
// 初始化rest容器,根目錄是APIInstaller的prefix屬性,從InstallAPI調用鏈下來值為/apis/{goup}/{version}
ws := a.newWebService()
...
// 進行類型轉換判斷當前的storage支持哪些類型的操作
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)

// Get the list of actions for the given scope.
switch {
case !namespaceScoped:
// 構造有無namespace資源的action
// Handle non-namespace scoped resources like nodes.
...
default:
// 構造有namespace資源的action
// 構造handler的注冊路徑
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的值為 /namespaces/{namespace}/{resource}
resourcePath := namespacedPath
resourceParams := namespaceParams
// itemPath: /namespaces/{namespace}/{resource}/{name}
// name是請求資源對象的名字
itemPath := namespacedPath + "/{name}"
nameParams := append(namespaceParams, nameParam)
proxyParams := append(nameParams, pathParam)
itemPathSuffix := ""
if isSubresource {
itemPathSuffix = "/" + subresource
// 有子資源等情況下 resourcePath被定義為:/namespaces/{namespace}/{resource}/{name}/{subResource}
itemPath = itemPath + itemPathSuffix
// itemPath與resourcePath的值一樣
resourcePath = itemPath
resourceParams = nameParams
}
apiResource.Name = path
apiResource.Namespaced = true
apiResource.Kind = resourceKind
namer := handlers.ContextBasedNaming{
Namer: a.group.Namer,
ClusterScoped: false,
}
// 根據storage實現的接口添加添加相關的action
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)

// list or post across namespace.
// For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods.
// TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete)
if !isSubresource {
actions = appendIf(actions, action{"LIST", resource, params, namer, true}, isLister)
// DEPRECATED in 1.11
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer, true}, allowWatchList)
}
}
...
for _, action := range actions {
...
switch action.Verb {
case "GET": // Get a resource.
var handler restful.RouteFunction
// 構造get請求的handler
// restfulGetResourceWithOptions和restfulGetResource將handlers.GetResource函數轉換成restful.RouteFunction,即handler的函數簽名
if isGetterWithOptions {
handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
} else {
handler = restfulGetResource(getter, reqScope)
}
...
// 將handler注冊到rest容器中
// action.Path是上面定義的itemPath或resourcePath,對于GET來說是itemPath
// 當前注冊的handler的路徑是ws的根路徑加上ation.Path. 完整的路徑為:/apis/{goup}/{version}/namespaces/{namespace}/{resource}/{name}
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)
if isGetterWithOptions {
if err := AddObjectParams(ws, route, versionedGetOptions); err != nil {
return nil, nil, err
}
}
addParams(route, action.Params)
routes = append(routes, route)
}
case "LIST": // List all resources of a kind.
...
case "PUT": // Update a resource.
...
case "PATCH": // Partially update a resource
...
case "POST": // Create a resource.
...
case "DELETE": // Delete a resource.
....
}
...

}

registerResourceHandlers? 中創建的handler并不是直接調用Creater? ,Updater?等接口定義的方法,而是在外面包了一層代碼進行一些額外的處理,例如對象的編解碼,admission control 的處理邏輯,針對 watch 這種長鏈接需要進行協議的處理等,相關的定義在k8s.io/apiserver/pkg/endpoints/handlers包下。文本以Get和Create例,分析請求的處理邏輯。

Get請求的處理過程比較簡單,通過請求的查詢串構造出metav1.GetOptions ,然后交給 Getter 接口處理,最后在將查詢結果進行轉換發回給請求者。

// k8s.io/apiserver/pkg/endpoints/handlers/get.go

// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
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) {
// check for export
options := metav1.GetOptions{}
// 獲取查詢串
if values := req.URL.Query(); len(values) > 0 {
...
// 將查詢串解碼成metav1.GetOptions
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil {
err = errors.NewBadRequest(err.Error())
return nil, err
}
}
if trace != nil {
trace.Step("About to Get from storage")
}
// 交給Getter接口處理
return r.Get(ctx, name, &options)
})
}

// getResourceHandler is an HTTP handler function for get requests. It delegates to the
// passed-in getterFunc to perform the actual 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)
...
result, err := getter(ctx, name, req, trace)
...
// 對處理結果進行轉化為用戶期望的格式并寫入到response中返回給用戶
transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
}
}

Create的處理邏輯在 createHandler 中,代碼較長,主要做以下幾件事情:

  • 對查詢串進行解碼生成 metav1.CreateOptions 。
  • 對請求的body體中的數據進行解碼,生成資源對象。解碼的對象版本是 internal 版本,internal 版本是該資源對象所有版本字段的全集。針對不同版本的對象內部可以使用相同的代碼進行處理。
  • 對對象進行修改的準入控制,判斷是否修需要修改對象。
  • 交給creater接口創建資源對象。
  • 將數據轉換為期望的格式寫入 response 中,調用 creater 接口返回的結果仍然是 internal 版本,編碼時,會編碼成用戶請求的版本返回給用戶。
// 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)
}

func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
...
// 從request中取出請求body
body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
...
// 對查詢傳進行解碼生成metav1.CreateOptions
options := &metav1.CreateOptions{}
values := req.URL.Query()
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
...
}
// 將請求body解碼成資源對象, defaultGVK是用戶請求的版本,這里decoder解碼出來的對象是internal版本的對象
obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
...
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
// 構建調用create方法的函數
requestFunc := func() (runtime.Object, error) {
return r.Create(
ctx,
name,
obj,
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
options,
)
}
// Dedup owner references before updating managed fields
dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
...
// 執行mutation的admission操作,即在創建時對象進行修改操作。
// admin在buildGenericConfig中初始化,通過config傳遞給genericsever,然后傳遞到此處
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
return nil, err
}
}
// Dedup owner references again after mutating admission happens
dedupOwnerReferencesAndAddWarning(obj, req.Context(), true)
// 調用創建方法
result, err := requestFunc()
...
return result, err
})
...
// resutl也是internal版本的對象,transformResponseObject會轉換為用戶請求的版本并輸出
transformResponseObject(ctx, scope, trace, req, w, code, outputMediaType, result)
}

Create請求的流程可以總結為下圖

圖片

圖3 create請求處理流程

總結

本文介紹了 K8s內置資源的注冊過程,對APIServer的訪問會先經過 filter,再路由給具體的 handler。filter 在 DefaultBuildHandlerChain? 中定義,主要對請求做超時處理,認證,鑒權等操作。handler 的注冊則是初始化 APIGoupInfo? 并設置其 VersionedResourcesStorageMap? 后作為入參,調用 GenericAPIServer.InstallAPIGroups?即可完成 handler 的注冊。k8s.io/apiserver/pkg/endpoints/handlers?包中的代碼則是對用戶請求做編解碼,對象版本轉換,協議處理等操作,最后在交給rest.Storage 具體實現的接口進行處理。

參考

? https://blog.tianfeiyu.com/source-code-reading-notes/kubernetes/kube_apiserver.html#kube-apiserver-處理流程[1]

? https://hackerain.me/2020/10/05/kubernetes/kube-apiserver-genericapiserver.html

? https://hackerain.me/2020/09/19/kubernetes/kube-apiserver-storage-overview.html

? https://github.com/gosoon/source-code-reading-notes/blob/master/kubernetes/kube_apiserver.md

? https://time.geekbang.org/column/article/41876

責任編輯:未麗燕 來源: 云原生社區動態
相關推薦

2022-01-06 07:06:52

KubernetesResourceAPI

2024-06-26 00:22:35

2023-11-29 16:21:30

Kubernetes服務注冊

2012-02-20 14:47:08

JavaPlay

2023-03-17 07:53:20

K8sAPIServerKubernetes

2022-07-01 17:57:45

KubernetesAPI

2022-06-21 08:12:17

K8sAPI對象Kubernetes

2016-10-21 13:03:18

androidhandlerlooper

2022-06-07 16:17:45

KubernetesAPI Schema

2021-10-15 08:27:14

Kubernetes 工具Mizu

2024-01-30 07:58:41

KubernetesGAMMA網關

2023-11-07 07:08:57

2011-08-29 10:55:03

SQL Server分頁存儲過程優化效率分

2010-07-15 12:38:14

SQL Server存

2021-07-12 08:00:21

Nacos 服務注冊源碼分析

2014-01-06 16:51:06

Mesos注冊

2009-07-10 11:28:39

2015-08-10 14:41:39

Kubernetes監控開源容器管理

2016-06-15 10:35:59

云計算

2022-01-06 07:46:01

Traefik 開源Gateway API
點贊
收藏

51CTO技術棧公眾號

欧美一区二区三区日韩视频| 成人一级视频在线观看| 国产一区二区三区精品久久久| 香蕉视频禁止18| 羞羞视频在线免费国产| 99久久99久久免费精品蜜臀| 日韩av免费一区| 欧美视频www| 亚洲激情77| 欧美一区2区视频在线观看| 全黄性性激高免费视频| 91ph在线| 91色|porny| 亚洲一区二区久久久久久久| 精品久久久三级| 久久久久久久久久网站| 中文字幕中文字幕精品| 欧美一级欧美一级在线播放| 日韩毛片在线免费看| 色呦呦在线播放| 日本一区二区在线不卡| 国产精品v欧美精品v日韩精品| 中文字幕视频一区二区| 亚洲在线视频| 欧美国产视频一区二区| 天天爽天天爽天天爽| 精品一区免费| 亚洲福利视频二区| 成人亚洲免费视频| 成人精品三级| 日韩欧美高清视频| 日韩国产一级片| av网址在线免费观看| 国产欧美日本一区二区三区| 精品在线视频一区二区| 秋霞网一区二区| 国产盗摄女厕一区二区三区 | 国产伦精品一区| 国产男男gay体育生网站| 石原莉奈在线亚洲二区| 97国产在线视频| 久久国产精品波多野结衣av| 91精品国产91久久综合| 色诱女教师一区二区三区| 中文字幕在线1| 国产欧美日韩| 亚洲人成电影在线| 亚洲熟妇无码av| 亚洲区小说区图片区qvod按摩| 精品久久久网站| 欧美一级大片免费看| 婷婷视频一区二区三区| 日韩一区二区三区av| 91视频福利网| 日本成人手机在线| 精品久久久久一区二区国产| 娇妻高潮浓精白浆xxⅹ| 青青草原在线亚洲| 日韩精品免费在线观看| 性欧美丰满熟妇xxxx性仙踪林| 无码少妇一区二区三区| 亚洲欧美日韩精品久久| av男人的天堂av| 色天天久久综合婷婷女18| 日韩中文字幕精品视频| 中文字幕在线2021| 红桃视频欧美| 欧洲日韩成人av| 中文字幕一二区| 国产在线精品一区二区不卡了| 91综合免费在线| 人妻偷人精品一区二区三区| www成人在线观看| 日本精品一区| 精产国品自在线www| 一区二区三区欧美激情| 久久精品国产sm调教网站演员| 天堂在线中文网官网| 在线观看亚洲精品| www.51色.com| 另类尿喷潮videofree| 国产亚洲精品日韩| 久久久久久久久久97| 亚洲国产日韩欧美一区二区三区| 欧美中文字幕视频| 国产精品久久久久久无人区| 成人免费视频免费观看| 日本精品二区| 羞羞的视频在线观看| 欧美日韩在线免费观看| 少妇一级淫免费播放| 日韩免费一级| 国产亚洲欧洲高清| 久草视频免费在线播放| 日韩高清欧美激情| 动漫一区二区在线| av电影在线网| 亚欧色一区w666天堂| 亚洲36d大奶网| 久久91在线| 日韩小视频在线观看| 粉嫩aⅴ一区二区三区| 国产叼嘿视频在线观看| 欧美女同一区| 精品久久久久久久中文字幕| 天天干天天av| 日韩动漫一区| 伦理中文字幕亚洲| 无码人妻久久一区二区三区不卡| 国产精品自在欧美一区| 欧美亚洲精品日韩| а√天堂中文在线资源8| 欧美精品自拍偷拍动漫精品| 国产ts丝袜人妖系列视频| 中文视频一区| 国产精品丝袜久久久久久高清| 天天操天天射天天舔| 亚洲视频一区二区免费在线观看| 可以在线看的黄色网址| 成人av综合网| 色综合导航网站| 一区二区视频网站| 久久精品亚洲乱码伦伦中文 | 日本精品一区二区三区高清| 久久久久国产免费| 91中文字幕精品永久在线| 全球成人中文在线| 色欲av伊人久久大香线蕉影院| 亚洲欧美日韩在线| 中文字幕22页| 日本一本不卡| 国产精品成人在线| 国产私人尤物无码不卡| 日韩欧美在线免费观看| 菠萝菠萝蜜网站| 91久久综合| 国产精品对白一区二区三区| 在线观看wwwxxxx| 欧美高清视频一二三区 | 亚洲中无吗在线| 国产精品直播网红| 第九色区av在线| 91黄色免费版| 国产精久久一区二区三区| 欧美一级播放| 欧美久久综合性欧美| 小草在线视频免费播放| 国产视频综合在线| 亚洲黄网在线观看| 国产欧美精品一区| 五月婷婷六月合| 色男人天堂综合再现| 成人亲热视频网站| www久久日com| 精品国产凹凸成av人导航| 国产一级理论片| 99久久综合国产精品| 国产精品333| 免费久久精品| 国产精品一区二区三区毛片淫片 | 亚洲av无码一区二区乱子伦| 有码一区二区三区| 女同性恋一区二区三区| 国产精品视频| 任我爽在线视频精品一| 久久69成人| 萌白酱国产一区二区| www.激情五月| 精品久久久久久久久久| 欧美特级黄色录像| 老司机午夜精品| 超碰超碰超碰超碰超碰| 国产欧美三级电影| 国产成人91久久精品| 暖暖日本在线观看| 精品国免费一区二区三区| 国产性xxxx高清| 国产亚洲一区二区在线观看| 欧美激情国内自拍| 在线日本高清免费不卡| 日本亚洲自拍| 日韩高清在线观看一区二区| 91av国产在线| 欧美13一16娇小xxxx| 亚洲黄色在线观看| 特级西西444www高清大视频| 亚洲欧洲中文日韩久久av乱码| 特级特黄刘亦菲aaa级| 久久久久在线| 777久久精品一区二区三区无码| 美女福利一区| 91久久中文字幕| 在线免费看h| 久久久99久久精品女同性| 无码国产精品一区二区色情男同| 欧美亚洲国产bt| 黄色小视频在线免费看| 国产欧美日韩激情| 波多野结衣视频播放| 麻豆精品视频在线观看免费| 1024av视频| 欧美.日韩.国产.一区.二区| 欧美色欧美亚洲另类七区| 图片一区二区| 日韩美女在线播放| 草美女在线观看| x99av成人免费| 男同在线观看| 亚洲国产欧美一区二区三区久久| 国产免费视频一区二区三区| 欧美性xxxx极品hd欧美风情| 欧美性猛交xxxxx少妇| 中文字幕不卡在线播放| 在线黄色免费网站| 国产毛片精品一区| 亚洲精品自拍网| 天堂久久久久va久久久久| 日韩精品在线中文字幕| 婷婷亚洲图片| 亚洲精品久久区二区三区蜜桃臀| 精品国产午夜肉伦伦影院| 91久久在线播放| 日韩有码欧美| 国产免费成人av| 日韩毛片一区| 国产精品99久久99久久久二8| 国产在线观看www| 国模叶桐国产精品一区| 99福利在线| 久久这里有精品| 嫩草香蕉在线91一二三区| 伊人青青综合网站| 黄色美女网站在线观看| 亚洲男人天堂2023| 天堂v视频永久在线播放| 欧美成人精品福利| www.亚洲天堂.com| 日韩视频国产视频| a天堂中文在线观看| 欧美一区二区视频在线观看2020| 亚洲字幕av一区二区三区四区| 色婷婷久久综合| 欧美人一级淫片a免费播放| 一本一本大道香蕉久在线精品| 日本黄色片视频| 欧美性高潮在线| 无码aⅴ精品一区二区三区| 日韩欧美精品在线观看| 亚洲 欧美 中文字幕| 欧美在线免费视屏| 亚洲网站免费观看| 4438亚洲最大| 亚洲精品成av人片天堂无码| 欧美大胆一级视频| 日本xxxx人| 亚洲精品小视频在线观看| 免费一级在线观看| 在线视频日本亚洲性| 思思99re6国产在线播放| 久久精品国产欧美亚洲人人爽| 老司机在线视频二区| 欧美久久精品午夜青青大伊人| 宅男在线观看免费高清网站| 久久久视频在线| 69久成人做爰电影| 国产精品一区二区女厕厕| 国产一区二区三区| 国产欧美在线一区二区| 曰本一区二区三区视频| 一本一道久久a久久精品综合 | 波多野结衣之无限发射| 久久xxxx| 国产精品久久久久久9999| 粉嫩av一区二区三区| av鲁丝一区鲁丝二区鲁丝三区| 久久精品日产第一区二区三区高清版| 正在播放国产对白害羞| 亚洲一区二区三区四区五区中文| 中文字幕激情小说| 欧美剧在线免费观看网站| 韩国av免费在线观看| 亚洲少妇激情视频| 黄色网页在线免费观看| 91黄色8090| 亚洲成人毛片| 精品国产_亚洲人成在线| 色天天综合网| 各处沟厕大尺度偷拍女厕嘘嘘| 免费av网站大全久久| 亚洲美女高潮久久久| 国产视频一区二区在线| 精品99在线观看| 欧美在线色视频| 免费成人在线看| 色视频www在线播放国产成人| av日韩中文| 成人在线视频福利| 欧美巨大xxxx| 国产91porn| 蜜桃av一区二区在线观看 | 中文在线资源观看网站视频免费不卡| 欧美被狂躁喷白浆精品| 欧美日韩一区在线| 无码国产伦一区二区三区视频 | 国产美女高潮在线观看| 国产精品色婷婷视频| 亚洲精品白浆高清| 日韩视频 中文字幕| 六月丁香婷婷久久| 色呦呦一区二区| 亚洲综合激情网| 在线观看国产一区二区三区| 精品爽片免费看久久| 国产探花在线观看| 91久久久久久久一区二区| 日本久久一二三四| 欧美激情国产精品日韩| 成人在线视频一区| 青娱乐国产在线| 5月丁香婷婷综合| 亚乱亚乱亚洲乱妇| 国产99视频精品免视看7| 欧美日韩一区二区三区不卡视频| 精品无码av无码免费专区| 韩国一区二区在线观看| 欧美三级视频网站| 日韩欧美中文字幕在线播放| 天天干,夜夜爽| 久久露脸国产精品| 亚洲一区二区三区日本久久九| 正在播放亚洲| 另类成人小视频在线| 国产jizz18女人高潮| 欧美日韩在线播放三区四区| 国产精品久久久久一区二区国产| 日韩av片电影专区| 视频精品在线观看| 欧美精品成人网| 欧美激情在线一区二区| 波多野结衣影片| 一区二区三区四区精品| 欧美影视资讯| 先锋影音亚洲资源| 蜜臀av性久久久久蜜臀aⅴ四虎| 我想看黄色大片| 欧美日韩中文一区| 在线观看黄色av| 91中文在线观看| 韩日成人在线| 国产一级二级在线观看| 色综合咪咪久久| 国产系列电影在线播放网址| 国产精品久久久久久久天堂| 色婷婷色综合| 在线观看视频你懂得| 亚洲韩国精品一区| 视频在线不卡| 国产福利成人在线| 午夜精品毛片| 高清中文字幕mv的电影| 欧美性猛交视频| 欧美日韩视频在线播放| 91九色对白| 一本久道综合久久精品| 亚洲最大成人综合网| 欧美日韩国产美女| 日本三级韩国三级欧美三级| 国产在线精品二区| 日韩电影免费在线观看网站| 亚洲女人毛茸茸高潮| 日韩欧美在线网站| 国产免费拔擦拔擦8x在线播放| 日本精品一区| 国产酒店精品激情| 免费在线观看黄网站| 国产一区二区日韩| 影音先锋欧美激情| 国产成人精品无码播放| 亚洲免费成人av| 色吊丝在线永久观看最新版本| 国产精品视频久久久| 在线看片日韩| 免费看一级黄色| 精品国产凹凸成av人导航| 亚洲精品国产嫩草在线观看| 男女爱爱视频网站| 久久嫩草精品久久久久| 国产女人高潮的av毛片| 欧美一区三区三区高中清蜜桃| 久久国产成人午夜av影院宅| 国产综合内射日韩久| 欧美中文字幕一区二区三区| 久草成色在线| 亚洲人成77777| 92精品国产成人观看免费| 91亚洲国产成人久久精品麻豆| 国自产精品手机在线观看视频| 日韩在线观看一区| 精品无码在线视频| 日韩视频免费观看高清在线视频|