自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

第三方應(yīng)用如何調(diào)用我們 kubebuilder 生成的自定義資源?

云計算 云原生
這次這篇文章的初衷其實也只是為了記錄一下 clientset 的最小化配置方法,但是在資料匯總的過程中發(fā)現(xiàn)了 controller-runtime 這種方法,作為 operator 的開發(fā)者最后選擇使用 controller-runtime,因為生成 clientset 需要改動的東西實在是太多了,而且很容易出錯。controller-runtime 在易用性和通用性都有不錯的表現(xiàn)。

kubebuilder 能否生成類似 clie

在去年寫的系列文章[1]中,我們完整的實現(xiàn)了 operator 開發(fā)過程中涉及到的絕大部分要素,但是在實際的生產(chǎn)應(yīng)用中我們定義的 CR(CustomResource[2]) 就像 k8s 自帶的 deployment、pod 等資源一樣,會存在其他服務(wù)直接調(diào)用 api-server 接口進行創(chuàng)建更新的需求,而不僅僅只是通過 kubectl 編輯yaml。

那么 k8s 自帶的對象我們可以通過 client-go 進行調(diào)用,我們自己設(shè)計的 CR 能否直接生成類似的 SDK 呢?

這個問題在 kubebuilder 社區(qū)從 v1 - v2 版本都有用戶在提,但是 kubebuilder 官方似乎不太贊同生成 sdk 的這種做法。

  • https://github.com/kubernetes-sigs/kubebuilder/issues/403[3]。
  • https://github.com/kubernetes-sigs/kubebuilder/issues/1152[4]

目前找到以下幾種方案。

方案

優(yōu)點

缺點

通過 client-gen[5] 生成對應(yīng)的 sdk

調(diào)用方使用起來會更加的方便,畢竟是靜態(tài)代碼,不容易出錯

對于 operator 的開發(fā)者來說比較麻煩,因為要通過這個工具生成對應(yīng)的代碼還需要做很多其他的事情,甚至需要調(diào)整 kubebuiler 生成的代碼結(jié)構(gòu)

客制化較強,通用性較弱,每個 CR 都需要單獨生成

controller-runtime/pkg/client[6]

調(diào)用也比較方便

通用性強,只需要將 kubebuilder 生成好的 CR 定義暴露出去即可

相對于通過 client-gen 來說靜態(tài)代碼檢查的能力相對較弱

client-go/dynamic[7]

通用性極強,甚至可以不用 Operator 開發(fā)中提供對應(yīng)的 CR 定義代碼

調(diào)用方來說極其不方便,需要自定義很多東西,并且需要反復(fù)進行序列化操作

接下來我們就自定義一個簡單的 CR,這個 CR 沒有任何的邏輯,只是為了用來驗證客戶端調(diào)用,關(guān)于 kubebuilder 生成 CR 如果不是特別清楚,可以閱讀之前的這篇文章: kubebuilder 簡明教程[8]。

apiVersion: job.lailin.xyz/v1
kind: Test
metadata:
labels:
app.kuberentes.io/managed-by: kustomize
app.kubernetes.io/created-by: operator-kubebuilder-clientset
app.kubernetes.io/instance: test-sample
app.kubernetes.io/name: test
app.kubernetes.io/part-of: operator-kubebuilder-clientset
name: test-sample
namespace: default
spec:
foo: test

如上所示這個 CR 只有一個 foo 字段,也就是 kubebuilder 初始化的一個字段,除此之外什么也沒有。

接下來我都以 get 數(shù)據(jù)為例來分別說明這三種方式的基本使用方法,下面的示例代碼可以在 operator-kubebuilder-clientset[9] 項目中找到。

通過 client-go 調(diào)用

如下所示可以看到,代碼整體來說相對比較復(fù)雜,dynamic 包生成的 client 是一個通用的 client,所以他只能獲取到 k8s 的一些通用的 metadata 數(shù)據(jù),如果想要獲取到 CR 的結(jié)構(gòu)化數(shù)據(jù)就只能通過 json 來進行轉(zhuǎn)換。

func main() {
cfg, err := clientcmd.BuildConfigFromFlags("", os.Getenv("HOME")+"/.kube/config")
fatalf(err, "get kube config fail")

// 獲取 client
gvr := schema.GroupVersionResource{
Group: jobv1.GroupVersion.Group,
Version: jobv1.GroupVersion.Version,
Resource: "tests",
}
client := dynamic.NewForConfigOrDie(cfg).Resource(gvr)

ctx := context.Background()
res, err := client.Namespace("default").Get(ctx, "test-sample", v1.GetOptions{})
fatalf(err, "get resource fail")

b, err := res.MarshalJSON()
fatalf(err, "get json byte fail")

test := jobv1.Test{}
err = json.Unmarshal(b, &test)
fatalf(err, "get json byte fail")

log.Printf("foo: %s", test.Spec.Foo)
}

執(zhí)行代碼可以獲取到正確的結(jié)果。

? go run client-example/client-go/main.go
2022/11/15 23:16:23 foo: test

簡單看一下源碼,可以看到實際上 Resource 方法就是返回了 NamespaceableResourceInterface 接口,這個接口支持了 Namespace 以及非 Namespace 級別的資源的 CURD 等訪問方法。

type ResourceInterface interface {
Create(ctx context.Context, obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error)
Update(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error)
UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error)
Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error
DeleteCollection(ctx context.Context, options metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error)
List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
Watch(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) 返回的接口
type NamespaceableResourceInterface interface {
Namespace(string) ResourceInterface
ResourceInterface
}

上面的這些方法返回的都是 *unstructured.Unstructured 類型的數(shù)據(jù),這個類型本質(zhì)上就是把 object 通過 map 保存了下來,然后提供了 GetNamespace 等便捷的方法給用戶使用。

type Unstructured struct {
// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
// map[string]interface{}
// children.
Object map[string]interface{}
}

通過 controller-runtime 調(diào)用

如下所示,可以發(fā)現(xiàn) controller-runtime 的代碼明顯要比上一種方式要簡潔一些,不需要手動去 json 編碼解碼了,基礎(chǔ)的 scheme 數(shù)據(jù)也可以直接使用生成好的數(shù)據(jù)。

func main() {
cfg, err := config.GetConfigWithContext("kind-kind")
fatalf(err, "get config fail")

scheme, err := v1.SchemeBuilder.Build()
fatalf(err, "get scheme fail")

c, err := client.New(cfg, client.Options{Scheme: scheme})
fatalf(err, "new client fail")

test := v1.Test{}
err = c.Get(context.Background(), types.NamespacedName{
Namespace: "default",
Name: "test-sample",
}, &test)
fatalf(err, "get resource fail")

log.Printf("foo: %s", test.Spec.Foo)
}

執(zhí)行測試一下。

? go run client-example/controller-runtime/main.go
2022/11/15 23:34:45 foo: test

同樣簡單看下接口,controller-runtime 的 client 是多個接口組合而來的,合并在一起之后其實和上面 client-go 的接口大差不差。

// Client knows how to perform CRUD operations on Kubernetes objects.
type Client interface {
Reader
Writer
StatusClient

Scheme() *runtime.Scheme
RESTMapper() meta.RESTMapper
}
type Reader interface {
Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error
List(ctx context.Context, list ObjectList, opts ...ListOption) error
}
type Writer interface {
Create(ctx context.Context, obj Object, opts ...CreateOption) error
Delete(ctx context.Context, obj Object, opts ...DeleteOption) error
Update(ctx context.Context, obj Object, opts ...UpdateOption) error
Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error
DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error
}

生成 clientset 調(diào)用

生成 clientset

我們使用 code-generator[10] 的 client-gen 子項目來生成客戶端的調(diào)用,使用這個方法我們需要對代碼做很多的調(diào)整。

  • 項目結(jié)構(gòu)調(diào)整,kubebuilder 生成的 api 目錄是api/v1,但是 client-gen 要求的目錄結(jié)構(gòu)是 api/${group}/${version} 。
  • 所以我們需要將目錄結(jié)構(gòu)調(diào)整為api/job/v1,調(diào)整后記得修改原有代碼的依賴路徑。
  • 修改PROJECT 文件,這個文件用于 kubebuilder 記錄,修改里面的 path 路徑。
resources:
# ... 刪除掉不需要關(guān)注的部分
- path: github.com/mohuishou/blog-code/02-k8s-operator/operator-kubebuilder-clientset/api/v1
+ path: github.com/mohuishou/blog-code/02-k8s-operator/operator-kubebuilder-clientset/api/job/v1
version: v1
version: "3"
  • 給需要生成 sdk 的資源加上// +genclient 注釋,如下所示,放在 //+kubebuilder:object:root=true 前面即可。
//+genclient
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// Test is the Schema for the tests API
type Test struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec TestSpec `json:"spec,omitempty"`
Status TestStatus `json:"status,omitempty"`
}
  • api 新增SchemeGroupVersion 全局變量,修改 api/job/v1/groupversion_info.go。
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "job.lailin.xyz", Version: "v1"}
// SchemeGroupVersion for clien-gen
SchemeGroupVersion = GroupVersion
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
  • 添加code-generator 依賴,注意 code-generator 版本一定要和你的 client-go 版本一致。
  • 例如在我們的測試項目里面 client-go 的版本是 v0.25.0 那我們執(zhí)行。
go get k8s.io/code-generator@v0.25.0
  • 由于我們的項目內(nèi)實際上并沒有依賴 code-generator ,所以我們需要添加一個文件依賴這個項目,我們新建一個 hack/code_generator.go 文件,我們加上 go:build tools 標簽確保在編譯應(yīng)用的時候不會將這個依賴編譯進去。
//go:build tools
// +build tools
package hack
import _ "k8s.io/code-generator"
  • ?然后我們執(zhí)行 go mod tidy。
  • 編寫代碼生成腳本,會將 clientset 放到 pkg 目錄下。
#!/bin/bash
set -e
set -x
# 生成 clientset 代碼
# 獲取 go module name
go_module=$(go list -m)
# crd group
group=${GROUP:-"job"}
# api 版本
api_version=${API_VERSION:-"v1"}
project_dir=$(cd $(dirname ${BASH_SOURCE[0]})/..; pwd) # 項目根目錄
# check generate-groups.sh is exist
# 直接下載 generate-groups.sh 腳本,這個腳本還可以生成其他類型的代碼,但是我們這里只用來生成 client 的代碼
if [ ! -f "$project_dir/hack/generate-groups.sh" ]; then
echo "hack/generate-groups.sh is not exist, download"

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
fi
# 生成 clientset
# 腳本文檔可以查看 https://raw.githubusercontent.com/kubernetes/code-generator/master/generate-groups.sh
CLIENTSET_NAME_VERSIONED="$api_version" \
$project_dir/hack/generate-groups.sh client \
$go_module/pkg $go_module/api "$group:$api_version" --output-base $project_dir/
if [ ! -d "$project_dir/pkg" ];then
mkdir $project_dir/pkg
fi
# 生成的 clientset 的文件夾路徑會包含 $go_module/pkg 所以我們需要把這個文件夾復(fù)制出來
rm -rf $project_dir/pkg/clientset
mv -f $project_dir/$go_module/pkg/* $project_dir/pkg/
# 刪除不需要的目錄
rm -rf $project_dir/$(echo $go_module | cut -d '/' -f 1)
  • 執(zhí)行 bash hack/gen-client.sh 生成代碼,生成的目錄結(jié)構(gòu)如下:
? tree pkg/clientset 
pkg/clientset
└── v1
├── clientset.go
├── doc.go
├── fake
├── clientset_generated.go
├── doc.go
└── register.go
├── scheme
├── doc.go
└── register.go
└── typed
└── job
└── v1
├── doc.go
├── fake
├── doc.go
├── fake_job_client.go
└── fake_test.go
├── generated_expansion.go
├── job_client.go
└── test.go
  • 生成的客戶端接口如下所示,我們可以看到和上面兩種方式的主要區(qū)別就是指定了類型。
// TestsGetter has a method to return a TestInterface.
// A group's client should implement this interface.
type TestsGetter interface {
Tests(namespace string) TestInterface
}
// TestInterface has methods to work with Test resources.
type TestInterface interface {
Create(ctx context.Context, test *v1.Test, opts metav1.CreateOptions) (*v1.Test, error)
Update(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)
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Test, error)
List(ctx context.Context, opts metav1.ListOptions) (*v1.TestList, error)
Watch(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)
TestExpansion
}

調(diào)用 clientset

可以看到 clientset 的代碼是最簡潔的。

func main() {
cfg, err := config.GetConfigWithContext("kind-kind")
fatalf(err, "get config fail")
client := clientv1.NewForConfigOrDie(cfg)
test, err := client.Tests("default").Get(context.Background(), "test-sample", v1.GetOptions{})
fatalf(err, "new client fail")

log.Printf("foo: %s", test.Spec.Foo)
}

執(zhí)行:

? go run client-example/clientset/main.go 
2022/11/16 10:26:50 foo: test

總結(jié)

這三種調(diào)用方式其實各有優(yōu)劣,kubebuilder 官方比較推薦直接使用 controller-runtime,但是另外兩種方式也有各自的使用場景,client-go 這種方式通用性最強,不用依賴 operator 開發(fā)者的代碼,clientset 的定制性最強,對于使用方來說也最方便。

對于我而言其實最開始只了解到 client-go 和 clientset  這兩種方式,所以之前一直都是使用的 clientset 這種方式,這次這篇文章的初衷其實也只是為了記錄一下 clientset 的最小化配置方法,但是在資料匯總的過程中發(fā)現(xiàn)了 controller-runtime 這種方法,作為 operator 的開發(fā)者最后選擇使用 controller-runtime,因為生成 clientset 需要改動的東西實在是太多了,而且很容易出錯。controller-runtime 在易用性和通用性都有不錯的表現(xiàn)。

參考資料

[1]系列文章: https://lailin.xyz/post/operator-11-summary.html。

[2]CustomResource: 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/generating-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/client-go@v0.25.4/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]code-generator: https://github.com/kubernetes/code-generator。

本文轉(zhuǎn)載自微信公眾號「mohuishou」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系mohuishou公眾號。

責(zé)任編輯:姜華 來源: mohuishou
相關(guān)推薦

2015-11-05 16:44:37

第三方登陸android源碼

2021-06-17 14:56:00

鴻蒙HarmonyOS應(yīng)用

2021-07-28 09:40:04

鴻蒙HarmonyOS應(yīng)用

2014-08-13 10:27:23

CocoaPods

2014-07-25 09:33:22

2013-08-12 16:04:19

第三方移動應(yīng)用

2014-09-22 10:04:55

ios8

2011-06-07 14:36:24

iOS5WWDC

2017-12-11 15:53:56

2022-04-25 09:00:46

npm包管理器

2012-08-03 09:44:11

iOS 6蘋果地圖

2014-07-23 08:55:42

iOSFMDB

2019-07-30 11:35:54

AndroidRetrofit

2023-09-14 10:55:16

2011-06-03 17:09:40

Android移動應(yīng)用智能手機

2012-05-24 21:59:55

iOS

2017-05-16 13:24:02

LinuxCentOS第三方倉庫

2025-02-05 10:19:24

2025-03-04 10:00:00

架構(gòu)接口k開發(fā)

2022-05-21 23:56:16

Python庫搜索Python
點贊
收藏

51CTO技術(shù)棧公眾號