使用 Golang 構建你的第一個 k8s Operator

本文將展示如何使用 Operator SDK[1] 搭建一個基本的 k8s Operator。在本文中,您將瞭解如何創建一個新項目,並通過創建自定義資源定義 (CRD) 和基本控制器來添加 API。

我們將在 CRD 中添加字段,以包含一些有關期望狀態和實際狀態的信息,修改控制器以調和新資源的實例,然後將 operator 部署到 Kubernetes 集羣。

Prerequisites

Step 1: Create a project

建一個目錄,並初始化一個 operator 項目:

$ mkdir memcached-operator
$ cd memcached-operator
$ operator-sdk init --domain=example.com --repo=github.com/example/memcached-operator
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.7.0
Update go.mod:
$ go mod tidy

Running make:
$ make
go: creating new go.mod: module tmp
Downloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1
go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.4.1
/Users/username/workspace/projects/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go
operator-sdk init — domain=example.com — repo=github.com/example/memcached-operator

大家可能熟悉的一個 API Group 是 rbac.authorization.k8s.io,創建 RBAC 資源(如 ClusterRoles 和 ClusterBindings)的功能通常就設置在 Kubernetes 集羣上。operator-sdk 允許您指定一個自定義域,將其附加到您定義的任何 API 組,以幫助避免名稱衝突。

這裏使用的 --repo 值只是一個示例。如果你想提交項目並保留它,可以將其設置爲你可以訪問的 Git 倉庫。

項目初始化後會生生一個 Operator 項目的殼子,我們剩下的工作就是在這個框架之上,實現 operator 的功能。

Step 2: Create an API

使用 create 命令生成 CRD 和控制器:

注意:--version 標誌針對的是操作符的 Kubernetes API 版本,而不是語義版本。因此,請勿在 --version 中使用

$ operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource=true --controller=true


Writing scaffold for you to edit...
api/v1alpha1/memcached_types.go
controllers/memcached_controller.go
Running make:

$ make
/Users/username/workspace/projects/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go

--group 是自定義資源所在的組,因此它最終會出現在 API Group "cache.example.com" 中。

--version 決定 API Group 的版本。可以使用不同的版本連續升級自定義資源。

-resource--controller 標誌設置爲 "true",以便爲這兩樣東西生成腳手架。

現在我們已經有了組件的輪廓,讓我們開始用實際功能來填充它們。首先是 CRD。在 api/v1alpha1/memcached_types.go 中,您應該能看到一些定義自定義資源類型規格和狀態的結構:

// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {
    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
    // Important: Run "make" to regenerate code after modifying this file
    // Foo is an example field of Memcached. Edit Memcached_types.go to remove/update
    Foo string `json:"foo,omitempty"`
}
// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {
    // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
    // Important: Run "make" to regenerate code after modifying this file
}

請注意文件頂部的信息。該文件是由 Operator-SDK 搭建的腳手架,我們可以根據自己的項目需求去修改。Spec 包含指定資源所需狀態的信息,而 Status 則包含系統的可觀測狀態,尤其是其他資源可能想要使用的信息。這些 Golang 結構與 Kubernetes 用戶爲創建自定義資源類型實例而編寫的 YAML 直接對應。

讓我們爲類型添加一些基本字段。

// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {
    // +kubebuilder:validation:Minimum=0
    // Size is the size of the memcached deployment
    Size int32 `json:"size"`
}
// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {
    // Nodes are the names of the memcached pods
    Nodes []string `json:"nodes"`
}

Size 是一個整數,用於確定 Memcached 集羣中的節點數量。我們在 Status 中添加了一個字符串數組,用於存儲集羣中包含的節點的 IP 地址。需要注意的是,這個特定的實現方式只是一個示例。注意父 Memcached 結構 Status 字段的 Kubebuilder 子資源標記。這將在生成的 CRD 清單中添加 Kubernetes 狀態子資源。這樣,控制器就可以只更新狀態字段,而無需更新整個對象,從而提高性能。

// Memcached is the Schema for the memcacheds API
// +kubebuilder:subresource:status
type Memcached struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec   MemcachedSpec   `json:"spec,omitempty"`
    Status MemcachedStatus `json:"status,omitempty"`
}

更改 types.go 文件後,應始終在項目根目錄下運行以下命令:

make generate

此 make target 會調用 controller-gen 更新 api/v1alpha1/zz_generated.deepcopy.go,使其包含您剛剛添加的字段的必要實現。完成更新後,我們應運行以下命令爲 CRD 生成 YAML 清單:

$ make manifests

會生成以下文件:

New:
        config/crd/bases/cache.example.com_memcacheds.yaml
        config/rbac/role.yaml

config/crd/bases/cache.example.com_memcacheds.yaml 是 memcached CRD 的清單。config/rbac/role.yaml 是 RBAC 清單,其中包含控制器所需的管理 memcached 類型的權限。

Step 3: Create a controller

讓我們看看 controllers/memcached_controller.go 中目前包含的內容:

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Memcached object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = r.Log.WithValues("memcached", req.NamespacedName)
    // your logic here
    return ctrl.Result{}, nil
}

Reconcile 方法負責將自定義資源狀態中包含的期望狀態與系統上運行的實際狀態進行覈對,也是實現控制器邏輯的主要部分。實現調和循環的具體細節超出了本教程的範圍,將在進階的文章中介紹。現在,請用此參考實現替換 controllers/memcached_controller.go。注意,如果你在初始化項目時指定了不同的 repo,可能需要更改 github.com/example/memcached-operator/api/v1alpha1 的導入路徑,以便正確指向你定義 memcached_types.goin 的目錄。粘貼後,確保重新生成清單:

make manifests

Step 4: Build and deploy your operator

現在您已經填寫了所有需要的組件,是時候部署 operator 了!一般來說,有三種不同的方法來部署:

現在,先構建並推送控制器的 Docker image。本示例使用了基礎 Dockerfile,但你也可以根據自己的需要進行修改。我使用 Docker Hub 作爲鏡像倉庫,但你能推 / 拉訪問的任何倉庫都可以。

export USERNAME=<docker-username>
$ make docker-build docker-push IMG=docker.io/$USERNAME/memcached-operator:v1.0.0

如果出現如下錯誤,則可能需要獲取其他依賴項。運行建議的命令下載依賴項。

/controllers: package k8s.io/api/apps/v1 imported from implicitly required module; to add missing requirements, run:
    go get k8s.io/api/apps/v1@v0.19.2

我們還可以在 Makefile 中設置鏡像的默認名稱和標籤。鏡像構建完成後,就可以部署 operator 了:

$ make deploy IMG=docker.io/$USERNAME/memcached-operator:v1.0.0

它使用 config/ 中的清單創建 CRD,在 Pod 中部署控制器,創建控制器管理 CRD 所需的 RBAC 角色,並將其分配給控制器。讓我們來看看。我們的 CRD 類型爲 memcacheds.cache.example.com:

$ kubectl get crds
NAME                                          CREATED AT
catalogsources.operators.coreos.com           2021-01-22T00:13:22Z
clusterserviceversions.operators.coreos.com   2021-01-22T00:13:22Z
installplans.operators.coreos.com             2021-01-22T00:13:22Z
memcacheds.cache.example.com                   2021-02-06T00:52:38Z
operatorgroups.operators.coreos.com           2021-01-22T00:13:22Z
rbacsyncs.ibm.com                             2021-01-22T00:08:59Z
subscriptions.operators.coreos.com            2021-01-22T00:13:22Z

運行控制器的部署和 Pod:

$ kubectl --namespace memcached-operator-system get deployments
NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
memcached-operator-controller-manager   1/1     1            1           2m18s

$ kubectl --namespace memcached-operator-system get pods 
NAME                                                     READY   STATUS    RESTARTS   AGE
memcached-operator-controller-manager-76b588bbb5-wvl7b   2/2     Running   0          2m44s

當 pod 開始運行,我們的 Operator 就可以使用了。編輯 config/samples/cache_v1alpha1_memcached.yaml 中的示例 YAML,加入一個大小整數,就像在自定義資源規範中定義的那樣:

apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
  name: memcached-sample
spec:
  size: 1

然後創建一個自定義資源的新實例:

$ kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml 
memcached.cache.example.com/memcached-sample created

再看看新的自定義資源和控制器在後臺創建的對象:

$ kubectl get memcached
NAME               AGE
memcached-sample   18s

$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
memcached-sample   1/1     1            1           33s

$ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
memcached-sample-9b765dfc8-2hvf8   1/1     Running   0          44s

如果查看一下 Memcached 對象,就會發現狀態已用運行節點的名稱更新:

$ kubectl get memcached memcached-sample -o yaml
apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"cache.example.com/v1alpha1","kind":"Memcached","metadata":{"annotations":{},"name":"memcached-sample","namespace":"default"},"spec":{"size":1}}
  creationTimestamp: "2021-03-29T19:22:53Z"
  generation: 1
  managedFields:
  - apiVersion: cache.example.com/v1alpha1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .: {}
          f:kubectl.kubernetes.io/last-applied-configuration: {}
      f:spec:
        .: {}
        f:size: {}
    manager: kubectl
    operation: Update
    time: "2021-03-29T19:22:53Z"
  - apiVersion: cache.example.com/v1alpha1
    fieldsType: FieldsV1
    fieldsV1:
      f:status:
        .: {}
        f:nodes: {}
    manager: manager
    operation: Update
    time: "2021-03-29T19:22:58Z"
  name: memcached-sample
  namespace: default
  resourceVersion: "1374"
  uid: 63c7b1b1-1a75-49e6-8132-2164807a1b78
spec:
  size: 1
status:
  nodes:
- memcached-sample-9b765dfc8-2hvf8

要查看控制器的運行情況,可以在集羣中添加另一個節點。將 config/samples/cache_v1alpha1_memcached.yaml 中的大小改爲 2,然後運行:

$ kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
memcached.cache.example.com/memcached-sample configured

查看創建的新 pod:

$ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
memcached-sample-9b765dfc8-2hvf8   1/1     Running   0          50s
memcached-sample-9b765dfc8-jdhlq   1/1     Running   0          3s

然後看到 Memcached 對象再次更新爲新 pod 的名稱:

$ kubectl get memcached memcached-sample -o yaml
apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"cache.example.com/v1alpha1","kind":"Memcached","metadata":{"annotations":{},"name":"memcached-sample","namespace":"default"},"spec":{"size":1}}
  creationTimestamp: "2021-03-29T19:22:53Z"
  generation: 2
  managedFields:
  - apiVersion: cache.example.com/v1alpha1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .: {}
          f:kubectl.kubernetes.io/last-applied-configuration: {}
      f:spec:
        .: {}
        f:size: {}
    manager: kubectl
    operation: Update
    time: "2021-03-29T19:22:53Z"
  - apiVersion: cache.example.com/v1alpha1
    fieldsType: FieldsV1
    fieldsV1:
      f:status:
        .: {}
        f:nodes: {}
    manager: manager
    operation: Update
    time: "2021-03-29T19:22:58Z"
  name: memcached-sample
  namespace: default
  resourceVersion: "1712"
  uid: 63c7b1b1-1a75-49e6-8132-2164807a1b78
spec:
  size: 2
status:
  nodes:
  - memcached-sample-9b765dfc8-2hvf8
  - memcached-sample-9b765dfc8-jdhlq

Step 5: Cleanup

完成後,可以通過運行這些命令來清理已部署的 operator:

$ kubectl delete memcached memcached-sample

$ make undeploy

Debugging

查看 operator 管理器日誌:

$ kubectl logs deployment.apps/memcached-operator-controller-manager -n memcached-operator-system -c manager

參考資料

[1]

operator sdk: https://github.com/operator-framework/operator-sdk

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/kSxetwU8oSgWeyCmCR6Dpw