著者: Han Weisen は、China Mobile Cloud Capability Center に勤務し、クラウド ネイティブ分野に重点を置いています。序文K8s は、Aggregated APIServer の拡張メソッドを提供します。 Aggregated APIServer の作成は、基本的に K8s の構築方法と似ています。 APiServer リソースがどのようにロードされるかを理解することで、Aggregated APIServer の開発方法をより深く理解できるようになります。この記事では、組み込みのリソース ハンドラー登録プロセスに基づいて、APiServer の起動プロセスとハンドラー登録プロセスについて説明します。 k8sコードコミットID c6970e64528ba78b74bf77b86f9b78b7b61bd0cdを使用する APIServer 起動プロセスの概要 図1 APIServerの起動プロセス 図 1 は ApiServer の初期化プロセスを示しています。まず、CreateServerChain を通じて 3 つの APIServer が構築されます。 - AggregatorServer: Aggregated APIServer で定義されたリソース オブジェクト要求をインターセプトし、関連する Aggregated APIServer に転送して処理します。
- KubeAPIServer: Deployment、ConfigMap などの k8s の組み込みリソースを処理するために使用されます。
- APIExtensionServer: ユーザー定義のリソースの処理を担当します。
それらの処理順序を下図に示します。ユーザーからのリクエストが届くと、まず AggregatorServer がそれを処理できるかどうかが判断されます。それ以外の場合は、kubeApiServer に委任されます。 kubeApiServer が処理できない場合は、処理が ApiExtensionServer に委任されます。どちらも処理できない場合は、notFoundHandler に渡されて処理されます。 図2 3つのAPIServerリクエストシーケンス スペースの制限により、この記事では主に kubeapiserver の起動プロセスを分析します。 CreateApiServerConfig は、buildGenericConfig を呼び出して genericapiserver.Config を構築します。 genericapiserver.Config には、Genericapiserver を起動するために必要な構成情報が含まれています。たとえば、RequestTimeout はリクエストのタイムアウトを定義し、AdmissionControl オブジェクトはアドミッション コントロールを実行します。 buildGenericConfig で注意する必要があるのは、BuildHandlerChainFunc です。これは、リクエストがリソース オブジェクトのハンドラーにルーティングされる前に通過する、BuildHandlerChainFunc で定義されたフィルターです。図 1 を参照してください。buildGenericConfig をさらに詳しく調べると、BuildHandlerChainFunc が DefaultBuildHandlerChain を渡し、その中で Filter が最初に定義され、後で呼び出されることがわかります。 // k8s.io/apiserver/pkg/server/config.go
DefaultBuildHandlerChain()関数(apiHandler http.Handler, c *Config) http.Handler { ハンドラー:= filterlatency.TrackCompleted(apiHandler) //権限チェックフィルターを構築 ハンドラー = genericapifilters.WithAuthorization(ハンドラー、c.Authorization.Authorizer、c.Serializer) ... //認証フィルタを構築 ハンドラー = genericapifilters.WithAuthentication(ハンドラー、c.Authentication.Authenticator、failedHandler、c.Authentication.APIAudiences) ... // リクエストタイムアウトフィルターを構築します。 LongRunningFunc は、監視リクエストなどのリクエストに LongRunning が必要かどうかを判断します。その場合、そのようなリクエストに対してフィルターは効果を発揮しません。 // WithTimeoutForNonLongRunningRequestsは、残りのリクエスト処理をgoルーチンで呼び出します。 // 期限付きのコンテキスト。 go ルーチンは実行を継続できますが、タイムアウト ロジックはクライアントにタイムアウトを返します。 ハンドラー = genericfilters.WithTimeoutForNonLongRunningRequests(ハンドラー、c.LongRunningFunc) ハンドラー = genericapifilters.WithRequestDeadline(ハンドラー、c.AuditBackend、c.AuditPolicyRuleEvaluator、 c.LongRunningFunc、c.Serializer、c.RequestTimeout)
ハンドラー = genericfilters.WithWaitGroup(ハンドラー、c.LongRunningFunc、c.HandlerChainWaitGroup) ... // RequestInfo のフィルターを初期化し、コンテキストに配置します。後続の処理ロジックはコンテキストからRequestInfoを直接取得できる ハンドラー = genericapifilters.WithRequestInfo(ハンドラー、c.RequestInfoResolver) .... 戻りハンドラ } CreateKubeAPIServer は kubeAPIServerConfig.Complete().New を呼び出して、kubeAPIServer の GenericServer を構築します。 kubeAPIServerConfig.Complete().New では、m.InstallLegacyAPI が呼び出され、コア リソースを初期化してルートに追加します。これは、Pod、ConfigMap などの api で始まるリソースに対応します。Deployment などの apis で始まる組み込みリソースを初期化するには、m.InstallAPI を呼び出します。 ハンドラー登録プロセス図 1 からわかるように、InstallAPI と InstallLegacyAPI の作成プロセスは基本的に似ています。この記事では主に InstallAPI の初期化プロセスを紹介します。 InstallAPIを呼び出す前に、kubeAPIServerConfig.Complete().Newは、まずInstallAPIの入力パラメータとして組み込みリソースオブジェクトRESTStorageProviderを作成します。 //pkg/コントロールプレーン/instance.go
func (c completeConfig) New(delegationTarget genericapiserver.DelegationTarget) (*Instance, error) { ... //組み込みリソース用のRESTStorageProviderを構築します restStorageProvider := []RESTStorageProvider{ apiserverinternalrest.StorageProvider{}, 認証rest.RESTStorageProvider{認証子: c.GenericConfig.Authentication.Authenticator、APIオーディエンス: c.GenericConfig.Authentication.APIAudiences}、 authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer、RuleResolver: c.GenericConfig.RuleResolver}、 autoscalingrest.RESTStorageProvider{}、 バッチレスト.RESTStorageProvider{}、 証明書rest.RESTStorageProvider{}, コーディネーションレスト.RESTStorageProvider{}, discoveryrest.StorageProvider{}, networkingrest.RESTStorageProvider{}、 ノードレスト.RESTStorageProvider{}、 ポリシーレスト.RESTStorageProvider{}, rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer}, schedulingrest.RESTStorageProvider{}、 storagerest.RESTStorageProvider{}、 flowcontrolrest.RESTStorageProvider{インフォーマーファクトリー: c.GenericConfig.SharedInformerFactory}, // 拡張機能の後にアプリを保持して、従来のクライアントが共有リソース名の拡張機能バージョンを解決できるようにします。 // https://github.com/kubernetes/kubernetes/issues/42392 を参照してください アプリレスト.ストレージプロバイダー{}, 入場登録rest.RESTStorageProvider{}, eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL}, } if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter,restStorageProviders...);エラー != ゼロ { nil、エラーを返す } ... } RESTStorageProvider は、NewRESTStorage を通じて APIGroupInfo を構築するインターフェースです。 APIGroupInfo には、コーデックなどのリソースを登録するために必要な基本情報と、グループ内のすべてのリソースのストレージ オブジェクト VersionedResourcesStorageMap が含まれています。 //k8s.io/apiserver/pkg/server/genericapiserver.go
// API グループに関する情報。 APIGroupInfo構造体型{ PrioritizedVersions[]schema.GroupVersion // このグループ内のリソースに関する情報。これはバージョンからリソース、ストレージまでのマップです。 VersionedResourcesStorageMap map[文字列]map[文字列]rest.Storage ... // NegotiatedSerializer は、このグループがデータをエンコードおよびデコードする方法を制御します ネゴシエートされたシリアライザー ランタイム.ネゴシエートされたシリアライザー // ParameterCodec は API 呼び出しに渡されるクエリ パラメータの変換を実行します パラメータコーデック ランタイム.パラメータコーデック ... } VersionedResourcesStorageMap には特別な注意を払う必要があります。 Aggregated APIServer を作成する主なロジックは、NewDefaultAPIGroupInfo を通じて APIGroupInfo を初期化し、次に VersionedResourcesStorageMap プロパティを設定することです。 VersionedResourcesStorageMap のシグネチャは map[string]map[string]rest.Storage です。最初のキーはバージョン番号、2 番目のキーはリソース名です。リソース名は、デプロイメントなどのリソースにすることも、 pod/status 、 pod/log などのサブリソースにすることもできます。ポッドのサブリソースには、個別のストレージがあります。最終的にハンドラーを構築するリクエスト パスは、VersionedResourcesStorageMap で提供されるバージョン番号とリソース名に基づいて決定されます。 rest.Storage は特定のリクエストを処理するために使用され、その宣言は次のとおりです。 // k8s.io/apiserver/pkg/registry/rest/rest.go
// Storage は、RESTful ストレージ サービスの汎用インターフェースです。 // apiserver の RESTful API にエクスポートされるリソースは、このインターフェースを実装する必要があります。予想されている // オブジェクトは以下のいずれかのインターフェースを実装できます。 型ストレージインターフェース{ // New は、リクエスト データが入力された後に Create および Update で使用できる空のオブジェクトを返します。 // このオブジェクトは Codec.DecodeInto([]byte,runtime.Object) で使用するためにポインタ型である必要があります New() ランタイム.オブジェクト
// Destroy はシャットダウン時にリソースをクリーンアップします。 // 破棄はスレッドセーフな方法で実装され、準備される必要があります // 複数回呼び出された場合。 破壊する() } rest.Storage インターフェースの実装が最も基本的です。異なるリクエストをサポートする必要がある場合は、他のインターフェースも実装する必要があります。関連する定義は k8s.io/apiserver/pkg/registry/rest/rest.go にあります。例: // k8s.io/apiserver/pkg/registry/rest/rest.go
// リソース オブジェクトは POST リクエストをサポートします。たとえば、kubectl を使用してリソース オブジェクトを作成します。 // Creater は、RESTful オブジェクトのインスタンスを作成できるオブジェクトです。 型 Creater インターフェース { // New は、リクエスト データが格納された後に Create で使用できる空のオブジェクトを返します。 // このオブジェクトは Codec.DecodeInto([]byte,runtime.Object) で使用するためにポインタ型である必要があります New() ランタイム.オブジェクト
// Create はリソースの新しいバージョンを作成します。 作成(ctx context.Context、objruntime.Object、createValidation ValidateObjectFunc、options *metav1.CreateOptions) (runtime.Object、error) } // リソース オブジェクトは GET リクエストをサポートします。たとえば、kubectl を介してリソース オブジェクトを取得します。 // Getter は、名前付きの RESTful リソースを取得できるオブジェクトです。 型ゲッターインターフェース{ // Get はストレージ内のリソースを名前で検索し、それを返します。 // 任意のエラー値を返すことができますが、IsNotFound(err)は // 指定されたリソースが見つからない場合はエラー値 err が返されます。 Get(ctx context.Context、名前文字列、オプション *metav1.GetOptions) (runtime.Object、エラー) } // kubectl get resource object -w を使用するなど、リソース オブジェクトに対する監視操作をサポートします。 ウォッチャーインターフェース型{ // 'label' はラベルを選択します。 「field」はオブジェクトのフィールドを選択します。すべての分野ではない // サポートされています。 'field'が次のフィールドを選択しようとするとエラーが返されます。 // サポートされていません。 「resourceVersion」は、ウォッチを次のタイミングで継続/開始することを可能にします。 // 特定のバージョン。 監視(ctx context.Context、オプション *metainternalversion.ListOptions) (watch.Interface、エラー) } 後続の処理では、Creater、Getter、Watcher インターフェースに従って対応するリクエスト ハンドラーが生成されます。これらについては後で詳細に分析します。 k8s の組み込みリソース ストレージはすべて etcd を使用するため、組み込みリソースのストレージは Store を通じて構築されます。 Store は /k8s.io/apiserver/pkg/registry/generic/registry/store.go ファイルで定義されており、Creater、Getter、Watcher などのインターフェースが実装されています。他のリソースでは、ストレージ レイヤーのインタラクション コードを記述せずに、Store を初期化するときにいくつかの必須パラメーターを渡すだけで済みます。以下はデプロイメント ストアを構築するプロセスです。他の組み込みリソースも同様です。 // NewREST はデプロイメントに対して機能する RESTStorage オブジェクトを返します。 func NewREST(optsGetter generic.RESTOptionsGetter) (*REST、*StatusREST、*RollbackREST、error) { // デプロイメント用の genericregistry.Store を作成する ストア:= &genericregistry.Store{ // 空のリソース オブジェクトを初期化します。ここでは内部バージョンが使用されます。以下に定義されるさまざまな戦略操作のオブジェクトも内部バージョンであるため、バージョンごとに戦略を記述する必要はありません。 NewFunc: func() ランタイム.Object { return &apps.Deployment{} }, // 空のリソースオブジェクトリストを初期化する NewListFunc: func() ランタイム.Object { return &apps.DeploymentList{} },
デフォルトの修飾リソース: apps.Resource("deployments"), // 主にユーザーが上書きできないフィールドをチェックして制御するための更新および削除戦略を作成します CreateStrategy: デプロイメント戦略、 更新戦略: 展開戦略、 削除戦略: デプロイメント戦略、 ResetFieldsStrategy: デプロイメント戦略、
テーブルコンバーター: プリンターストレージ.TableConvertor{テーブルジェネレーター: プリンター.NewTableGenerator().With(プリンター内部.AddHandlers)}, } オプション := &generic.StoreOptions{RESTOptions: optsGetter} // store.Storage プロパティの初期化など、他のストア プロパティの初期化を続行します。ストレージは主に、基盤となるストレージ層とのやり取りに使用されます。 エラーの場合:= store.CompleteWithOptions(options);エラー != ゼロ { nil、nil、nil、err を返す }
ステータスストア:= *ストア // デプロイメントのステータス サブリソースもストアを使用しますが、更新戦略は異なります。つまり、更新時に、古いオブジェクトの仕様とラベルを使用して新しいオブジェクトを上書きし、ステータス以外のフィールドがユーザーによって誤って上書きされるのを防ぎます。 statusStore.UpdateStrategy = デプロイメント.StatusStrategy statusStore.ResetFieldsStrategy = デプロイメント.StatusStrategy &REST{store}、&StatusREST{store: &statusStore}、&RollbackREST{store: store}、nil を返します。 } InstallAPIs 呼び出しチェーンは非常に深いです。図 1 を参照すると、最終的に k8s.io/apiserver/pkg/endpoints/groupversion.go の InstallREST メソッドに到達します。 InstallREST メソッドは、ハンドラー プレフィックスを構築し、APIInstaller を作成してから、installer.Install() メソッドを呼び出してハンドラーの登録を続行します。 // k8s.io/apiserver/pkg/endpoints/groupversion.go
func (g *APIGroupVersion) InstallREST(コンテナ *restful.Container) ([]*storageversion.ResourceInfo, error) { // InstallAPI 呼び出しチェーンから、ここでの g.Root は /apis なので、ハンドラーのプレフィックスは /apis/{goup}/{version} であると判断できます。 プレフィックス := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version) インストーラー := &APIInstaller{ グループ: g, 接頭辞: 接頭辞、 最小リクエストタイムアウト: g.MinRequestTimeout、 }
apiResources、resourceInfos、ws、registrationErrors := installer.Install() バージョン検出ハンドラー:=検出.NewAPIVersionHandler(g.Serializer、g.GroupVersion、staticLister{apiResources}) バージョン検出ハンドラ.WebService に追加(ws) コンテナ.Add(ws) removeNonPersistedResources(resourceInfos)、utilerrors.NewAggregate(registrationErrors) を返します。 } installer.Install() メソッドは、registerResourceHandlers メソッドを呼び出して、リクエストを処理するためのハンドラーを実際に作成して登録します。 a.group.Storage は、前述の VersionedResourcesStorageMap にバージョン番号を渡すことによって取得されるマップであることに注意してください。読者は分析のために図 1 の呼び出しチェーンを参照できます。 a.registerResourceHandlersは、各タイプのストレージのハンドラーを登録します。 //API リソースのハンドラーをインストールします。 func (a *APIInstaller) Install() ([]metav1.APIResource、[]*storageversion.ResourceInfo、*restful.WebService、[]error) { var apiResources []metav1.APIリソース var resourceInfos []*ストレージバージョン.ResourceInfo var エラー [] エラー ws := a.newWebService()
// 決定論的な Swagger 仕様を取得するには、パスを決定論的な (ソートされた) 順序で登録します。 パス:= make([]文字列、len(a.group.Storage)) var i int = 0 // a.goup.Storageのシグネチャはmap[string]Storageであり、forループのパスはマップのキー、つまりリソース名です。 パス:=範囲a.group.Storage{ パス[i] = パス 私は++ } ソート.文字列(パス) _の場合、パス:=範囲パス{ apiResource、resourceInfo、err := a.registerResourceHandlers(path、a.group.Storage[path]、ws) ... } registerResourceHandlers は、rest.Storage によって実装されたインターフェースに基づいて関連するアクションを生成します。最後に、アクションに応じてハンドラーが生成され、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 のプレフィックス プロパティです。 InstallAPI 呼び出しチェーンからの値は /apis/{goup}/{version} です。 ws := a.newWebService() ... // 現在のストレージがサポートする操作の種類を決定するために型変換を実行します 作成者、isCreater := storage.(rest.Creater) namedCreater、isNamedCreater := storage.(rest.NamedCreater) リストア、isLister := storage.(rest.Lister) ゲッター、isGetter := storage.(rest.Getter) getterWithOptions、isGetterWithOptions := storage.(rest.GetterWithOptions) gracefulDeleter、isGracefulDeleter := storage.(rest.GracefulDeleter) コレクション削除子、isCollectionDeleter := storage.(rest.CollectionDeleter) アップデータ、isUpdater := storage.(rest.Updater) パッチ、isPatcher := storage.(rest.Patcher) ウォッチャー、isWatcher := storage.(rest.Watcher) コネクタ、isConnecter := storage.(rest.Connecter) storageMeta、isMetadata := storage.(rest.StorageMetadata) storageVersionProvider、isStorageVersionProvider := storage.(rest.StorageVersionProvider)
// 指定されたスコープのアクションのリストを取得します。 スイッチ{ case !namespaceScoped: // 名前空間リソースの有無にかかわらずアクションを構築します // ノードのような非名前空間スコープのリソースを処理します。 ... デフォルト: // 名前空間リソースを使用してアクションを構築する //ハンドラの登録パスを構築します namespaceParamName := "名前空間" // 標準の REST 動詞 (GET、PUT、POST、DELETE) のハンドラー。 namespaceParam := ws.PathParameter("namespace", "チームやプロジェクトなどのオブジェクト名と認証スコープ").DataType("string") namespacedPath := namespaceParamName + "/{namespace}/" + リソース 名前空間パラメータ:= []*restful.Parameter{名前空間パラメータ}
//resourcePath の値は /namespaces/{namespace}/{resource} です リソースパス:=名前空間パス リソースパラメータ:=名前空間パラメータ // アイテムパス: /namespaces/{名前空間}/{リソース}/{名前} // name は要求されたリソースオブジェクトの名前です itemPath := namespacedPath + "/{name}" nameParams := append(名前空間Params, nameParam) proxyParams := append(nameParams, pathParam) アイテムパスサフィックス:= "" サブリソースの場合{ itemPathSuffix = "/" + サブリソース // サブリソースがある場合、resourcePath は次のように定義されます: /namespaces/{namespace}/{resource}/{name}/{subResource} アイテムパス = アイテムパス + アイテムパスサフィックス // itemPathの値はresourcePathと同じです リソースパス = アイテムパス リソースパラメータ = 名前パラメータ } apiResource.Name = パス apiResource.Namespaced = true apiResource.Kind = リソースの種類 ネーマー:=ハンドラー.ContextBasedNaming{ 命名者: a.group.Namer、 クラスタースコープ: false、 } // ストレージによって実装されたインターフェースに従って関連するアクションを追加します アクション = appendIf(アクション、アクション{"LIST"、リソースパス、リソースパラム、ネーム、false}、isLister) アクション = appendIf(アクション、アクション{"POST"、リソースパス、リソースパラム、ネーム、false}、isCreater) アクション = appendIf(アクション、アクション{"DELETECOLLECTION"、リソースパス、リソースパラム、ネーム、false}、isCollectionDeleter) // 1.11 では非推奨 アクション = appendIf(アクション、アクション{"WATCHLIST"、"watch/" + resourcePath、resourceParams、namer、false}、allowWatchList)
アクション = appendIf(アクション、アクション{"GET"、itemPath、nameParams、namer、false}、isGetter) getSubpath {の場合 アクション = appendIf(アクション、アクション{"GET"、itemPath + "/{path:*}"、proxyParams、namer、false}、isGetter) } アクション = appendIf(アクション、アクション{"PUT"、itemPath、nameParams、namer、false}、isUpdater) アクション = appendIf(アクション、アクション{"PATCH"、itemPath、nameParams、namer、false}、isPatcher) アクション = appendIf(アクション、アクション{"DELETE"、itemPath、nameParams、namer、false}、isGracefulDeleter) // 1.11 では非推奨 アクション = appendIf(アクション、アクション{"WATCH"、"watch/" + itemPath、nameParams、namer、false}、isWatcher) アクション = appendIf(アクション、アクション{"CONNECT"、itemPath、nameParams、namer、false}、isConnecter) アクション = appendIf(アクション、アクション{"CONNECT"、itemPath + "/{path:*}"、proxyParams、namer、false}、isConnecter && connectSubpath)
// 名前空間全体にリストまたは投稿します。 // たとえば、/api/apiVersion/pods で LIST リクエストを送信して、すべての名前空間内のすべてのポッドを一覧表示します。 // TODO: リソースが「すべての名前空間」でこれらのアクションを許可するかどうかをより厳密に指定します (一括削除) !isSubresourceの場合{ アクション = appendIf(アクション、アクション{"LIST"、リソース、パラメータ、ネーム、true}、isLister) // 1.11 では非推奨 アクション = appendIf(アクション、アクション{"WATCHLIST", "watch/" + リソース、パラメータ、ネーム、true}、allowWatchList) } } ... _の場合、アクション:=範囲アクション{ ... スイッチアクション。動詞 { case "GET": // リソースを取得します。 var ハンドラーrestful.RouteFunction // 取得リクエストのハンドラを構築します // restfulGetResourceWithOptions と restfulGetResource は、handlers.GetResource 関数をハンドラーの関数シグネチャである restful.RouteFunction に変換します。 isGetterWithOptionsの場合{ ハンドラー = restfulGetResourceWithOptions(getterWithOptions、reqScope、isSubresource) } それ以外 { ハンドラー = restfulGetResource(ゲッター、reqScope) } ... // RESTコンテナにハンドラを登録する // action.Path は上記で定義された itemPath または resourcePath です。GET の場合は itemPath です。 // 現在登録されているハンドラーのパスは、ws plus ation.Path のルート パスです。完全なパスは次のとおりです: /apis/{goup}/{version}/namespaces/{namespace}/{resource}/{name} ルート:=ws.GET(action.Path).To(ハンドラ). ドク(doc)。 Param(ws.QueryParameter("pretty", "'true' の場合、出力はきれいに印刷されます。"))。 操作("read"+namespaced+kind+strings.Title(サブリソース)+operationSuffix)。 (append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...) を生成します。 (http.StatusOK、"OK"、producedObject) を返します。 書き込み(生成されたオブジェクト) isGetterWithOptionsの場合{ エラーの場合:= AddObjectParams(ws, route, versionedGetOptions);エラー != ゼロ { nil、nil、err を返す } } addParams(ルート、アクション.Params) ルート = append(ルート、ルート) } case "LIST": // 特定の種類のリソースをすべて一覧表示します。 ... case "PUT": // リソースを更新します。 ... case "PATCH": // リソースを部分的に更新する ... case "POST": // リソースを作成します。 ... case "DELETE": // リソースを削除します。 .... } ...
} registerResourceHandlers で作成されたハンドラーは、Creater や Updater などのインターフェースで定義されたメソッドを直接呼び出すのではなく、オブジェクトのエンコードとデコード、アドミッション コントロール処理ロジック、watch などの長いリンクのプロトコル処理などの追加処理を実行するために、外部にコード レイヤーをラップします。関連する定義は、k8s.io/apiserver/pkg/endpoints/handlers パッケージにあります。この記事では、Get と Create を例として使用して、要求処理ロジックを分析します。 Get リクエストの処理は比較的簡単です。 Metav1.GetOptions はリクエストのクエリ文字列を通じて構築され、処理のために Getter インターフェイスに渡されます。最後に、クエリ結果が変換され、要求者に送り返されます。 // k8s.io/apiserver/pkg/endpoints/handlers/get.go
// GetResource は、rest.Storage オブジェクトから単一のリソースを取得する関数を返します。 getResource()関数(r rest.Getter, スコープ *RequestScope) http.HandlerFunc { getResourceHandler(スコープ、 func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) { // エクスポートをチェック オプション:= metav1.GetOptions{} // クエリ文字列を取得する 値の場合 := req.URL.Query(); len(値) > 0 { ... // クエリ文字列をmetav1.GetOptionsにデコードします エラーの場合:= metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options);エラー != ゼロ { エラー = errors.NewBadRequest(err.Error()) nil、エラーを返す } } トレースがnilの場合{ trace.Step("ストレージから取得しようとしています") } // Getterインターフェースによって処理される r.Get(ctx, name, &options) を返します }) }
// getResourceHandler は、get リクエストの HTTP ハンドラー関数です。それは、 // 実際の取得を実行するために getterFunc が渡されます。 func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc { 戻り値 func(w http.ResponseWriter, req *http.Request) { ... 名前空間、名前、エラー:= scope.Namer.Name(req) ... ctx := req.Context() ctx = リクエスト.WithNamespace(ctx, 名前空間) ... 結果、エラー:= getter(ctx、名前、req、トレース) ... //処理結果をユーザーが期待する形式に変換し、レスポンスに書き込んでユーザーに返します transformResponseObject(ctx、スコープ、トレース、req、w、http.StatusOK、outputMediaType、結果) } } Create の処理ロジックは createHandler にあります。コードは比較的長く、主に次のことを行います。 - クエリ文字列をデコードして metav1.CreateOptions を生成します。
- リクエスト本文のデータをデコードし、リソース オブジェクトを生成します。デコードされたオブジェクト バージョンは内部バージョンであり、リソース オブジェクトのすべてのバージョン フィールドの完全なセットです。同じコードを使用して、異なるバージョンのオブジェクトを処理できます。
- オブジェクトを変更する必要があるかどうかを判断するためのオブジェクトを変更するためのアクセス制御。
- 作成者インターフェイスに送信してリソース オブジェクトを作成します。
- データを期待される形式に変換し、応答に書き込みます。作成者インターフェイスを呼び出すことによって返される結果は、依然として内部バージョンです。エンコード時には、ユーザーが要求したバージョンにエンコードされ、ユーザーに返されます。
// k8s.io/apiserver/pkg/endpoints/handlers/create.go
// CreateNamedResource は、名前付きのリソース作成を処理する関数を返します。 func CreateNamedResource(r rest.NamedCreater, スコープ *RequestScope, admission admission.Interface) http.HandlerFunc { createHandler(r, スコープ, 入場, true) を返します }
func createHandler(r rest.NamedCreater, スコープ *RequestScope, 承認 admission.Interface, includeName bool) http.HandlerFunc { 戻り値 func(w http.ResponseWriter, req *http.Request) { ... // リクエストからリクエストボディを取得する 本文、エラー:= limitedReadBody(req、scope.MaxRequestBodyBytes) ... // クエリをデコードして metav1.CreateOptions を生成する オプション:= &metav1.CreateOptions{} 値 := req.URL.Query() エラーの場合:= metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options);エラー != ゼロ { ... } // リクエスト本体をリソース オブジェクトにデコードします。 defaultGVK はユーザーが要求したバージョンです。ここでデコーダーによってデコードされるオブジェクトは、内部バージョン オブジェクトです。 obj、gvk、err := デコーダー.Decode(body、&defaultGVK、オリジナル) ... 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) { r.Create() を返す ctx、 名前、 オブジェクト、 rest.AdmissionToValidateObjectFunc(許可、admissionAttributes、スコープ)、 オプション、 ) } // 管理フィールドを更新する前に所有者参照を重複排除する dedupOwnerReferencesAndAddWarning(obj, req.Context(), false) 結果、エラー:=finisher.FinishRequest(ctx, func() (runtime.Object, error) { ... // 変異の受付操作を実行します。つまり、オブジェクトが作成された時点でオブジェクトを変更します。 // admin は buildGenericConfig で初期化され、config 経由で genericsever に渡され、その後ここに渡されます mutatingAdmission の場合、ok := admission.(admission.MutationInterface); OK && mutatingAdmission.Handles(admission.Create) { エラーの場合:= mutatingAdmission.Admit(ctx, admissionAttributes, scope);エラー != ゼロ { nil、エラーを返す } } // ミューテーションアドミッションが発生した後、所有者参照を再度重複排除します dedupOwnerReferencesAndAddWarning(obj, req.Context(), true) //createメソッドを呼び出す 結果、エラー:= requestFunc() ... 結果を返す、エラー }) ... //resutl も内部バージョンオブジェクトであり、transformResponseObject はそれをユーザーが要求したバージョンに変換して出力します。 transformResponseObject(ctx、スコープ、トレース、req、w、コード、出力メディアタイプ、結果) } 作成リクエストのプロセスは次のように要約できます。 図3 作成リクエスト処理フロー 要約するこの記事では、K8s 組み込みリソースの登録プロセスを紹介します。 APIServer へのアクセスは、まずフィルターを通過し、次に特定のハンドラーにルーティングされます。フィルターは DefaultBuildHandlerChain で定義されており、主にリクエストのタイムアウト処理、認証、承認を実行します。ハンドラーの登録は、APIGoupInfo を初期化し、その VersionedResourcesStorageMap を入力パラメータとして設定し、GenericAPIServer.InstallAPIGroups を呼び出してハンドラーの登録を完了することです。 k8s.io/apiserver/pkg/endpoints/handlers パッケージ内のコードは、ユーザー リクエストをエンコードおよびデコードし、オブジェクト バージョンを変換し、プロトコルを処理し、最後にそれらを rest.Storage の特定の実装インターフェースに渡して処理します。 参照する• https://blog.tianfeiyu.com/source-code-reading-notes/kubernetes/kube_apiserver.html#kube-apiserver-processing flow[1] • https://hackerain.me/2020/10/05/kubernetes/kube-apiserver-genericapiserver.html • https://hackerin.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 |