サードパーティのアプリケーションは、kubebuilder によって生成されたカスタム リソースをどのように呼び出すのでしょうか?

サードパーティのアプリケーションは、kubebuilder によって生成されたカスタム リソースをどのように呼び出すのでしょうか?

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
種類:テスト
メタデータ:
ラベル:
アプリkuberentesio /管理- : kustomize
アプリKubernetesio /作成:オペレーター- kubebuilder - clientset
アプリKubernetesio /インスタンス:テスト-サンプル
アプリKubernetesio /名前:テスト
アプリKubernetesio /一部- :演算子- kubebuilder -クライアントセット
名前:テスト-サンプル
名前空間:デフォルト
仕様:
foo :テスト

上記のように、この CR には kubebuilder によって初期化された foo フィールドが 1 つだけあり、他には何もありません。

次に、get data を例にして、これら 3 つのメソッドの基本的な使用方法を説明します。次のサンプルコードはoperator-kubebuilder-clientset[9]プロジェクトにあります。

client-go 経由での呼び出し

以下に示すように、コードは全体として比較的複雑です。動的パッケージによって生成されたクライアントは一般的なクライアントであるため、k8s の一般的なメタデータ データの一部しか取得できません。 CR の構造化データを取得したい場合は、json 経由でのみ変換できます。

関数main (){
cfgエラー: = clientcmdBuildConfigFromFlags ( ""os . Getenv ( "HOME" ) + "/.kube/config" )
fatalf ( err「kube 構成の取得に失敗しました」 )

// クライアントを取得する
gvr : =スキーマグループバージョンリソース{
グループ: jobv1グループバージョングループ
バージョン: jobv1グループバージョンバージョン
リソース: 「テスト」
}
クライアント: =動的NewForConfigOrDie ( cfg ) 。リソース( gvr )

ctx : =コンテキスト背景()
reserr : =クライアント名前空間( 「default」 )。取得( ctx"テストサンプル"v1GetOptions {})
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.Contextobj * unstructured.Unstructuredオプションmetav1.CreateOptionsサブリソース...文字) ( * unstructured.Unstructured 、エラー)
更新( ctx context.Contextobj * unstructured.Unstructuredオプションmetav1.UpdateOptionsサブリソース...文字) ( * unstructured.Unstructuredエラー)
UpdateStatus ( ctx context.Contextobj * unstructured.Unstructuredoptions 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 . Contextname stringpt types . PatchTypedata [] byteoptions metav1 . PatchOptionssubresources ... string ) ( * unstructured . Unstructurederror )
Apply ( ctx context . Contextname stringobj * unstructured . Unstructuredoptions metav1 . ApplyOptionssubresources ... string ) ( * unstructured . Unstructurederror )
ApplyStatus ( ctx context . Contextname stringobj * unstructured . Unstructuredoptions metav1 . ApplyOptions ) ( * unstructured . Unstructurederror )
}

// dynamic.NewForConfigOrDie(cfg).Resource(gvr) によって返されるインターフェース
タイプNamespaceableResourceInterfaceインターフェース{
名前空間(文字列)リソースインターフェース
リソースインターフェース
}

上記のメソッドはすべて *unstructured.Unstructured 型のデータを返します。これは基本的にマップを通じてオブジェクトを保存し、ユーザーが使用できる GetNamespace などの便利なメソッドを提供します。

非構造化構造体型{
// オブジェクトは文字列、浮動小数点数、整数、ブール値、[]interface{}、または
// map[文字列]インターフェース{}
// 子供たち。
オブジェクトマップ[文字列]インターフェース{}
}

コントローラーランタイム経由で呼び出される

以下に示すように、コントローラーのランタイム コードは明らかに前の方法よりもシンプルであることがわかります。 JSONを手動でエンコード・デコードする必要はなく、基本スキームデータは生成されたデータを直接使用することもできます。

関数main (){
cfgエラー: = configGetConfigWithContext ( "kind-kind" )
fatalf ( err「設定の取得に失敗しました」 )

スキームエラー: = v1スキームビルダー建てる()
fatalf ( err「スキームの取得に失敗しました」 )

cエラー: =クライアント新規( cfgclientOptions { Scheme : scheme })
fatalf ( err「新しいクライアントの失敗」 )

テスト: = v1テスト{}
エラー= cGet ( 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 . Contextkey ObjectKeyobj Objectopts ... 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 . Contextobj Objectpatch Patchopts ... 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.Contexttest * 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.Contextname stringpt types.PatchTypedata [] byteopts metav1.PatchOptions subresources ... string ) ( result * v1.Test err error )
テスト拡張
}

クライアントセットの呼び出し

clientset コードが最も簡潔であることがわかります。

関数main (){
cfgエラー: = configGetConfigWithContext ( "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公式アカウントまでご連絡ください。

<<:  Amazon Web Services は、Gartner Magic Quadrant に 12 年連続でランクインしています。クラウド市場をどのようにリードするのでしょうか?

>>:  シーメンスはRed Hat OpenShiftで工場エッジのイノベーションを加速

推薦する

オンライン音楽は7月以降に完全有料化される見込みで、中小規模の音楽サイトが影響を受ける

北京ビジネスデイリー(記者 江夢偉)現在、ウェブサイトやその他の配信チャネルでは音楽の著作権侵害が依...

加速クラウド:900元、50Mbps BGP帯域幅、100G高防御、2*e5-2620/24gメモリ/240g SSD/100gクラウドディスク

加速クラウドは、衝撃的なニュースを正式に発表しました。価格は据え置きで、(1)ルナンビッグデータセン...

外国貿易のマーケティングとプロモーションでよくある6つの間違いの分析

対外貿易マーケティングとは、「広告」、「マスプロモーション」、「バイラルマーケティング」、「テレマー...

ウェブサイトの運用最適化には、ユーザーに役立つ最適化のコンセプトと考え方を確立する必要があります

ご存知のとおり、検索エンジンのアルゴリズムが継続的にアップグレードされるにつれて、ウェブサイトの運用...

#ニュースLinode-KVM社内テストアプリケーション開始/Windowsは世界

Linode の公式フォーラムから衝撃的なニュースが出てきました。Linode は KVM 仮想 V...

winnervps-シンガポール/XEN/windows/G ポート/$7/512m メモリ/10gSSD/1T トラフィック

winnervps はインドネシア人によって開設されたようですが、設立された正確な年は不明で、ドメイ...

インターネットの発展とトレンド!

過去2年間で、ユーザーは次々と「無料」に別れを告げてきました。支払い残高の引き出しには料金がかかり、...

モバイルアプリ広告対決:急成長の「爆発ゾーン」はどこ?

最近、Google はモバイル ショッピングからの広告収入を増やすために、モバイル アプリ内の広告ス...

これらの4つの文章について考えれば、SEOに悩まされることはなくなるでしょう

百度はSEOを文書で認めたことはなく、検索最適化ガイドラインなどの公式文書を発表したのみだが、SEO...

ブラックフライデー - 100TB サーバー - サーバー無料 [2x1] 256G SSD

uk2 グループの 100tb.com は、常に膨大なトラフィックで有名であり、その価格は通常安くは...

estnoc: 香港の VPS に直接接続、帯域幅が大きい、スピードが速い、ウェブサイト構築、「言葉では言い表せない」推薦

香港には大きな帯域幅を持つ VPS はほとんどなく、大きな帯域幅と直接接続を備えた香港の VPS は...

クラウドの自動化は重要

ハイブリッド クラウドでは、人為的なエラーなしにアプリケーションをスムーズに展開および実行するために...

著作権法案は第46条を削除し、オンライン侵害の定義に関する新たな条項を追加する。

著作権法第2次草案、意見募集中 著作権の法的許諾範囲を「教科書や新聞・雑誌の転載の法的許諾」に絞り込...