kubebuilderはクライアントのような昨年執筆した一連の記事[1]では、オペレータ開発プロセスに関わる要素のほとんどを完全に実装しました。しかし、実際の本番アプリケーションでは、定義したCR(CustomResource[2])は、k8sに付属するデプロイメント、ポッド、その他のリソースのようなものです。他のサービスでは、kubectl を通じて yaml を編集するのではなく、api-server インターフェースを直接呼び出して作成および更新する必要があります。 client-go を通じて、k8s に付属するオブジェクトを呼び出すことができます。独自に設計した CR で同様の SDK を直接生成できますか? この問題は、バージョン v1 から v2 にかけて kubebuilder コミュニティのユーザーによって提起されましたが、公式の kubebuilder は SDK を生成するこのアプローチに同意していないようです。 - https://github.com/kubernetes-sigs/kubebuilder/issues/403 [3]。
- https://github.com/kubernetes-sigs/kubebuilder/issues/1152 [4]
現在、以下の解決策が見つかりました。 プラン | アドバンテージ | 欠点 | client-gen [5]を通じて対応するSDKを生成する | 呼び出し側にとっては、静的コードでありエラーが発生しにくいため、より便利になります。 | オペレーター開発者にとっては、このツールを通じて対応するコードを生成するために他の多くの作業を行う必要があり、kubebuilderによって生成されたコード構造を調整する必要さえあるため面倒です。 カスタマイズ性は高いが汎用性は低く、各 CR を個別に生成する必要がある | コントローラランタイム/pkg/クライアント[6] | 通話もより便利に 非常に汎用性が高く、kubebuilderによって生成されたCR定義を公開するだけで済みます。 | クライアント生成と比較すると、静的コードチェックは比較的弱い | クライアントゴー/ダイナミック[7] | 汎用性が非常に高く、Operator 開発時に対応する CR 定義コードを提供する必要もありません。 | 呼び出し側にとっては非常に不便であり、多くのカスタマイズが必要となり、シリアル化操作を繰り返す必要があります。 |
次に、シンプルな CR をカスタマイズします。この CR にはロジックはなく、クライアント呼び出しの検証にのみ使用されます。 kubebuilderがどのようにCRを生成するかがよくわからない場合は、前回の記事「Kubebuilder Concise Tutorial [8]」をお読みください。 apiバージョン:ジョブ。ライリン。 XYZ / v1 種類:テスト メタデータ: ラベル: アプリ。 kuberentes 。 io /管理- : kustomize アプリ。 Kubernetes 。 io /作成者:オペレーター- kubebuilder - clientset アプリ。 Kubernetes 。 io /インスタンス:テスト-サンプル アプリ。 Kubernetes 。 io /名前:テスト アプリ。 Kubernetes 。 io /一部- :演算子- kubebuilder -クライアントセット 名前:テスト-サンプル 名前空間:デフォルト 仕様: foo :テスト 上記のように、この CR には kubebuilder によって初期化された foo フィールドが 1 つだけあり、他には何もありません。 次に、get data を例にして、これら 3 つのメソッドの基本的な使用方法を説明します。次のサンプルコードはoperator-kubebuilder-clientset[9]プロジェクトにあります。 client-go 経由での呼び出し以下に示すように、コードは全体として比較的複雑です。動的パッケージによって生成されたクライアントは一般的なクライアントであるため、k8s の一般的なメタデータ データの一部しか取得できません。 CR の構造化データを取得したい場合は、json 経由でのみ変換できます。 関数main (){ cfg 、エラー: = clientcmd 。 BuildConfigFromFlags ( "" 、 os . Getenv ( "HOME" ) + "/.kube/config" ) fatalf ( err 、 「kube 構成の取得に失敗しました」 )
// クライアントを取得する gvr : =スキーマ。グループバージョンリソース{ グループ: jobv1 。グループバージョン。グループ、 バージョン: jobv1 。グループバージョン。バージョン、 リソース: 「テスト」 、 } クライアント: =動的。 NewForConfigOrDie ( cfg ) 。リソース( gvr )
ctx : =コンテキスト。背景() res 、 err : =クライアント。名前空間( 「default」 )。取得( ctx 、 "テストサンプル" 、 v1 。 GetOptions {}) fatalf ( err 、 「リソースの取得に失敗しました」 )
b 、エラー: = res 。マーシャルJSON () fatalf ( err 、 「JSON バイトの取得に失敗しました」 )
テスト: = jobv1 。テスト{} エラー= json 。逆整列化( b 、 & test ) fatalf ( err 、 「JSON バイトの取得に失敗しました」 )
ログ。 Printf ( "foo: %s" 、テスト. Spec . Foo ) } コードを実行すると正しい結果が得られます。 ❯ client-example/client-go/main.go を実行します 2022/11/15 23:16:23 foo: テスト ソース コードをざっと見ると、Resource メソッドは実際には NamespaceableResourceInterface インターフェイスを返すことがわかります。このインターフェイスは、名前空間レベルと非名前空間レベルのリソースに対する CURD およびその他のアクセス メソッドをサポートしています。 タイプResourceInterfaceインターフェース{ 作成( ctx context.Context 、 obj * unstructured.Unstructured 、オプションmetav1.CreateOptions 、サブリソース...文字列) ( * unstructured.Unstructured 、エラー) 更新( ctx context.Context 、 obj * unstructured.Unstructured 、オプションmetav1.UpdateOptions 、サブリソース...文字列) ( * unstructured.Unstructured 、エラー) UpdateStatus ( ctx context.Context 、 obj * unstructured.Unstructured 、 options metav1.UpdateOptions ) ( * unstructured.Unstructured 、 error ) 削除( ctxコンテキスト.コンテキスト、名前文字列、オプションmetav1 . DeleteOptions 、サブリソース...文字列)エラー DeleteCollection ( ctx context.Context 、 options metav1.DeleteOptions 、 listOptions metav1.ListOptions )エラー Get ( ctxコンテキスト.コンテキスト、名前文字列、オプションmetav1 . GetOptions 、サブリソース...文字列) ( *非構造化.非構造化、エラー) リスト( ctx context . Context , opts metav1 . ListOptions ) ( * unstructured . UnstructuredList , error ) 監視( ctx context.Context , opts metav1.ListOptions ) ( watch.Interface , error ) Patch ( ctx context . Context 、 name string 、 pt types . PatchType 、 data [] byte 、 options metav1 . PatchOptions 、 subresources ... string ) ( * unstructured . Unstructured 、 error ) Apply ( ctx context . Context 、 name string 、 obj * unstructured . Unstructured 、 options metav1 . ApplyOptions 、 subresources ... string ) ( * unstructured . Unstructured 、 error ) ApplyStatus ( ctx context . Context 、 name string 、 obj * unstructured . Unstructured 、 options metav1 . ApplyOptions ) ( * unstructured . Unstructured 、 error ) }
// dynamic.NewForConfigOrDie(cfg).Resource(gvr) によって返されるインターフェース タイプNamespaceableResourceInterfaceインターフェース{ 名前空間(文字列)リソースインターフェース リソースインターフェース } 上記のメソッドはすべて *unstructured.Unstructured 型のデータを返します。これは基本的にマップを通じてオブジェクトを保存し、ユーザーが使用できる GetNamespace などの便利なメソッドを提供します。 非構造化構造体型{ // オブジェクトは文字列、浮動小数点数、整数、ブール値、[]interface{}、または // map[文字列]インターフェース{} // 子供たち。 オブジェクトマップ[文字列]インターフェース{} } コントローラーランタイム経由で呼び出される以下に示すように、コントローラーのランタイム コードは明らかに前の方法よりもシンプルであることがわかります。 JSONを手動でエンコード・デコードする必要はなく、基本スキームデータは生成されたデータを直接使用することもできます。 関数main (){ cfg 、エラー: = config 。 GetConfigWithContext ( "kind-kind" ) fatalf ( err 、 「設定の取得に失敗しました」 )
スキーム、エラー: = v1 。スキームビルダー。建てる() fatalf ( err 、 「スキームの取得に失敗しました」 )
c 、エラー: =クライアント。新規( cfg 、 client 。 Options { Scheme : scheme }) fatalf ( err 、 「新しいクライアントの失敗」 )
テスト: = v1 。テスト{} エラー= c 。 Get ( context . Background ()、 types . NamespacedName { 名前空間: "default" 、 名前: "テストサンプル" 、 }、 &テスト) fatalf ( err 、 「リソースの取得に失敗しました」 )
ログ。 Printf ( "foo: %s" 、テスト. Spec . Foo ) } テストを実行します。 ❯ client - example / controller - runtime / mainを実行します。行く 2022/11/15 23:34:45 foo : テスト インターフェースを簡単に見てみましょう。コントローラー ランタイム クライアントは複数のインターフェースで構成されます。これらをマージすると、実際には上記の client-go インターフェースと非常によく似たものになります。 // クライアントは、Kubernetes オブジェクトに対して CRUD 操作を実行する方法を知っています。 型クライアントインターフェース{ リーダー ライター ステータスクライアント
Scheme () *ランタイム。スキーム RESTMapper ()メタ。 RESTMapper について } 型リーダーインターフェース{ Get ( ctx context . Context 、 key ObjectKey 、 obj Object 、 opts ... GetOption )エラー リスト( ctxコンテキスト.コンテキスト,リストObjectList , opts ... ListOption )エラー } 型Writerインターフェース{ Create ( ctx context . Context , obj Object , opts ... CreateOption )エラー 削除( ctxコンテキスト.コンテキスト, objオブジェクト, opts ... DeleteOption )エラー 更新( ctxコンテキスト.コンテキスト, objオブジェクト, opts ... UpdateOption )エラー Patch ( ctx context . Context 、 obj Object 、 patch Patch 、 opts ... PatchOption )エラー DeleteAllOf ( ctx context . Context , obj Object , opts ... DeleteAllOfOption )エラー } クライアントセット呼び出しを生成するクライアントセットを生成するクライアント呼び出しを生成するために、code-generator[10]のclient-genサブプロジェクトを使用します。この方法を使用する場合、コードに多くの調整を加える必要があります。 - プロジェクト構造の調整、kubebuilder によって生成される api ディレクトリは api/v1 ですが、client-gen に必要なディレクトリ構造は api/${group}/${version} です。
- したがって、ディレクトリ構造を api/job/v1 に調整する必要があり、調整後に元のコードへの依存パスを変更することを忘れないでください。
- kubebuilder レコードに使用される PROJECT ファイルを変更し、内部のパス パスを変更します。
リソース: # ...気にする必要がない部分を削除する -パス: github .com / mohuishou / blog - code / 02 - k8s - operator / operator - kubebuilder - clientset / api / v1 +パス: github .com / mohuishou / blog - code / 02 - k8s - operator / operator - kubebuilder - clientset / api / job / v1 バージョン: v1 バージョン: "3" - 以下に示すように、SDK を生成する必要があるリソースに、//+kubebuilder:object:root=true の前に // +genclient コメントを追加します。
//+genclient //+kubebuilder:オブジェクト:ルート=true //+kubebuilder:サブリソース:ステータス // Test はテスト API のスキーマです 型テスト構造体{ メタv1 。タイプメタ`json : ",inline" ` メタv1 。オブジェクトメタ`json : "metadata,omitempty" `
仕様TestSpec `json : "spec,omitempty" ` ステータスTestStatus `json : "status,omitempty" ` } - api は SchemeGroupVersion グローバル変数を追加し、api/job/v1/groupversion_info.go を変更しました。
var ( // GroupVersion はこれらのオブジェクトを登録するために使用されるグループ バージョンです GroupVersion =スキーマ。グループバージョン{グループ: "job.lailin.xyz" 、バージョン: "v1" } // クライアント生成の SchemeGroupVersion スキームグループバージョン=グループバージョン // SchemeBuilder は、GroupVersionKind スキームに go 型を追加するために使用されます SchemeBuilder = &スキーム。ビルダー{グループバージョン:グループバージョン} // AddToScheme は、このグループ バージョン内の型を指定されたスキームに追加します。 AddToScheme = SchemeBuilderです。スキームに追加 ) - コードジェネレータの依存関係を追加します。コードジェネレータのバージョンは、クライアントの go バージョンと一致している必要があることに注意してください。
- たとえば、テスト プロジェクトでは、client-go のバージョンは v0.25.0 なので、これを実行します。
k8s .io /コード-ジェネレーター@v0 .25 .0を取得します - 私たちのプロジェクトは実際にはコードジェネレータに依存していないため、このプロジェクトにファイル依存関係を追加する必要があります。新しい hack/code_generator.go ファイルを作成し、go:build ツール タグを追加して、アプリケーションのコンパイル時にこの依存関係がコンパイルされないようにします。
//go:ビルドツール // +ビルドツール パッケージハック _ "k8s.io/code-generator"をインポートします - 次に、go mod tidy を実行します。
- クライアントセットを pkg ディレクトリに配置するコード生成スクリプトを作成します。
# !/ bin / bash セット- e セット- x #クライアントセットコードを生成する #Goモジュール名を取得する go_module = $ ( goリスト- m ) # crdグループ グループ= $ { GROUP : - "ジョブ" } # APIバージョン api_version = $ { API_VERSION : - "v1" } project_dir = $ ( cd $ ( dirname $ { BASH_SOURCE [ 0 ] }) / ..; pwd ) #プロジェクトルートディレクトリ # generate - groupsをチェックします。 shは存在する # generate-groups.shスクリプトを直接ダウンロードします。このスクリプトは他のタイプのコードも生成できますが、ここではクライアントコードの生成にのみ使用します。 もし[ ! - f "$project_dir/hack/generate-groups.sh" ];それから echo "hack/generate-groups.sh は存在しません。ダウンロードしてください"
wget - O "$project_dir/hack/generate-groups.sh " https://raw.githubusercontent.com/kubernetes/code-generator/master/generate-groups.sh chmod + x $ project_dir / hack / generate-groups.sh フィ #クライアントセットを生成する #スクリプトドキュメントはhttps://raw.githubusercontent.com/kubernetes/code-generator/master/generate-groups.shで参照できます。 CLIENTSET_NAME_VERSIONED = "$api_version" \ $ project_dir / hack / generate-groups.shクライアント\ $go_module / pkg $go_module / api "$group:$api_version" --出力-ベース$project_dir / もし[ ! - d "$project_dir/pkg" ];それから mkdir $project_dir / pkg フィ #生成されたクライアントセットのフォルダパスには$go_module / pkgが含まれるので、このフォルダをコピーする必要があります rm -rf $プロジェクトディレクトリ/ pkg /クライアントセット mv -f $ project_dir / $go_module / pkg /* $project_dir/pkg/ # 不要なディレクトリを削除する rm -rf $project_dir/$(echo $go_module | cut -d '/' -f 1) - bash hack/gen-client.sh を実行してコードを生成します。生成されたディレクトリ構造は次のようになります。
❯ツリーパッケージ/クライアントセット パッケージ/クライアントセット └── v1 ├──クライアントセット。行く ├──ドク.行く ├──偽物 │ ├── clientset_generated .行く │ ├──ドク.行く │ └──登録します。行く ├──スキーム │ ├──ドク.行く │ └──登録します。行く └──入力 └──仕事 └── v1 ├──ドク.行く ├──偽物 │ ├──ドク.行く │ ├── fake_job_client .go │ └── fake_test .行く ├──生成された展開.go ├── job_client 。行く └──テスト。行く - 生成されたクライアント インターフェイスを以下に示します。上記の 2 つの方法の主な違いは、指定されたタイプであることがわかります。
// TestsGetter には TestInterface を返すメソッドがあります。 // グループのクライアントはこのインターフェースを実装する必要があります。 型TestsGetterインターフェース{ テスト(名前空間文字列) TestInterface } // TestInterface には、テスト リソースを操作するためのメソッドがあります。 タイプTestInterfaceインターフェース{ 作成( ctx context.Context , test * v1.Test , opts metav1.CreateOptions ) ( * v1.Test , error ) 更新( ctx context.Context 、 test * v1.Test 、 opts metav1.UpdateOptions ) ( * v1.Test 、 error ) UpdateStatus ( ctx context.Context , test * v1.Test , opts metav1.UpdateOptions ) ( * v1.Test , error ) 削除( ctxコンテキスト.コンテキスト、名前文字列、 opts metav1 . DeleteOptions )エラー DeleteCollection ( ctx context.Context 、 opts metav1.DeleteOptions 、 listOpts metav1.ListOptions )エラー Get ( ctx context.Context , name string , opts metav1.GetOptions ) ( * v1.Test , error )を取得します。 リスト( ctx context.Context , opts metav1.ListOptions ) ( * v1.TestList , error ) 監視( ctx context.Context , opts metav1.ListOptions ) ( watch.Interface , error ) Patch ( ctx context.Context 、 name string 、 pt types.PatchType 、 data [] byte 、 opts metav1.PatchOptions 、 subresources ... string ) ( result * v1.Test 、 err error ) テスト拡張 } クライアントセットの呼び出しclientset コードが最も簡潔であることがわかります。 関数main (){ cfg 、エラー: = config 。 GetConfigWithContext ( "kind-kind" ) fatalf ( err 、 「設定の取得に失敗しました」 ) クライアント: = clientv1 。新しいForConfigOrDie ( cfg ) テスト、エラー: =クライアント。テスト( 「デフォルト」 )。取得(コンテキスト.Background (), "テストサンプル" 、 v1.GetOptions {}) fatalf ( err 、 「新しいクライアントの失敗」 )
ログ。 Printf ( "foo: %s" 、テスト. Spec . Foo ) } 埋め込む: ❯クライアントを実行する- example / clientset / main.go 2022/11/16 10:26:50 foo : テスト 要約するこれら 3 つの呼び出し方法には、それぞれ長所と短所があります。 Kubebuilder では、 controller-runtime を直接使用することを公式に推奨していますが、他の 2 つの方法にも独自の使用シナリオがあります。 Client-go は最も汎用性が高く、オペレーター開発者のコードに依存しません。 Clientset は最もカスタマイズ性が高く、ユーザーにとって最も便利です。 実は、最初は client-go と clientset の 2 つの方法しか知らなかったので、ずっと clientset の方法を使っていました。この記事の本来の目的は、clientset の最小限の構成方法を記録することだけでしたが、情報をまとめる過程で、controller-runtime 方式を見つけました。オペレーター開発者として、クライアントセットを生成するために変更する必要のあるものが多すぎて、間違いを起こしやすいため、最終的にコントローラーランタイムを使用することを選択しました。コントローラーランタイムは、使いやすさと汎用性の点で優れたパフォーマンスを発揮します。 参考文献[1]記事シリーズ:https://lailin.xyz/post/operator-11-summary.html. [2]カスタムリソース: https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/api-extension/custom-resources/. [3] https://github.com/kubernetes-sigs/kubebuilder/issues/403: https://github.com/kubernetes-sigs/kubebuilder/issues/403。 [4] https://github.com/kubernetes-sigs/kubebuilder/issues/1152: https://github.com/kubernetes-sigs/kubebuilder/issues/1152。 [5] client-gen: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/generated-clientset.md。 [6] controller-runtime/pkg/client: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/client?utm_source=godoc#example-Client-Update. [7] client-go/dynamic: https://pkg.go.dev/k8s.io/[email protected]/dynamic. [8] Kubebuilderの簡単なチュートリアル: https://lailin.xyz/post/operator-03-kubebuilder-tutorial.html. [9] operator-kubebuilder-clientset: https://github.com/mohuishou/blog-code/tree/main/02-k8s-operator/operator-kubebuilder-clientset/client-example. [10]コードジェネレーター: https://github.com/kubernetes/code-generator. この記事はWeChatの公式アカウント「mohuishou」から転載したもので、以下のQRコードからフォローできます。この記事の転載についてはmohuishou公式アカウントまでご連絡ください。 |