go語言代碼生成器code generator使用示例介紹
代碼生成器介紹
client-go
為每種k8s內置資源提供瞭對應的clientset
和informer
。那麼我們要監聽和操作自定義資源對象,應該如何做呢?
方式一:使用client-go
提供的dynamicClient
來操作自定義資源對象,當然由於dynamicClient
是基於RESTClient
實現的,所以我們可以使用RESTClient
來達到同樣的目的。
方式二: 使用conde-generator
來幫我們生成我們需要的代碼,這樣我們就可以像使用client-go
為k8s內置資源對象提供的方式監聽和操作自定義資源瞭。
code-generator
code-generator 就是 Kubernetes 提供的一個用於代碼生成的項目,它提供瞭以下工具為 Kubernetes 中的資源生成代碼:
- deepcopy-gen: 生成深度拷貝方法,為每個 T 類型生成 func (t* T) DeepCopy() *T 方法,API 類型都需要實現深拷貝
- client-gen: 為資源生成標準的 clientset
- informer-gen: 生成 informer,提供事件機制來響應資源的事件
- lister-gen: 生成 Lister,為 get 和 list 請求提供隻讀緩存層(通過 indexer 獲取)
Informer 和 Lister 是構建控制器的基礎,使用這4個代碼生成器可以創建全功能的、和 Kubernetes 上遊控制器工作機制相同的 production-ready 的控制器。
code-generator 還包含一些其它的代碼生成器,例如 Conversion-gen 負責產生內外部類型的轉換函數、Defaulter-gen 負責處理字段默認值。
大部分的生成器支持–input-dirs參數來讀取一系列輸入包,處理其中的每個類型,然後生成代碼:
1、部分代碼生成到輸入包所在目錄,例如 deepcopy-gen 生成器,也可以使用參數–output-file-base "zz_generated.deepcopy" 來定義輸出文件名
2、其它代碼生成到 –output-package 指定的目錄,例如 client-gen、informer-gen、lister-gen 等生成器
示例
接來下我們使用code-generator進行實戰演示:
首先我們將項目拉到本地:
$ git clone https://github.com/kubernetes/code-generator.git $ git checkout 0.23.3
然後我們進入到cmd目錄下,就會看到我們上面介紹的工具:
接著我們對client-gen
,deepcopy-gen
,infromer-gen
,lister-gen
進行安裝,會安裝到GOPATH的bin目錄下:
# 進行安裝 $ go install ./cmd/{client-gen,deepcopy-gen,informer-gen,lister-gen} # 獲取GOPATH路徑 $ go env | grep GOPATH GOPATH="/Users/Christian/go" # 查看 ls /Users/Christian/go/bin client-gen deepcopy-gen goimports lister-gen controller-gen defaulter-gen informer-gen type-scaffold
發現我們已經成功的安裝瞭,這時候我們就可以直接使用這些工具瞭,比如我們可以使用--help
命令來查看如何使用client-gen
:
當然通常情況下我們不會去單獨的使用某一個工具。
接下來我們來創建我們的項目,此處我們可以仿照sample controller項目進行編寫:
$ mkdir operator-test && cd operator-test $ go mod init operator-test $ mkdir -p pkg/apis/example.com/v1 ➜ operator-test tree . ├── go.mod ├── go.sum └── pkg └── apis └── example.com └── v1 ├── doc.go ├── register.go └── types.go 4 directories, 5 files
接下來我們對v1下面的三個go文件進行填充(可以直接復制sample-controller,對其進行做簡單修改):
doc.go主要是用來聲明要使用deepconpy-gen
以及groupName。
// pkg/crd.example.com/v1/doc.go // +k8s:deepcopy-gen=package // +groupName=example.com package v1
types.go主要是定義crd資源對應的go中的結構。
// pkg/crd.example.com/v1/types.go package v1 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Bar is a specification for a Bar resource type Bar struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec BarSpec `json:"spec"` // Status BarStatus `json:"status"` } // BarSpec is the spec for a Bar resource type BarSpec struct { DeploymentName string `json:"deploymentName"` Image string `json:"image"` Replicas *int32 `json:"replicas"` } // BarStatus is the status for a Bar resource type BarStatus struct { AvailableReplicas int32 `json:"availableReplicas"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // BarList is a list of Bar resources type BarList struct { metav1.TypeMeta `json:",inline" :"metav1.TypeMeta"` metav1.ListMeta `json:"metadata" :"metav1.ListMeta"` Items []Bar `json:"items" :"items"` }
register.go顧名思義,就是註冊資源。
package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: "example.com", Version: "v1"} // Kind takes an unqualified kind and returns back a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } var ( // SchemeBuilder initializes a scheme builder SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) // AddToScheme is a global function that registers this API group & version to a scheme AddToScheme = SchemeBuilder.AddToScheme ) // Adds the list of known types to Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Bar{}, &BarList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil }
這時候會發現&Bar{},&BarLis{}會報錯,這是因為我們還沒有為其實現deepcopy
方法。
由於在自動生成代碼的時候,需要指定header的信息,所以我們為瞭方便,可以將code-generator
項目下的hack
包直接拷貝到我們當前項目根目錄下。
接下來我們使用code-generator
來為我們自動生成代碼:
# 運行 code-generator/generate-group.sh ./../../github/code-generator/generate-groups.sh all \ # 指定 group 和 version,生成deeplycopy以及client operator-test/pkg/client operator-test/pkg/apis crd.example.com:v1 \ # 指定頭文件 --go-header-file=./hack/boilerplate.go.txt \ # 指定輸出位置,默認為GOPATH --output-base ../ Generating deepcopy funcs Generating clientset for crd.example.com:v1 at operator-test/pkg/client/clientset Generating listers for crd.example.com:v1 at operator-test/pkg/client/listers Generating informers for crd.example.com:v1 at operator-test/pkg/client/informers
這時候我們再來查看項目結構:
➜ operator-test tree . ├── go.mod ├── go.sum ├── hack │ └── boilerplate.go.txt └── pkg ├── apis │ └── crd.example.com │ └── v1 │ ├── doc.go │ ├── register.go │ ├── types.go │ └── zz_generated.deepcopy.go └── client ├── clientset │ └── versioned │ ├── clientset.go │ ├── doc.go │ ├── fake │ │ ├── clientset_generated.go │ │ ├── doc.go │ │ └── register.go │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ └── typed │ └── crd.example.com │ └── v1 │ ├── bar.go │ ├── crd.example.com_client.go │ ├── doc.go │ ├── fake │ │ ├── doc.go │ │ ├── fake_bar.go │ │ └── fake_crd.example.com_client.go │ └── generated_expansion.go ├── informers │ └── externalversions │ ├── crd.example.com │ │ ├── interface.go │ │ └── v1 │ │ ├── bar.go │ │ └── interface.go │ ├── factory.go │ ├── generic.go │ └── internalinterfaces │ └── factory_interfaces.go └── listers └── crd.example.com └── v1 ├── bar.go └── expansion_generated.go 22 directories, 29 files
這時候我們就可以像操作內置資源一樣,操作我們的自定義資源瞭。
我們先準備crd以及對應的cr,這邊也是可以直接從sample-controller
項目進行拷貝,做簡單的修改即可。
# manifests/example.com_bars.yaml --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: (devel) creationTimestamp: null name: bars.crd.example.com spec: group: crd.example.com names: kind: Bar listKind: BarList plural: bars singular: bar scope: Namespaced versions: - name: v1 schema: openAPIV3Schema: description: Bar is a specification for a Bar resource properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the generated submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: description: BarSpec is the spec for a Bar resource properties: deploymentName: type: string image: type: string replicas: format: int32 type: integer required: - deploymentName - image - replicas type: object required: - spec type: object served: true storage: true # manifests/cr.yaml --- apiVersion: crd.example.com/v1 kind: Bar metadata: name: bar-demo namespace: default spec: image: "nginx:1.17.1" deploymentName: example-bar replicas: 2
接下來我們來編寫main函數,這時候我們就可以使用client-go像操作我們內置資源一樣,操作crd資源瞭。
package main import ( "context" "fmt" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/clientcmd" "log" clientSet "operator-test/pkg/client/clientset/versioned" "operator-test/pkg/client/informers/externalversions" ) func main() { config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile) if err != nil { log.Fatalln(err) } clientset, err := clientSet.NewForConfig(config) if err != nil { log.Fatalln(err) } list, err := clientset.CrdV1().Bars("default").List(context.TODO(), v1.ListOptions{}) if err != nil { log.Fatalln(err) } for _, bar := range list.Items { fmt.Println(bar.Name) } factory := externalversions.NewSharedInformerFactory(clientset, 0) factory.Crd().V1().Bars().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: nil, UpdateFunc: nil, DeleteFunc: nil, }) // todo } // ==== // 程序輸出結果: bar-demo
代碼生成tag
在我們上面的示例中,我們在源碼中添加瞭很多tag
,我們使用這些tag
來標記一些供生成器使用的屬性。這些tag
主要分為兩類:
- 在
doc.go
的package語句智商提供的全局tag - 在需要被處理的類型上提供局部tag
tag的使用方法如下所示:
// +tag-name // 或者 // +tag-name=value
我們可以看到 tag 是通過註釋的形式存在的,另外需要註意的是 tag 的位置非常重要,很多 tag 必須直接位於 type 或 package 語句的上一行,另外一些則必須和 go 語句隔開至少一行空白。
全局tag
必須在目標包的doc.go
文件中聲明,一般路徑為pkg/apis/<apigroup>/<version>/doc.go,如下所示:
// 為包中任何類型生成深拷貝方法,可以在局部 tag 覆蓋此默認行為 // +k8s:deepcopy-gen=package // groupName 指定 API 組的全限定名 // 此 API 組的 v1 版本,放在同一個包中 // +groupName=crd.example.com package v1
註意:空行不能省略
局部tag
局部tag要麼直接聲明在類型之前,要麼位於類型之前的第二個註釋塊中。下面的 types.go 中聲明瞭 CR 對應的類型:
// 為當前類型生成客戶端,如果不加此註解則無法生成 lister、informer 等包 // +genclient // 提示此類型不基於 /status 子資源來實現 spec-status 分離,產生的客戶端不具有 UpdateStatus 方法 // 否則,隻要類型具有 Status 字段,就會生成 UpdateStatus 方法 // +genclient:noStatus // 為每個頂級 API 類型添加,自動生成 DeepCopy 相關代碼 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // K8S 資源,數據庫 type Database struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec DatabaseSpec `json:"spec"` } // 不為此類型生成深拷貝方法 // +k8s:deepcopy-gen=false // 數據庫的規范 type DatabaseSpec struct { User string `json:"user"` Password string `json:"password"` Encoding string `json:"encoding,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // 數據庫列表,因為 list 獲取的是列表,所以需要定義該結構 type DatabaseList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata"` Items []Database `json:"items"` }
在上面 CR 的定義上面就通過 tag 來添加瞭自動生成相關代碼的一些註釋。此外對於集群級別的資源,我們還需要提供如下所示的註釋:
// +genclient:nonNamespaced // 下面的 Tag 不能少 // +genclient
另外我們還可以控制客戶端提供哪些 HTTP 方法:
// +genclient:noVerbs // +genclient:onlyVerbs=create,delete // +genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watch // 僅僅返回 Status 而非整個資源 // +genclient:method=Create,verb=create,result=k8s.io/apimachinery/pkg/apis/meta/v1.Status // 下面的 Tag 不能少 // +genclient
使用 tag 定義完需要生成的代碼規則後,執行上面提供的代碼生成腳本即可自動生成對應的代碼瞭。
補充
除瞭上面介紹的代碼生成方式,我們還可以直接使用sample-controller
項目提供的hack/update-condegen.sh腳本。
#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. # 代碼生成器包的位置 CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} # generate-groups.sh <generators> <output-package> <apis-package> <groups-versions> # 使用哪些生成器,可選值 deepcopy,defaulter,client,lister,informer,逗號分隔,all表示全部使用 # 輸出包的導入路徑 # CR 定義所在路徑 # API 組和版本 bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ k8s.io/sample-controller/pkg/generated k8s.io/sample-controller/pkg/apis \ samplecontroller:v1alpha1 \ --output-base "$(dirname "${BASH_SOURCE[0]}")/../../.." \ --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt # 自動生成的源碼頭部附加的內容: # --go-header-file "${SCRIPT_ROOT}"/hack/custom-boilerplate.go.txt
執行上面的腳本後,所有 API 代碼會生成在 pkg/apis
目錄下,clientsets、informers、listers 則生成在 pkg/generated
目錄下。不過從腳本可以看出需要將 code-generator 的包放置到 vendor 目錄下面,現在我們都是使用 go modules
來管理依賴保,我們可以通過執行 go mod vendor
命令將依賴包放置到 vendor 目錄下面來。
我們還可以進一步提供 hack/verify-codegen.sh 腳本,用於判斷生成的代碼是否 up-to-date:
#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail # 先調用 update-codegen.sh 生成一份新代碼 # 然後對比新老代碼是否一樣 SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. DIFFROOT="${SCRIPT_ROOT}/pkg" TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" _tmp="${SCRIPT_ROOT}/_tmp" cleanup() { rm -rf "${_tmp}" } trap "cleanup" EXIT SIGINT cleanup mkdir -p "${TMP_DIFFROOT}" cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" "${SCRIPT_ROOT}/hack/update-codegen.sh" echo "diffing ${DIFFROOT} against freshly generated codegen" ret=0 diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" if [[ $ret -eq 0 ]] then echo "${DIFFROOT} up to date." else echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh" exit 1 fi
以上就是go語言代碼生成器code generator簡單介紹的詳細內容,更多關於go語言code generator的資料請關註WalkonNet其它相關文章!