APIServer ソースコード分析ルーティング登録

APIServer ソースコード分析ルーティング登録

Kube APIServer と go-restful のエントリ ポイントの基本を理解したので、APIExtensionServer がどのようにインスタンス化されるかを理解し始めることができます。

API拡張サーバー

APIExtensionServer の作成プロセスには、通常、次の手順が含まれます。

  • GeneriAPIServerを作成する
  • CustomResourceDefinitions のインスタンス化
  • APIGroupInfo をインスタンス化する
  • インストールAPIグループ

これら 3 種類のサーバーすべての最下層は、GeneriAPIServer に依存する必要があります。 2 番目の手順で作成された CustomResourceDefinitions は、現在のタイプの Server オブジェクトであり、後続のルーティング登録に使用されます。 APIGroupInfo は、各バージョンおよび各リソース タイプに対応するストレージ オブジェクトです。最後に、 ​InstallAPIGroup​を呼び出してルートを登録し、各リソースのバージョンとタイプを URI アドレスにマッピングします。コードは次のようになります。

 // cmd/kube-apiserver/app/apiextensions.go
func createAPIExtensionsServer ( apiextensionsConfig * apiextensionsapiserver.Config , delegateAPIServer genericapiserver.DelegationTarget ) ( * apiextensionsapiserver.CustomResourceDefinitions , error ) {
apiextensionsConfigを返します完了() 。新規( delegateAPIServer )
}

// 実際のコードは次の場所にあります: /vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go
// New は指定された構成から CustomResourceDefinitions の新しいインスタンスを返します
func ( c CompletedConfig ) New ( delegationTarget genericapiserver.DelegationTarget ) ( * CustomResourceDefinitions , error ) {
// APIExtensionsServer は GenericAPIServer に依存します

// GenericConfig を通じて apiextensions-apiserver という名前の genericServer を作成します
genericServerエラー: = c汎用構成新規( "apiextensions-apiserver"delegationTarget )


// CustomResourceDefinitions オブジェクトをインスタンス化します
s : = &カスタムリソース定義{
GenericAPIServer : genericServer
}

apiResourceConfig : = cです。汎用構成マージされたリソース構成

// APIGroupInfo オブジェクトをインスタンス化します。 APIGroup は、サーバーが公開する必要がある API を参照します。
apiGroupInfo : = genericapiserverNewDefaultAPIGroupInfo ( apiextensions . GroupName , Scheme , metav1 . ParameterCodec , Codecs )

// v1 が有効な場合は、リソース バージョン、リソース、およびリソース ストレージを APIGroupInfo マップに保存します。
apiResourceConfigの場合バージョンが有効( v1 . SchemeGroupVersion ) {
ストレージ: = map [文字列] rest .ストレージ{}
customResourceDefinitionStorageエラー: = customresourcedefinitionNewREST ( Schemec . GenericConfig . RESTOptionsGetter )

ストレージ[ "customresourcedefinitions" ] = customResourceDefinitionStorage
ストレージ[ "customresourcedefinitions/status" ] = customresourcedefinitionNewStatusREST (スキームカスタムリソース定義ストレージ)

apiGroupInfoバージョン付きリソースストレージマップ[ v1 .スキームグループバージョンバージョン] =ストレージ
}

//登録API
err : = sの場合汎用APIサーバーAPIGroup をインストールします( & apiGroupInfo );エラー!=ゼロ{
nilまたはerrを返す
}

// コントローラを初期化するために、CRD クライアントセットとインフォーマーを初期化します
crdClientエラー: = clientsetNewForConfig ( s . GenericAPIServer . LoopbackClientConfig )
s情報提供者=外部情報提供者NewSharedInformerFactory ( crdClient5 *時間.)

delegateHandler : = delegationTarget保護されていないハンドラ()
delegateHandler == nilの場合{
delegateHandler = http見つからないハンドラ()
}

バージョン検出ハンドラ: = &バージョン検出ハンドラ{
検出:マップ[スキーマ. GroupVersion ] *検出APIバージョンハンドラー{},
デリゲート: delegateHandler
}
グループディスカバリハンドラ: = &グループディスカバリハンドラ{
検出:マップ[文字列] *検出APIグループハンドラ{},
デリゲート: delegateHandler
}
// コントローラーを初期化する
コントローラを確立します: =確立しますNewEstablishingController ( s . Informers . Apiextensions () . V1 () . CustomResourceDefinitions (), crdClient . ApiextensionsV1 ())
// ハンドラーを申請する
crdHandlerエラー: = NewCustomResourceDefinitionHandler (
バージョン検出ハンドラ
グループ検出ハンドラ
s情報提供者API拡張機能()。 V1 ()。カスタムリソース定義()、
// ......

s汎用APIサーバーハンドラー非GoRestfulMuxハンドル( "/apis"crdHandler )
s汎用APIサーバーハンドラー非GoRestfulMuxハンドルプレフィックス( "/apis/"crdHandler )
// 他のコントローラーを初期化する
discoveryController : = NewDiscoveryController ( s . Informers . Apiextensions () . V1 () . CustomResourceDefinitions ()、 versionDiscoveryHandlergroupDiscoveryHandler )
namingController : =ステータスNewNamingConditionController ( s . Informers . Apiextensions () . V1 () . CustomResourceDefinitions (), crdClient . ApiextensionsV1 ())
nonStructuralSchemaController : =非構造スキーマNewConditionController ( s . Informers . Apiextensions () . V1 () . CustomResourceDefinitions (), crdClient . ApiextensionsV1 ())
apiApprovalController : = apiapproval新しいKubernetesAPIApprovalPolicyConformantConditionController ( s . Informers . Apiextensions () . V1 () . CustomResourceDefinitions (), crdClient . ApiextensionsV1 ())
ファイナライズコントローラ: =ファイナライザー新しいCRDFinalizer (
s情報提供者API拡張機能()。 V1 ()。カスタムリソース定義()、
crdClient.ApiextensionsV1 ()
crdHandler

// openapi コントローラーを初期化する
openapiController : = openapicontrollerNewController ( s.Informers.Apiextensions (). V1 (). CustomResourceDefinitions ( ) )
var openapiv3Controller * openapiv3controllerコントローラ
utilfeature の場合デフォルトのフィーチャゲート有効(機能. OpenAPIV3 ) {
.openapiv3ControllerオーバーライドしますNewController ( s.Informers.Apiextensions (). V1 (). CustomResourceDefinitions ( ) )
}
// PostStartHook にインフォーマーとコントローラーを追加する
s汎用APIサーバーAddPostStartHookOrDie ( "start-apiextensions-informers"func ( context genericapiserver . PostStartHookContext )エラー{
s情報提供者開始(コンテキスト. StopCh )
ゼロを返す
})
// フック関数を登録し、先ほどインスタンス化したさまざまなコントローラを起動します
s汎用APIサーバーAddPostStartHookOrDie ( "start-apiextensions-controllers"func ( context genericapiserver . PostStartHookContext )エラー{

sの場合汎用APIサーバーOpenAPIVersionedService != nil && s汎用APIサーバーStaticOpenAPISpec != nil {
openapiController に移動します実行( s.GenericAPIServer.StaticOpenAPISpec s.GenericAPIServer.OpenAPIVersionedService context.StopCh )
utilfeature の場合デフォルトのフィーチャゲート有効(機能. OpenAPIV3 ) {
openapiv3Controller に移動します実行( s.GenericAPIServer.OpenAPIV3VersionedService context.StopCh )
}
}
// 各種コントローラーを起動する
コントローラに名前を付けます実行(コンテキスト.StopCh )
コントローラを確立します実行(コンテキスト.StopCh )
nonStructuralSchemaControllerに移動します実行( 5コンテキスト. StopCh )
apiApprovalControllerに移動します実行( 5コンテキスト. StopCh )
コントローラを終了させます実行( 5コンテキスト. StopCh )

discoverySyncedCh : = make ( chan構造体{})
探索コントローラに移動します実行(コンテキスト.StopCh , discoverySyncedCh )
選択{
ケース<-コンテキストストップCh :
ケース<- discoverySyncedCh :
}

ゼロを返す
})
// ....

snilを返す
}

まず、GenericConfig を使用して apiextensions-apiserver という名前の genericServer を作成します。 GenericServer は汎用 HTTP サーバーを提供し、アドレス、ポート、認証、承認、ヘルス チェック、その他の一般的な機能などの共通テンプレートを定義します。 APIServer と APIExtensionsServer はどちらも genericServer に依存しており、実装は次のようになります。

 // ベンダー/k8s.io/apiserver/pkg/server/config.go
// New は、渡されたサーバーと処理チェーンを論理的に結合する新しいサーバーを作成します。
// 名前はログレコードを区別するために使用されます
func ( c CompletedConfig ) New ( name文字列delegationTarget DelegationTarget ) ( * GenericAPIServererror ) {
// ...

handlerChainBuilder : = func ( handler http.Handler ) http .ハンドラ{
// BuildHandlerChainFunc を使用すると、apiHandler を装飾してカスタム ハンドラー チェーンを構築できます。
// 現在のデフォルトの処理チェーン関数は DefaultBuildHandlerChain です。これには多くのデフォルトの処理メソッドが含まれています
cを返しますBuildHandlerChainFunc (ハンドラc.Config )
}

apiServerHandler : = NewAPIServerHandler ( name , c.Serializer , handlerChainBuilder , delegationTarget.UnprotectedHandler ( ) )

s : = & GenericAPIServer {

ハンドラー: apiServerHandler

リストされたパスプロバイダー: apiServerHandler

// ......
}

// ......
// すべてのインターフェース、メトリック インターフェースなどのインデックス作成など、いくつかの追加ルートをインストールします。
installAPI ( scConfig )

snilを返す
}

// ベンダー/k8s.io/apiserver/pkg/server/handler.go
// HandlerChainBuilderFn は、提供されたハンドラー チェーンを使用している GoRestfulContainer ハンドラーをラップするために使用されます。
// 通常は認証や承認などのアプリケーションフィルタリングに使用されます
タイプHandlerChainBuilderFn func ( apiHandler http . Handler ) http .ハンドラ

// この関数は、go-restfulモードに従ってコンテナを初期化するために使用されます
func NewAPIServerHandler ( name stringsランタイム.NegotiatedSerializerhandlerChainBuilder HandlerChainBuilderFnnotFoundHandler http .Handler ) * APIServerHandler {
nonGoRestfulMux : = muxです。 NewPathRecorderMux (名前)
notFoundHandler != nil の場合{
非GoRestfulMuxNotFoundHandler ( notFoundHandler )
}

gorestfulContainer : =安らかに新しいコンテナ()
gorestfulコンテナサーブMux = http新しいサーブマルチプレックス()
gorestfulコンテナRouter ( restful . CurlyRouter {}) // 例: proxy/{kind}/{name}/{*}
gorestfulコンテナRecoverHandler ( func ( panicReasonインターフェース{}, httpWriter http . ResponseWriter ) {
logStackOnRecover ( spanicReasonhttpWriter ) のログ
})
gorestfulコンテナServiceErrorHandler ( func ( serviceErr restful.ServiceError , request * restful.Request , response * restful.Response ) {
serviceErrorHandler ( sserviceErrrequestresponse )
})

ディレクター: =ディレクター{
名前:名前
goRestfulContainer : gorestfulContainer
非GoRestfulMux :非GoRestfulMux
}

戻り値& APIServerHandler {
FullHandlerChain : handlerChainBuilder (ディレクター)、
GoRestfulContainer : gorestfulContainer
非GoRestfulMux :非GoRestfulMux
監督監督
}
}

次に、CRD と APIGroupInfo をインスタンス化します。 APIGroupInfo オブジェクトは、リソース グループ情報を記述するために使用されます。 1 つのリソースは 1 つの APIGroupInfo オブジェクトに対応し、各リソースは 1 つのリソース ストレージ オブジェクトに対応します。

 // /vendor/k8s.io/apiextensions-apiserver/pkg/server/genericapiserver.go
func NewDefaultAPIGroupInfo (グループ文字列スキーム*ランタイム.Scheme、パラメーターコードランタイム.ParameterCodec、コーデックシリアライザー.CodecFactory ) APIGroupInfo {
APIGroupInfoを返す{
PrioritizedVersions :スキームPrioritizedVersionsForGroup (グループ)、
// このマップは、リソースとリソース ストレージ オブジェクト間のマッピング関係を格納するために使用されます
// 形式: リソース バージョン/リソース/リソース ストレージ オブジェクト
// リソース CRUD を担当するリソース ストレージ オブジェクト RESTStorage
//その後、RESTStorageをhttpハンドラ関数に変換します
VersionedResourcesStorageMap : map [文字列] map [文字列] rest .ストレージ{}、

オプション外部バージョン: &スキーマグループバージョン{バージョン: "v1" },
スキームスキーム
パラメータコーデック:パラメータコーデック
NegotiatedSerializer :コーデック
}
}

次に、APIGroupInfo を登録する必要があります。これは、s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo) を通じて実行され、APIGroupInfo 内のリソース オブジェクトが APIExtensionServerHandler 関数に登録されます。プロセスは次のとおりです。

  • APIGroupInfo をトラバースします。
  • リソース グループ、リソース バージョン、およびリソース名を http パス要求パスにマップします。
  • InstallREST 関数を使用して、リソース オブジェクトをリソース ハンドラー メソッドとして保存します。
  • 最後に、go-restful の ws.Route を使用して、定義されたリクエスト パスとハンドラー メソッドを go-restful に追加します。

詳細なコードは次のとおりです。

 // /vendor/k8s.io/apiextensions-apiserver/pkg/server/genericapiserver.go

// 指定された APIGroup を API で公開します
func ( s * GenericAPIServer ) InstallAPIGroup ( apiGroupInfo * APIGroupInfo )エラー{
s.InstallAPIGroups ( apiGroupInfo )を返します
}

func ( s * GenericAPIServer ) InstallAPIGroups ( apiGroupInfos ... * APIGroupInfo )エラー{

// ...
// OpenAPI モデルを取得する
openAPIModelsエラー: = sgetOpenAPIModels ( APIGroupPrefixapiGroupInfos ...) を取得します。
// すべてのリソース情報を走査し、リソースバージョンプロセッサを一度インストールします
_の場合apiGroupInfo : =範囲apiGroupInfos {
err : = sの場合APIリソースをインストールします( APIGroupPrefixapiGroupInfoopenAPIModels )。エラー!=ゼロ{
fmtを返しますErrorf ( "API リソースをインストールできません: %v"err )
}

apiVersionsForDiscovery : = [] metav1検出用グループバージョン{}
// ...
apiGroup : = metav1です。 APIグループ{
名前: apiGroupInfo優先バージョン[ 0 ]。グループ
バージョン: apiVersionsForDiscovery
優先バージョン:優先バージョン検出
}

sディスカバリーグループマネージャーグループの追加( apiGroup )
sハンドラーGoRestfulContainer追加( discovery . NewAPIGroupHandler ( s . Serializer , apiGroup ). WebService ())
}
ゼロを返す
}

// installAPIResources は各 API グループバージョンリソースをサポートするために RESTStorage をインストールするために使用されます
func ( s * GenericAPIServer ) installAPIResources ( apiPrefix stringapiGroupInfo * APIGroupInfoopenAPIModels openapiproto . Models )エラー{
var resourceInfos [] * storageversionリソース情報
_の場合groupVersion : = range apiGroupInfo優先バージョン{

apiGroupVersion : = sAPIグループバージョンを取得します( apiGroupInfogroupVersionapiPrefix )
// ...
apiグループバージョンMaxRequestBodyBytes = s最大リクエストボディバイト数

// コアは InstallREST を呼び出すことです。パラメータは go-restful のコンテナ オブジェクトです。
rエラー: = apiGroupVersionInstallREST ( s . Handler . GoRestfulContainer )
// ...
resourceInfos = append ( resourceInfos , r ...)
}

// ...

ゼロを返す
}

// InstallREST は、REST ハンドラー (ストレージ、ウォッチ、プロキシ、リダイレクト) を RESTful コンテナーに登録します。
// 指定されたパスのルートプレフィックスはすべての操作に対応することが期待されており、ルートはスラッシュで終わることはできません。
func ( g * APIGroupVersion ) InstallREST ( container * restful.Container ) ([] * storageversion.ResourceInfo , error ) {
// たとえば、InstallAPI 呼び出しチェーンから、ここでの g.Root は /apis なので、ハンドラーのプレフィックスは /apis/{goup}/{version} であると判断できます。
プレフィックス: =パス結合( g.Root g.GroupVersion.Group g.GroupVersion.Version )
//APIInstallerオブジェクトをインスタンス化する
インストーラー: = & APIInstaller {
グループ: g
接頭辞:接頭辞
minRequestTimeout : g最小リクエストタイムアウト
}
// Install関数を呼び出します。APIを登録し、go-restful WebServiceオブジェクトを返します。
apiResourcesresourceInfoswsregistrationErrors : =インストーラーインストール()
バージョンDiscoveryHandler : =検出新しいAPIVersionHandler ( g.Serializer g.GroupVersionstaticLister { apiResources })
バージョンDiscoveryHandler.AddToWebService (ws )
// コンテナに WebService を追加します。これには go-restful フレームワークの知識が必要です。
容器追加( ws )
removeNonPersistedResources ( resourceInfos )、 utilerrors を返します新しい集計(登録エラー)
}

上記のプロセス全体は、API を登録するための準備です。コアは installer.Install() 関数にあり、API リソースをプロセッサに追加するために使用されます。

 // /vendor/k8s.io/apiserver/pkg/endpoints/installer.go

// APIインストーラのプレフィックスとバージョンを使用して、新しいRESTful Webサービスオブジェクトを作成します
// この部分は go-restful フレームワークの使用法に属します
func ( a * APIInstaller ) newWebService () * restfulWebサービス{
ws : = new ( restful.WebService )追加します。
wsパス( .プレフィックス)
// a.prefix には「prefix/group/version」が含まれます
wsDoc ( 「API at」 + .プレフィックス)
ws消費する( "*/*" )
mediaTypesstreamMediaTypes : =ネゴシエーションMediaTypesForSerializer ( .グループ. Serializer )
ws 。 ( append ( mediaTypesstreamMediaTypes ...)...)を生成します
wsApiVersion ( a.group.GroupVersion.String ( ) )
WSを返す
}

func ( a * APIInstaller ) Install () ([] metav1 . APIResource , [] * storageversion . ResourceInfo , * restful . WebService , [] error ) {
var apiResources [] metav1APIリソース
var resourceInfos [] * storageversionリソース情報
var errors []エラー

// 新しい WebService オブジェクトを作成します (go-restful フレームワーク内)
ws : = aです。新しいWebサービス()

// パスをソートする
パス: = make ([]文字列, len ( a .グループ.ストレージ))
var i int = 0
パスの場合: =範囲aグループストレージ{
パス[ i ] =パス
私は++
}
選別文字列(パス)
// Swagger仕様を取得する
_の場合パス: =範囲パス{
// ストレージをルーターに変換し、ルートを Web サービスに登録します
apiResourceresourceInfoerr : = aregisterResourceHandlers (パスa .グループ.ストレージ[パス]、 ws )

apiResource != nil の場合{
apiResources =追加( apiResources* apiResource )
}
リソース情報!= nilの場合{
resourceInfos =追加( resourceInfosresourceInfo )
}
}
apiResourcesresourceInfoswserrorsを返します
}

ここで最も重要なのは、registerResourceHandlers 関数です。この方法は非常に長いです。そのコア機能は、ストレージに基づいてハンドラーを構築し、ハンドラーとパスを go-restful フレームワークの Route オブジェクトに構築し、最後に Route を Web サービスに追加することです。

 // /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 ) {
// ......

// 名前空間レベルですか?
var名前空間スコープ付きブール
// ......

// ストレージはどの動詞操作をサポートしていますか?これは、各パスでサポートされている操作を理解するために使用されます。
createrisCreater : = storage .( rest . Creater )
namedCreaterisNamedCreater : = storage .( rest . NamedCreater )
リストアisLister : =ストレージ. (rest.Lister )
getterisGetter : = storage .( rest . Getter )

// ......

// 指定された範囲の操作リストを取得します
スイッチ{
場合名前空間スコープ:
// ノードなどの名前空間スコープ外のリソースを処理する
// ......

// リソースパスにアクションを追加します: /api/apiVersion/resource
アクション= appendIf (アクションアクション{ "LIST"リソースパスリソースパラメータネームサーバーfalse }、 isLister )
アクション= appendIf (アクションアクション{ "POST"リソースパスリソースパラメータネームサーバーfalse }、 isCreater )
アクション= appendIf (アクションアクション{ "DELETECOLLECTION"リソースパスリソースパラメータネームサーバーfalse }、 isCollectionDeleter )
// ......
デフォルト
// 名前空間レベルのリソース オブジェクト
namespaceParamName : = "名前空間"
名前空間Param : = wsPathParameter ( "namespace""チームやプロジェクトなどのオブジェクト名と認証スコープ" )。データ型( "文字列" )
namespacedPath : = namespaceParamName + "/{namespace}/" +リソース
namespaceParams : = [] * restful ですパラメータ{ namespaceParam }
// ......
// アクションリストを構築
アクション= appendIf (アクションアクション{ "LIST"リソースパスリソースパラメータネームサーバーfalse }、 isLister )
アクション= appendIf (アクションアクション{ "POST"リソースパスリソースパラメータネームサーバーfalse }、 isCreater )
// ......

}

// アクションのルートを作成する

// go-restfulによって生成されたMIMEタイプを設定します
mediaTypesstreamMediaTypes : =ネゴシエーションMediaTypesForSerializer ( .グループ. Serializer )
allMediaTypes : = append ( mediaTypesstreamMediaTypes ...) を追加します。
ws.Produces (allMediaTypes ... )

// ...
_の場合アクション: =範囲アクション{
// ......

// Go-Restful RouteBuilder オブジェクトを構築します
ルート: = [] *安らかにルートビルダー{}

// サブリソースの場合、種類は現在の種類と同じである必要があります
サブリソースの場合{
parentStorageOK : = aグループストレージ[リソース]

fqParentKinderr : = GetResourceKind ( a.group.GroupVersionparentStorage a.group.Typer )

種類= fqParentKind親切
}

//異なる動詞に応じて異なるハンドラに登録する
スイッチアクション動詞{
ケース「GET」 :
varハンドラーRESTfulRouteFunction // go-restful ハンドラー
// ハンドラを初期化する
isGetterWithOptionsの場合{
ハンドラー= restfulGetResourceWithOptions ( getterWithOptionsreqScopeisSubresource )
}それ以外{
ハンドラー= restfulGetResource (ゲッターreqScope )
}

//...

// ルートを構築します(これは go-restful フレームワークを使用する方法です)
ルート: = wsGET (アクション.パス)。 (ハンドラー)
ドキュメントdoc )。
Param ( ws . QueryParameter ( "pretty" , "「true」の場合、出力はきれいに印刷されます。" ))。
操作( 「読み取り」 +名前空間+種類+文字列タイトル(サブリソース) +操作サフィックス)。
( append ( storageMeta . ProducesMIMETypes ( action . Verb ), mediaTypes ...)...)を生成します
( http . StatusOK"OK"producedObject )を返します
書き込み( producedObject )

// ルートにルートを追加する
addParams (ルートアクションParams )
ルート=追加(ルートルート)
// ...他の動詞処理方法は基本的に同じです
case "POST" : // リソースを作成します。
varハンドラーrestful .RouteFunction
isNamedCreaterの場合{
ハンドラー= restfulCreateNamedResource ( namedCreaterreqScopeadmission )
}それ以外{
ハンドラー= restfulCreateResource ( createrreqScopeadmission )
}
// ......
}

// ルートをトラバースしてWebServiceに追加します
_の場合ルート: =範囲ルート{
ルートメタデータ( ROUTE_META_GVKmetav1GroupVersionKind {
グループ: reqScope親切グループ
バージョン: reqScope親切バージョン
種類: reqScope親切親切
})
ルートメタデータ( ROUTE_META_ACTION文字列. ToLower (アクション. Verb ))
// WebService にルートを追加
wsルート(ルート)
}
}

// ......

戻り値& apiResource , resourceInfo , nil
}

registerResourceHandlers 関数は非常に長いですが、詳細は脇に置いて全体として理解することができます。まず、Storage を使用してサポートされている Verbs 操作を判別し、次にアクション リストを生成し、最後に各アクションのルート リストを構築することがわかります。最後に、これらのルートが go-restful WebService に追加されます。ここで構築されるプロセッサにバインドされるルートには、さまざまな実装方法があります。たとえば、GET ハンドラーは restfulGetResource を通じてインスタンス化され、POST ハンドラーは restfulCreateResource を通じてインスタンス化されます。実装方法は基本的に同じです。

GET メソッド ハンドラー関数 restfulGetResource の実装は次のとおりです。

 // /vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func restfulGetResource ( r rest.Getter ,スコープハンドラー.RequestScope ) restful .ルート関数{
戻りfunc ( req * restful.Request , res * restful.Response ) {
ハンドラーGetResource ( r& scope )( res . ResponseWriterreq . Request )
}
}

restfulGetResource 関数は、go-restful の方法で restful.RouteFunction を取得します。実際に処理されるのは、getResourceHandler を呼び出す​handlers.GetResource​関数です。この関数は、対応するルート要求を処理するための http 標準ライブラリ ハンドラー関数を返します。

GET リクエストの処理は比較的簡単です。要求されたクエリを通じて metav1.GetOptions が構築され、処理のために Getter インターフェイスに渡されます。最後に、クエリ結果が変換され、要求者に返されます。

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

// GetResource は、rest.Storage オブジェクトから単一のリソースを取得する関数を返します。
func GetResource ( r rest . Getterscope * RequestScope ) http .ハンドラ関数{
getResourceHandler (スコープ
func ( ctx context.Context , name string , req * http.Request , trace * utiltrace.Trace ) ( runtime.Object , error ) {
// 必要なGetOptionsを初期化する
オプション: = metav1オプションを取得{}
// クエリパラメータを取得する
値の場合= req.URL .クエリ(); len> 0 {
// ...
// クエリパラメータをデコードし、GetOptions オブジェクトをプログラムします
errの場合: = metainternalversionschemeパラメータコーデックDecodeParameters (スコープMetaGroupVersionおよびオプション);エラー!=ゼロ{
err =エラーNewBadRequest ( err . Error ())
nilまたはerrを返す
}
}
//次に、Getterインターフェースを使用して処理します
rを返します取得( ctxname& options )
})
}

// getResourceHandlerはリクエストを取得するために使用されるHTTPハンドラ関数です
// 渡されたgetterFuncに実際の取得の実行を委任します
function getResourceHandler ( scope * RequestScopegetter getterFunc ) httpハンドラ関数{
戻りfunc ( w http.ResponseWriterreq * http.Request ) {
// ...

名前空間名前エラー: =スコープネーマー名前(必須)

/// ...
ctx : =要求コンテクスト()
ctx =リクエスト. WithNamespace ( ctx名前空間)

outputMediaType_エラー=ネゴシエーションNegotiateOutputMediaType ( reqscopeSerializerscope )

// ...
// getterFuncを使用して実際の取得操作を実行します。
結果エラー: = getter ( ctxnamereqtrace )
// ...

//結果をユーザーが要求する形式に変換し、ユーザーに返します
transformResponseObject ( ctxscopetracereqwhttpStatusOKoutputMediaTyperesult )

}
}

POST ハンドラーも同様で、対応するロジックは restfulCreateResource にあります。

 // /vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func restfulCreateResource ( r rest.Creater ,スコープhandlers.RequestScope , admission.Interface ) restful .ルート関数{
戻りfunc ( req * restful.Request , res * restful.Response ) {
ハンドラーCreateResource ( r& scopeadmission )( res . ResponseWriterreq . Request )
}
}

実際にリクエストを処理する関数は、createHandler を呼び出す handlers.CreateResource です。この関数は、対応するルーティング要求を処理するための http 標準ライブラリ ハンドラー関数を返します。

 //vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go

// CreateNamedResource は、名前付きのリソース作成を処理する関数を返します。
func CreateNamedResource ( r rest.NamedCreater 、スコープ* RequestScope admission admission.Interface ) http .ハンドラ関数{
createHandler ( rスコープ入場true )を返します
}

// CreateResource はリソース作成を処理する関数を返します
func CreateResource ( r rest.Creater , scope * RequestScope , admission admission.Interface ) http .ハンドラ関数{
createHandler ( & namedCreaterAdapter { r },スコープアドミッションfalse )を返します
}

createHandler の実装コードは比較的長く、主に次のことを行います。

  1. クエリ文字列をデコードして metav1.CreateOptions を生成します。
  2. リクエスト本文のデータをデコードし、リソース オブジェクトを生成します。デコードされたオブジェクト バージョンは内部バージョンであり、リソース オブジェクトのすべてのバージョン フィールドの完全なセットです。同じコードを使用して、異なるバージョンのオブジェクトを処理できます。
  3. オブジェクトを変更する必要があるかどうかを判断するためのオブジェクトを変更するためのアクセス制御。
  4. 作成者インターフェイスに送信してリソース オブジェクトを作成します。
  5. データを期待される形式に変換し、応答に書き込みます。作成者インターフェイスを呼び出すことによって返される結果は、依然として内部バージョンです。エンコード時には、ユーザーが要求したバージョンにエンコードされ、ユーザーに返されます。
 //vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go

// 対応するルーティング要求を処理するための http ハンドラ関数を返します
func createHandler ( r rest . NamedCreaterスコープ* RequestScopeadmission . InterfaceincludeName bool ) http .ハンドラ関数{
//標準のhttpハンドラ関数
戻りfunc ( w http.ResponseWriterreq * http.Request ) {
// ...

outputMediaType_エラー=ネゴシエーションNegotiateOutputMediaType ( reqscopeSerializerscope )

// 適切なシリアライザーを見つける
gv : =スコープ親切グループバージョン()
serr=交渉NegotiateInputSerializer ( reqfalsescopeSerializer )

// リクエストをCreateOptionsにデコードする
オプション: = & metav1オプションの作成{}
​​: =必須.URL .クエリ()
errの場合: = metainternalversionschemeパラメータコーデックDecodeParametersスコープMetaGroupVersionオプション);エラー!=ゼロ{
// ...
}
// ...
オプションタイプメタSetGroupVersionKind ( metav1 . SchemeGroupVersion . WithKind ( "CreateOptions" ))

defaultGVK : =スコープ親切
オリジナル: = r新しい()

// ...

// 適切なデコーダーを見つける
デコーダー: =スコープシリアライザーDecoderToVersion ( decodeSerializerスコープ. HubGroupVersion )

// デコードするボディをリクエストしてください
objgvkerr : =デコーダーデコード( body defaultGVKoriginal )


ctx =リクエスト. WithNamespace ( ctx名前空間)
// 監査、承認、リクエストのログ記録
ae : =監査監査イベント元( ctx )
認める=入場監査付き(認めるae )
監査LogRequestObject ( req . Context ()、 objobjGVscope . Resourcescope . Subresourcescope . Serializer )

userInfo_ : =リクエストユーザーFrom ( ctx )

入場属性: =入場NewAttributesRecord ( objnilscope . Kindnamespacenamescope . Resourcescope . Subresourceadmission . Createoptionsdryrun . IsDryRun ( options . DryRun )、 userInfo )
requestFunc : = func () ( runtime.Object , error ) {
rを返します作成する
ctx
名前
オブジェクト
休むAdmissionToValidateObjectFunc (認める入場属性スコープ)、
オプション

}

// リクエストを処理する
結果エラー: =フィニッシャーFinishRequest ( ctx , func () (ランタイム. Object , error ) {
// ...
// アドミッションコントロールの変更操作を実行します。つまり、オブジェクトの作成時に変更します。
mutatingAdmissionの場合ok : = admission .( admission . MutationInterface ); ok && mutatingAdmissionハンドル入場.作成){
err の場合: = mutatingAdmission認める( ctxadmissionAttributesscope );エラー!=ゼロ{
nilまたはerrを返す
}
}
// ......
//createメソッドを呼び出す
結果エラー: = requestFunc ()
結果を返すエラー
})


コード: = http .ステータス作成済み
ステータスOK : =結果。( * metav1 .ステータス)
ok &&ステータスの場合コード== 0 {
状態コード= int32 (コード)
}
//結果をユーザーが要求する形式に変換し、ユーザーに返します
transformResponseObject ( ctxscopetracereqwcodeoutputMediaTyperesult ) は、
}
}

KubeAPIサーバー

KubeAPIServer は、組み込みリソース オブジェクトに対するリクエストを処理するために使用されるコア サーバーです。サーバーは次のように作成されます。

 kubeAPIServer エラー: = CreateKubeAPIServer ( kubeAPIServerConfigapiExtensionsServer。GenericAPIServer )

kubeAPIServerConfig は、汎用構成の生成を紹介する際に上記で紹介しました。次のパラメータ apiExtensionsServer.GenericAPIServer は、以前の APIExtensionServer によって生成された汎用 APIServer です。 KubeAPIServer もこのサーバーに依存するため、直接使用されます。

 // cmd/kube-apiserver/app/server.go

func CreateKubeAPIServer ( kubeAPIServerConfig * controlplane.Config , delegateAPIServer genericapiserver.DelegationTarget ) ( * controlplane.Instance , error ) {
kubeAPIServerエラー: = kubeAPIServerConfig完了()。新規( delegateAPIServer )

kubeAPIServerを返すnil
}

同様に、CreateKubeAPIServer 関数は、kubeAPIServerConfig オブジェクトにデフォルト オブジェクトを直接入力し、インスタンス化します。

 // pkg/コントロールプレーン/インスタンス.go
// New は指定された構成から新しい Master インスタンスを返します。
// 設定されていない場合、一部の構成フィールドはデフォルト値に設定されます。
// KubeltClientConfig など、特定の構成フィールドを指定する必要があります。
func ( c CompletedConfig ) New ( delegationTarget genericapiserver.DelegationTarget ) ( *インスタンスエラー) {

// ここでは、crd サーバーと同様に、GenericConfig が呼び出され、kube-apiserver という名前のサーバーがインスタンス化されます。
sエラー: = c汎用構成新規( "kube-apiserver"delegationTarget )
//ログルーティングのサポートを構成する
cの場合追加構成ログサポートを有効にする{
ルート。 {}をログに記録しますインストール( s . Handler . GoRestfulContainer )
}

// ......

m : = &インスタンス{
GenericAPIServer : s
クラスター認証情報: c追加構成クラスター認証情報
}

// レガシーAPIをインストール
cの場合追加構成APIリソース構成ソースバージョンが有効( apiv1 . SchemeGroupVersion ) {
legacyRESTStorageProvider : = corerestレガシーRESTStorageProvider {
ストレージファクトリー: c .追加構成ストレージファクトリー
// ......
}
// restStorageProvider が有効になっている場合、InstallLegacyAPI はそれ用のレガシー API をインストールします
err : = m の場合InstallLegacyAPI ( & c , c . GenericConfig . RESTOptionsGetter , LegacyRESTStorageProvider );エラー!=ゼロ{
nilまたはerrを返す
}
}

// 同じ名前のリソースが複数のグループに存在する場合(例:"deployments.apps""と"deployments.extensions")、
//このリストの順序は、どちらが優先されるかを決定します(例:「展開」)。
//この優先順位はローカルディスカバリーに使用されますが、最終的には `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go`に特定の優先順位があります。

RestStorageProviders= [] RestStorageProvider {
ApiserverinternalrestStorageProvider {}、
AuthenticationRestRestStorageProvider { Authenticatorcgenericconfig認証AuthenticatorApiaudiencescgenericconfig認証apiaudiences }、
AuthorizationRestRestStorageProvider { authorizercgenericconfig許可AuthorizerRuleresolvercgenericconfigRuleresolver }、
autoscalingrest .RestStorageProvider {}、
BatchRest .RestStorageProvider {}、
certificatesRest .RestStorageProvider {}、
ColdinationRest .RestStorageProvider {}、
DiscoveryRestStorageProvider {}、
NetworkingrestRestStorageProvider {}、
NoderestRestStorageProvider {}、
policyrestRestStorageProvider {}、
rbacrestRestStorageProvider { authorizercgenericconfig許可承認者}、
SchedulringRestRestStorageProvider {}、
StoragerestRestStorageProvider {}、
FlowControlrestRestStorageProvider {}、
AppSrestStorageProvider {}、
AdminisionRegistrationRestRestStorageProvider {}、
eventsrestRestStorageProvider { TTLcextraconfigeventttl }、
}
//インターフェイスの新しいバージョンをインストールする:/apis/
err= mの場合InstallApis c。Extraconfig。apiresourceconfigsource c。genericconfig。restooptionsgetterrestStorageProviders ... ;エラー!=ゼロ{
nilerrを返します
}

// ...

mnilを返します
}

CRDサーバーと同様に、最初にGenericConfigを呼び出してKube-Apserverという名前のサーバーをインスタンス化し、M.Installlegacyapiを呼び出してLegayapiをインストールします。この方法の主な機能は、コアAPIをルートに登録することです。これは、Apiserverの初期化プロセスで最もコアメソッドの1つです。

 // pkg/controlplane/instance.go

// InstallLegacyapiは、APIの古いバージョンをインストールします。これは実際にはCore API:/API/
func m * instance installlegacyapic * completedconfigreptoptionsettergeneric。restooptionsgetter legacyrestStorageProviderCorerest。LegacyRestStorageProviderエラー{
//レガシアピの各リソースのRESTSTORAGEを作成する
LegacyRestStorageapigroupinfoerr= legacyrestStorageProviderNewLegacyRestStorageRESTOPTIONSGETTER

//ブートストラップコントローラーを初期化します
コントロール名= "bootstrap-controller"
coreclient= corev1clientNewForConfigordie c。GenericConfig。LoopbackClientConfig
bootstrapcontroller= cNewBootStrapController LegacyRestStorageCoreClientCoreClientCoreClient。RestClient ()
mGenericApiserveraddpoststarthookordieControlnnameBootstrapController。PostStarthook
mGenericApiserveraddpreshutdownhookordiecontrollnamebootstrapcontroller。preshutdownhook
//ルーティング情報を登録します
err= mの場合GenericApiserverInstallLegacyApigroup genericApiserver。defaultlegacyapiprefix apigroupinfo );エラー!=ゼロ{
FMTを返しますerrorf"グループバージョンの登録のエラー:%v"err
}
ゼロを返す
}

APIをルーティングに登録するという究極の目標は、RESTFUL APIを提供して対応するリソースを操作することです。 APIの登録は、主に2つのステップに分割されます。最初のステップは、APIの各リソースのRESTSTORAGEを初期化して、バックエンドに保存されているデータの変更を操作することです。 2番目のステップは、動詞に従って各リソースに対応するルートを構築することです。 m.installlegacyapiの主な論理は次のとおりです。

  1. LegacyRestStorageProvider.NewLegacyRestStorageに電話して、レガシアピの各リソースのレストストラージを作成します。 RestStorageの目的は、各リソースのアクセスパスとそのバックエンドストレージ操作に対応することです。
  2. Bootstrap-Controllerを初期化し、PostStarthookに追加します。 Bootstrap-Controllerは、Apiserverのコントローラーです。その主な機能は、システムに必要ないくつかの名前空間を作成し、Kubernetesサービスを作成し、対応する同期操作を定期的にトリガーすることです。 Apiserverが開始すると、PostStarthookを呼び出すことにより、Bootstrap-Controllerを開始します。
  3. :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::づでしょしろうと::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.. :::: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::づでしょしろうと::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.. :::::: ::::::::::::最後に、アクションアレイに従って、各操作にハンドラーメソッドを追加し、ルートに登録してから、ウェブサービスへのルートを登録します。 WebServiceは、最終的に、Restful Designパターンに従ってコンテナに登録します。このプロセスは、以前のCRDサーバーとまったく同じです。

次に、m.installapisメソッドは似ており、(/apis)リソースインターフェイスの新しいバージョンの登録とインストールに使用されます。

次に、AggregatorServerサービスを作成します。基本的な方法は、前の2つのサーバーに似ています。 CreateServerchainプロセスのコールチェーンは次のとおりです。

 |  - > createkubeapiserverconfig
|
CreateServerchain - | - > CreateapiextensionsConfig
|
| | - > cgenericconfig新しい
| - > createapiextensionsserver - > apiextensionsconfig完了()。新しい- |
| | - > sGenericApiserverinstallapigroup
|
| | - > cgenericconfignew- > regacyrestStorageProviderNewLegacyRestStorage
| |
| - > CreateKubeapiserver- > KubeapiserverConfig完了()。新規- | - > mInstallLegacyapi
| |
| | - > mInstallApis
|
|
| - > createaggregatorConfig
|
| | - > cgenericconfig新しい
| |
| - > createaggregatorserver- > gregatorconfig完了()。 NewWithDelegate- | - > apiservicerestNewRestStorage
|
| - > sGenericApiserverinstallapigroup

起動する

createServerchainを呼び出して各サーバーの初期化を完了した後、Server.preparerunが呼び出され、サービスが開始される前に準備を完了し、最後にforted.runメソッドが呼び出され、安全なHTTPサーバーを起動します。

server.preparerunは、主に健康チェック、サバイバルチェック、Openapiルーティングの登録を完了します。以下は、preat.runプロセスを分析し続けており、prepare.runでは、主にS.NonBlockingRunを呼び出してスタートアップ作業を完了します。

 // Vendor/K8s.io/Kube-Aggregator/PKG/APISERVER/APISERVER.GO
funcs predapiaggregatorrunspotch < -chan struct {}) error {
s 実行可能ラン停止
}

ここで実行可能は以前の準備で初期化されており、取得されたものは準備されたGenericApiserverオブジェクトです。

タイプpredapiaggregator struct {
* Apiaggregator
Runnable Runnable
}

funcs * apiaggregatorpreaderun ()( predapiaggregatorerror ){
// ...
//あなたが得るのは、準備されたGenericApiserverオブジェクトです
準備= sGenericApiserverpreaverun ()

// ...

Returnpiaggregator { apiaggregators runnablepreperion }、 nil
}

したがって、実際のスタートアップメソッドの実行は、reportgenericApiserverの実行方法です。

 // vendor/k8s.io/apiserver/pkg/server/generic/apiserver.go

// run Secure HTTPサーバーを起動します。
//ストップが閉じられているか、安全なポートが最初に聴くことができない場合にのみ返されます
funcs preatedgenericapiserverrunspotch < -chan struct {}) error {
DelayedStopch= sライフサイクルセイニルaftershutdowndelayduration
ShutdownInitided= sライフサイクルセイニルシャットダウンが発生しました

// ...

go func (){
遅延した延期signal ()
Klogを延期しますV1 )。 infos"[Graceful-Termination]シャットダウンイベント""name" delayedStopch。name ())

< -停止
//開始したら、 /Readyzはすぐに失敗メッセージの返品を開始する必要があります。
//これにより、ロードバランサーは、ShutdownDelayDurationによって定義されたタイムウィンドウを提供し、 /Readyzが利用できない場合、サーバーへのトラフィックの送信を停止します。
ShutdownInitiatedChsignal ()
クログV1 )。 infos"[Graceful-Termination]シャットダウンイベント""name"shutdowninitidech。name ))

時間睡眠s。ShutdownDelayDuration
}()

//ソケットを閉じます
drainedch= sライフサイクルセイニルInflightrequestsdrained
Stophttpserverch= delayedStopchsignaled ()
ShutdownTimeOut= sShutdownTimeOut
s shutdownsendretryafter {
stophttpserverch = drainedchsignaled ()
shutdowntimeout = 2 *時間2番
クログV1 )。 infos"[[Graceful-Termination] HTTPサーバーシャ​​ットダウンタイムアウトを使用した""ShutdownTimeout"ShutdownTimeout
}
// nonblockingRunを呼び出して、スタートアッププロセスを完了します
StoppedChristenStoppedcherr= snonblockingrunstophttpserverchshutdowntimeout

//次のことは、出口信号を受信した後に行われた最終作業です
httpserStoppedListeningch= sライフサイクルセイニルhttpserStoppedListening
go func (){
< -sienSorstoppedch
httpserverstoppedlisteningchsignal ()
クログV1 )。 infos"[Graceful-Termination]シャットダウンイベント""name" httpserverstoppedlistening。name ()))
}()

// ...
}

サービスを開始する本当のコアはS.NonBlockingRunメソッドであり、その主な実装コードは次のとおりです。

 // /vendor/k8s.io/apiextensions-apiserver/pkg/server/genericapiserver.go

funcs reprentgenericapiservernonblockingrunspotch < -chan struct {}、 shutdowntimeout time持続時間)( <- chan struct {}、 <- chan struct {}、 error ){

auditStopch= makechan struct {})

//監査ログを開始するかどうか
s auditbackend != nil {
err = s監査バックエンドrunauditStopch );エラー!=ゼロ{
nilnilfmtを返しますerrorf"監査バックエンドの実行に失敗した:%v"err
}
}

//実際のHTTPSサーバーを起動します
internalStopch= makechan struct {})
var stoppedch < -chan struct {}
var sienderstoppedch < -chan struct {}
s SecureServingInfo != nil && sハンドラー!= nil {
var errエラー
StoppedChristenStoppedcherr = sSecureServinginfoservewithlistenerstopped s。handlershutdowntimeoutinternalStopch
///
}

//その後、一部の処理も受信されます。
go func (){
< -停止
CloseinternalStopch
stoppedch != nil {
< -停止した
}
sHandlerChainWaitGroup待って()
閉じるauditStopch
}()
// PostStarthooksを実行します
srunpoststarthooks停止
// systemdにready信号を送信します
_err := systemd sdnotifytrue"Ready = 1 \ n" );エラー!=ゼロ{
クログerrorf"SystemDデーモンを送信できませんStart Startメッセージ:%V \ n"err
}

Return Stoppedchリスニングストップnil
}

s.nonblockingRunの主なロジックは次のとおりです。

  1. 監査ログサービスを開始するかどうかを決定します。
  2. s.secureservinginfo.serveを呼び出して、HTTPSサーバーを構成して起動します。
  3. PostStarthooksを実行します。
  4. systemdに準備ができた信号を送信します。

上記は、Apiserverの初期化と起動プロセスの分析です。これは単なる全体的なプロセスであり、上記のAPIリソースRestStorageなど、分析する必要があるなど、詳細な詳細がたくさんあります。

<<:  マルチクラウドプラットフォーム環境統合ソリューション

>>:  企業は依然としてクラウド環境にセキュリティリスクを持ち込んでいる

推薦する

「百度の外部リンク判定」を客観的かつ冷静に見る

序文: SEO における大きな出来事: Baidu は 2013 年 4 月 25 日に「外部リンク...

#クリスマス# profitserver: シンガポール VPS、オーストラリア VPS、50% オフ プロモーション、無制限トラフィック

profitserver は現在、世界中の 15 のデータセンターで VPS および専用サーバー サ...

映画やテレビのビデオウェブサイトのマーケティングとプロモーションの方法

現在、映画、テレビ、ビデオのウェブサイトは数多く存在します。長年運営されている有名なサイトとしては、...

アリババが土地を奪い、グーグルが資金を投じる:クラウドコンピューティング大手、春の軍拡競争開始

近年、クラウド コンピューティングは、俊敏性、拡張性、コストなどの利点により、企業が IT 変革を実...

2018年はブランドマーケティングを再認識しましょう!

「企業ブランドマーケティングと国家ブランドマーケティングは、一見異なる概念のように見えますが、実際に...

ウェブサイトデザイン: 将来の発展の可能性を組み合わせて優れたウェブサイトを作成します

ウェブサイトのデザインは、インターネット ウェブマスターにとって大きな問題であるだけでなく、ウェブ ...

chicagovps 2gメモリエンタープライズバージョンの簡単なレビュー

私はHostcatからchicagovpsの2GメモリエンタープライズエディションVPSのプロモーシ...

2018 年第 4 四半期の低価格 VPS ランキング トップ 10

低価格 VPS リストの第 4 四半期のランキングが発表され、順位は次のようになりました。1 位 r...

Kubernetesを保護する方法

[[408253]] Kubernetes が開発され、そのテクノロジーが成熟するにつれて、ますます...

Taoxie.com がウェブサイトアーキテクチャの観点から SEO をレイアウトする方法 (1)

昨日、私は「内部構造の観点から見たLefeng.comのSEO設定の欠陥」に関する2つの記事を書き終...

以下のことをうまくやれば、ダイヤモンド展示会のプロモーションは素晴らしいものになるでしょう

ダイヤモンド展のプロモーションは、費用対効果の面では直通列車のプロモーションよりはるかに低く、テスト...

オペレーターがエッジコンピューティングの主導権を握ります。このトレンドの中で 5G の新しい機能をどのように活用すればよいのでしょうか?

5Gの商用化が徐々に進むにつれ、5Gの商業的可能性と下流の産業機会を結び付けることができるエッジコン...

編集者への一言:私たちの厄介な立場にどう対処するか

インターネットの影響は日々変化しています。オンラインマーケティングは多くの企業の主な焦点となっていま...

Qvodは現場で罰金通知書に署名することを拒否し、罰金の額はまだ決定されていない。

【A5ウェブマスターネットワークニュース】最近、Qvodは何度も著作権侵害に関与したと非難され、2億...