Kubernetes CRD开发汇总
1. Kubernetes CRD開發
1.1 kubernetes 自定義資源(CRD)
在研究 Service Mesh 的過程中,發現 Istio 很多參數都通過 kubernetes CRD 來管理,例如 VirtualService 和 DestinationRule,這種方式使部署在 k8s 集群上的服務的管理方式更趨向一致。
kubernetes 的資源管理方式和聲明式 API 的良好設計使得在這個平臺上的功能擴展變得異常容易。例如 CoreOS 推出的 Operator 框架就是一個很好的例子。
這篇文章通過一個簡短的示例來演示如何創建自定義資源。
1.1.1 創建 CRD(CustomResourceDefinition)
這里以創建一個簡單的彈性伸縮配置的 CRD 為例。將下面的內容保存在 scaling_crd.yaml 文件中。
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata:# name must match the spec fields below, and be in the form: <plural>.<group>name: scalings.control.example.com spec:# group name to use for REST API: /apis/<group>/<version>group: control.example.com# list of versions supported by this CustomResourceDefinitionversions:- name: v1# Each version can be enabled/disabled by Served flag.served: true# One and only one version must be marked as the storage version.storage: true# either Namespaced or Clusterscope: Namespacednames:# plural name to be used in the URL: /apis/<group>/<version>/<plural>plural: scalings# singular name to be used as an alias on the CLI and for displaysingular: scaling# kind is normally the CamelCased singular type. Your resource manifests use this.kind: Scaling# shortNames allow shorter string to match your resource on the CLIshortNames:- sc通過 kubectl 創建這個 CRD:
kubectl apply -f scaling_crd.yaml1.1.2 創建自定義資源的對象
我們編寫一個 test.yaml 文件來創建一個自定義的 Scaling 對象。
apiVersion: "control.example.io/v1" kind: Scaling metadata:name: test spec:targetDeployment: testminReplicas: 1maxReplicas: 5metricType: CPUstep: 1scaleUp: 80scaleDown: 40通過 kubectl 創建:
kubectl apply -f test.yaml提示:
scaling.control.example.io/test created你可以通過 kubectl 查看已經創建的名為 test 的 Scaling 對象。
kubectl get scalings.control.example.io test -o yaml會輸出類似如下的結果:
apiVersion: control.example.io/v1 kind: Scaling metadata:annotations:kubectl.kubernetes.io/last-applied-configuration: |{"apiVersion":"control.example.io/v1","kind":"Scaling","metadata":{"annotations":{},"name":"test","namespace":"default"},"spec":{"maxReplicas":5,"metricType":"CPU","minReplicas":1,"scaleDown":40,"scaleUp":80,"step":1,"targetDeployment":"test"}}creationTimestamp: "2019-01-09T12:22:36Z"generation: 1name: testnamespace: defaultresourceVersion: "1316610"selfLink: /apis/control.example.io/v1/namespaces/default/scalings/testuid: 28717b37-5ac2-11e9-89f8-080027a9fd96 spec:maxReplicas: 5metricType: CPUminReplicas: 1scaleDown: 40scaleUp: 80step: 1targetDeployment: test我們可以像操作 k8s 內置的 Deployment 資源一樣操作我們創建的 Scaling 資源,同樣可以對它進行更新和刪除的操作。
1.1.3 參數校驗
上面的 CRD 配置中我們并沒有指定這個資源的 Spec,也就是說用戶可以使用任意的 Spec 創建這個 Scaling 資源,這并不符合我們的要求。我們希望在用戶創建 Scaling 對象時,可以像 k8s 的原生資源一樣進行參數校驗,如果出錯的情況下,就不會去創建或更新這個對象,而是給用戶錯誤提示。
k8s 目前提供了兩種方式來實現參數校驗,OpenAPI v3 schema 和 validatingadmissionwebhook。
這里主要使用比較簡單的 OpenAPI v3 schema 來實現。validatingadmissionwebhook 需要用戶自己提供一個檢查服務,通過創建 ValidatingWebhookConfiguration 讓 APIServer 將指定的操作請求轉發給這個檢查服務,檢查服務返回 true 或者 false,決定參數校驗是否成功。
我們將之前的 CRD 配置文件 scaling_crd.yaml 做一下修改,增加參數校驗的部分:
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata:name: scalings.control.example.io spec:group: control.example.ioversions:- name: v1served: truestorage: truescope: Namespacednames:plural: scalingssingular: scalingkind: Scalingvalidation:openAPIV3Schema:properties:spec:required:- targetDeployment- minReplicas- maxReplicas- metricType- step- scaleUp- scaleDownproperties:targetDeployment:type: stringminReplicas:type: integerminimum: 0maxReplicas:type: integerminimum: 0metricType:type: stringenum:- CPU- MEMORY- REQUESTSstep:type: integerminimum: 1scaleUp:type: integerscaleDown:type: integerminimum: 0可以看到 spec 中增加了 validation 字段,其中定義了對各個參數的檢驗要求。
- required 表示數組中的參數必須要設置。
- type string 和 type integer 表示限制參數類型。
- minimum: 0 表示數字最小值為 0。
- enum 表示參數只能在指定的值中。
具體支持哪些校驗方法可以通過 https://github.com/OAI/OpenAPI-Specification 查看。
更新 CRD 資源:
kubectl apply -f scaling_crd.yaml再次修改 test.yaml 測試我們的參數校驗是否生效,將 targetDeployment: test 這一行刪除。
更新 Name 為 test 的 Scaling 對象。
kubectl apply -f test.yaml可以看到錯誤提示輸出如下:
validation failure list: spec.targetDeployment in body is required至此,不符合我們要求的 Scaling 對象將不被允許創建。
1.2 kubernetes 自定義控制器
kubernetes 的 controller-manager 通過 APIServer 實時監控內部資源的變化情況,通過各種操作將系統維持在一個我們預期的狀態上。比如當我們將 Deployment 的副本數增加時,controller-manager 會監聽到此變化,主動創建新的 Pod。
對于通過 CRD 創建的資源,也可以創建一個自定義的 controller 來管理。
1.2.1 目的
在上文中我們創建了自己的 Scaling 資源,如果我們想要通過監聽該資源的變化來實現實時的彈性伸縮,就需要自己寫一個控制器,通過 APIserver watch 該資源的變化。
當我們創建了一個 Scaling 對象,自定義控制器都能獲得其參數,之后執行相關的檢查,根據結果決定是否需要擴容或縮容相關的實例。
1.2.2 實現
client-go 這個 repo 封裝了對 k8s 內置資源的一些常用操作,包括了 clients/listers/informer 等對象和函數,可以 通過 Watch 或者 Get List 獲取對應的 Object,并且通過 Cache,可以有效避免對 APIServer 頻繁請求的壓力。
但是對于我們自己創建的 CRD,沒有辦法直接使用這些代碼。
通過 code-generator 這個 repo,我們可以提供自己的 CRD 相關的結構體,輕松的生成 client-go 中類似的代碼,方便我們編寫自己的控制器。
1.2.3 在自己的項目中使用 code-generator
這里主要參考了 sample-controller 這個項目。
1.2.3.1 創建自定義 CRD 結構體
假設我們有一個 test repo,在根目錄創建一個 pkg 目錄,用于存放我們自定義資源的 Spec 結構體。
這里我們要知道自己創建的自定義資源的相關內容:
- API Group: 我們使用的是 control.example.com。
- Version: 我們用的是 v1,但是可以同時存在多個版本。
- 資源名稱: 這里是 Scaling。
接著創建如下的目錄結構:
mkdir -p pkg/apis/control/v1在 pkg/apis/control 目錄下創建一個 register.go 文件。內容如下:
package controlconst (GroupName = "control.example.com" )創建 pkg/apis/control/v1/types.go 文件,內容如下:
package v1import (metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" )// +genclient // +genclient:noStatus // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Objecttype Scaling struct {metav1.TypeMeta `json:",inline"`metav1.ObjectMeta `json:"metadata,omitempty"`Spec ScalingSpec `json:"spec"` }type ScalingSpec struct {TargetDeployment string `json:"targetDeployment"`MinReplicas int `json:"minReplicas"`MaxReplicas int `json:"maxReplicas"`MetricType string `json:"metricType"`Step int `json:"step"`ScaleUp int `json:"scaleUp"`ScaleDown int `json:"scaleDown"` }// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Objecttype ScalingList struct {metav1.TypeMeta `json:",inline"`metav1.ListMeta `json:"metadata,omitempty"`Items []Scaling `json:"items"` }這個文件中我們定義了 Scaling 這個自定義資源的結構體。
其中,類似 // +<tag_name>[=value] 這樣格式的注釋,可以控制代碼生成器的一些行為。
- +genclient: 為這個 package 創建 client。
- +genclient:noStatus: 當創建 client 時,不存儲 status。
- +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object: 為結構體生成 deepcopy 的代碼,實現了 runtime.Object 的 Interface。
創建 doc 文件,pkg/apis/control/v1/doc.go:
// +k8s:deepcopy-gen=package // +groupName=control.example.compackage v1最后 client 對于自定義資源結構還需要一些接口,例如 AddToScheme 和 Resource,這些函數負責將結構體注冊到 schemes 中去。
為此創建 pkg/apis/control/v1/register.go 文件:
package v1import ("test/pkg/apis/control"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/apimachinery/pkg/runtime""k8s.io/apimachinery/pkg/runtime/schema" )var SchemeGroupVersion = schema.GroupVersion{Group: control.GroupName,Version: "v1", }func Resource(resource string) schema.GroupResource {return SchemeGroupVersion.WithResource(resource).GroupResource() }var (// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.SchemeBuilder runtime.SchemeBuilderlocalSchemeBuilder = &SchemeBuilderAddToScheme = localSchemeBuilder.AddToScheme )func init() {// We only register manually written functions here. The registration of the// generated functions takes place in the generated files. The separation// makes the code compile even when the generated files are missing.localSchemeBuilder.Register(addKnownTypes) }// Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) error {scheme.AddKnownTypes(SchemeGroupVersion,&Scaling{},&ScalingList{},)metav1.AddToGroupVersion(scheme, SchemeGroupVersion)return nil }至此,初期的準備工作已近完成,可以通過代碼生成器來自動幫助我們生成相關的 client, informer, lister 的代碼。
1.2.3.2 生成代碼
通常我們通過創建一個 hack/update-codegen.sh 腳本來固化生成代碼的步驟。
$GOPATH/src/k8s.io/code-generator/generate-groups.sh all \ test/pkg/client \ test/pkg/apis \ control:v1可以看到,執行這個腳本,需要使用 code-generator 中的的腳本,所以需要先通過 go get 將 code-generator 這個 repo 的內容下載到本地,并且編譯出相關的二進制文件(client-gen, informer-gen, lister-gen)。
執行完成后,可以看到 pkg 目錄下多了一個 client 目錄,其中就包含了 informer 和 lister 相關的代碼。
并且在 pkg/apis/control/v1 目錄下,會多一個 zz_generated.deepcopy.go 文件,用于 deepcopy 相關的處理。
1.2.3.3 創建自定義控制器代碼
這里只創建一個 main.go 文件用于簡單示例,通過我們剛剛自動生成的代碼,每隔一段時間,自動通過 lister 獲取所有的 Scaling 對象。
package mainimport ("fmt""log""os""time""k8s.io/apimachinery/pkg/labels""k8s.io/client-go/tools/clientcmd"clientset "test/pkg/client/clientset/versioned"informers "test/pkg/client/informers/externalversions" )func main() {client, err := newCustomKubeClient()if err != nil {log.Fatalf("new kube client error: %v", err)}factory := informers.NewSharedInformerFactory(client, 30*time.Second)informer := factory.Control().V1().Scalings()lister := informer.Lister()stopCh := make(chan struct{})factory.Start(stopCh)for {ret, err := lister.List(labels.Everything())if err != nil {log.Printf("list error: %v", err)} else {for _, scaling := range ret {log.Println(scaling)}}time.Sleep(5 * time.Second)} }func newCustomKubeClient() (clientset.Interface, error) {kubeConfigPath := os.Getenv("HOME") + "/.kube/config"config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)if err != nil {return nil, fmt.Errorf("failed to create out-cluster kube cli configuration: %v", err)}cli, err := clientset.NewForConfig(config)if err != nil {return nil, fmt.Errorf("failed to create custom kube client: %v", err)}return cli, nil }編譯并執行此代碼,每隔 5 秒鐘,會在標準輸出中輸出我們創建的所有 Scaling 對象的具體內容。
需要注意的是,這里生成的 kube client 只能用于操作我們自己的 Scaling 對象。如果需要操作 Deployment 這一類的內置的資源,仍然需要使用 client-go 中的代碼,因為不同的 clientset.Interface 實現的接口也是不同的。
上述的方法也是最頂層的實現方式,下面介紹兩種可以快速搭建CRD開發的工具:一種是kubebuilder,另一種是operader-sdk,該工具目前正在與kubebuilder融合,其中kubebuilder是一個官方提供的快速實現Operator的工具包,可以快速生成k8s的CRD、Controller、Webhook,我們只需要實現業務邏輯。
kubebuilder封裝了controller-runtime和controller-tools工具,通過controller-gen來生成代碼,提供腳手架工具初始化 CRDs 工程,自動生成 boilerplate 代碼和配置;提供代碼庫封裝底層的 K8s client-go;簡化了用戶創建Operator的步驟:
1.3 kubebuilder 開發自定義資源(CRD)
1.3.1 創建腳手架工程
kubebuilder init --domain edas.io1這一步創建了一個 Go module 工程,引入了必要的依賴,創建了一些模板文件。
1.3.2 創建 API
kubebuilder create api --group apps --version v1alpha1 --kind Application1這一步創建了對應的 CRD 和 Controller 模板文件,經過 1、2 兩步,現有的工程結構如圖 2 所示:
1.3.3 定義 CRD
在上圖中對應的文件edasapplication_types.go定義 Spec 和 Status。
1.3.4 編寫 Controller 邏輯
在上圖中對應的文件edasapplication_controller.go實現 Reconcile 邏輯。
1.3.5 測試發布
本地測試完之后使用 Kubebuilder 的 Makefile 構建鏡像,部署我們的 CRDs 和 Controller 即可。
1.4 operator-sdk 開發自定義資源(CRD)
該 SDK 提供了一個工作流程,用于使用 Go、 Ansible 或 Helm來開發operators。
下面的工作流用于創建新的 Go operator:
下面的工作流用于創建新的Ansible operator:
下面的工作流用于創建新的Helm operator:
下面就以Go來創建一個operators開發示例
1.4.1 創建腳手架工程
$ operator-sdk new app-operator $ cd app-operator1.4.2 創建API
$ operator-sdk add api --api-version=app.example.com/v1alpha1 --kind=AppService1.4.3 創建控制器
$ operator-sdk add controller --api-version=app.example.com/v1alpha1 --kind=AppService1.4.4 編譯并PUSH鏡像
$ operator-sdk build quay.io/example/app-operator $ docker push quay.io/example/app-operator1.4.5 測試發布
$ kubectl create -f deploy/1.5 總結
通過上述介紹來看Kubernetes 中CRD 的開發方式有多種,其中第一種不借用工具的開發方式其實使用的方法是調用client-go 和 code-generate兩個工具庫中的方法實現CRD資源的管理,涉及的知識點也相對底層,如果閱讀了k8s kube-apiserver源碼的人更加容易理解這種開發方式;通過kubebuilder和operator-sdk這兩種工具的開發方式其實都是將client-go、controller-runtime和controller-tools代碼進行了再封裝,封裝后的庫為controller-gen,其目的是簡化用戶在不理解kube-apiserver等實現的基礎上開發CRD的流程。
總結
以上是生活随笔為你收集整理的Kubernetes CRD开发汇总的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: controller-runtime 控
- 下一篇: Kubernetes API 聚合开发汇