Kubernetes Operator 作為 Kubernetes 的擴充套件機制,能讓開發者以自定義資源和控制器來管理應用。本文介紹如何使用 Kubebuilder 開發 Operator,包含定義 CRD、實作 API 和控制器邏輯。首先,我們會用 Kubebuilder 建立 CRD,定義 EGApp 資源的規格和狀態。接著,將 CRD 佈署到 Kubernetes 叢集,並透過 kubectl 驗證。控制器的核心 Reconcile 函式則負責調整資源狀態,包含檢查資源、驗證和更新。文章也提供程式碼範例,展示如何檢查 EGApp 資源、建立和更新 Deployment,以及更新 EGApp 狀態。最後,我們會探討 Operator 的開發流程、生命週期管理和最佳實踐,讓讀者更全面地掌握 Operator 開發。

Kubernetes Operator 開發:API 與控制器實作

Kubernetes Operator 是一種擴充套件 Kubernetes 功能的機制,允許開發者建立自定義資源(Custom Resource)和控制器(Controller)來管理複雜的應用程式或服務。本文將探討如何使用 Kubebuilder 工具建立一個簡單的 Operator,包括自定義資源定義(CRD)的建立、API 的實作以及控制器的調諧邏輯。

自定義資源定義(CRD)與 API 建立

首先,我們需要建立一個自定義資源定義(CRD)來描述我們想要管理的資源。使用 Kubebuilder,可以透過以下命令建立一個新的 API:

$ kubebuilder create api --group egplatform --version v1alpha1 --kind EGApp

這將生成相關的程式碼和組態檔案,包括 CRD 的定義。CRD 定義了自定義資源的結構,例如 EGApp 資源的 specstatus 部分。

type: object
properties:
  spec:
    type: object
    properties:
      environment:
        type: string
      framework:
        type: string
    required:
      - environment
      - framework
  status:
    type: object
    properties:
      pods:
        type: array
        items:
          type: string
    required:
      - pods

內容解密:

  • 上述 YAML 定義了 EGApp 資源的結構,包括 specstatus 兩個主要部分。
  • spec 部分包含了 environmentframework 兩個屬性,分別代表應用程式的環境和框架。
  • status 部分包含了 pods 屬性,用於記錄與該資源相關的 Pod 狀態。

佈署自定義資源

建立 CRD 後,我們可以透過 Kubebuilder 提供的 make install 命令將 CRD 佈署到 Kubernetes 叢集中:

$ make install

佈署後,可以使用 kubectl explain 命令檢視 EGApp 資源的結構和說明:

$ kubectl explain egapp --recursive

內容解密:

  • kubectl explain 命令用於顯示 Kubernetes 資源的詳細資訊。
  • --recursive 引數表示遞迴顯示所有子欄位的資訊。

控制器調諧邏輯

控制器的核心是調諧邏輯(Reconciliation Logic),負責將實際狀態調整為期望狀態。調諧邏輯在 controllers/<kind>_controller.go 檔案中的 Reconcile 方法中實作。

調諧邏輯的基本流程如下:

  1. 檢查自定義資源是否存在:首先檢查是否有對應的自定義資源例項。
  2. 驗證資源的有效性:如果存在,則進行驗證,確保資源的有效性。
  3. 調整狀態:根據需要調整實際狀態,使其符合期望狀態。
func (r *EGAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // 檢查自定義資源是否存在
    var egapp egplatformv1alpha1.EGApp
    if err := r.Get(ctx, req.NamespacedName, &egapp); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }
    
    // 驗證資源的有效性
    if !egapp.IsValid() {
        // 處理無效資源
    }
    
    // 調整狀態
    // ...
    
    return ctrl.Result{}, nil
}

內容解密:

  • Reconcile 方法是控制器的核心,負責調諧邏輯的實作。
  • 首先,透過 r.Get 方法取得對應的自定義資源例項。
  • 然後,進行資源的有效性驗證。
  • 最後,根據需要調整實際狀態。

資源驗證

資源驗證是 Operator 開發中的重要環節。Kubebuilder 支援多種驗證機制,包括 OpenAPI 驗證、准入控制器(Admission Controller)驗證等。

  1. OpenAPI 驗證:在 CRD 中定義 OpenAPI 驗證規則,可以在資源建立或更新時進行初步驗證。
  2. 准入控制器驗證:透過實作准入控制器,可以對資源進行更複雜的驗證邏輯。

內容解密:

  • OpenAPI 驗證提供了基本的驗證功能,可以防止無效資源進入叢集。
  • 准入控制器驗證提供了更靈活的驗證機制,可以根據具體需求實作自定義驗證邏輯。

控制器實作詳解

在前述範例的基礎上,我們進一步探討控制器的邏輯實作。以下程式碼展示了控制器主要的調節(Reconcile)流程:

// +kubebuilder:rbac:groups=egplatform.platform.evillgenius.com,resources=egapps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=egplatform.platform.evillgenius.com,resources=egapps/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=egplatform.platform.evillgenius.com,resources=egapps/finalizers,verbs=update

func (r *EGAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    logger := log.Log.WithValues("EGApp", req.NamespacedName)
    logger.Info("EGApp Reconcile started...")

    // 抓取 EGApp CR 例項
    egApp := &egplatformv1alpha1.EGApp{}
    err := r.Get(ctx, req.NamespacedName, egApp)
    if err != nil {
        if errors.IsNotFound(err) {
            logger.Info("EGApp 資源未找到。物件必定已被刪除")
            return ctrl.Result{}, nil
        }
        logger.Error(err, "擷取 EGApp 失敗")
        return ctrl.Result{}, err
    }

    // 檢查佈署是否存在,若不存在則建立新的佈署
    found := &appsv1.Deployment{}
    err = r.Get(ctx, types.NamespacedName{Name: egApp.Name, Namespace: egApp.Namespace}, found)
    if err != nil {
        dep := r.deploymentForEGApp(egApp)
        logger.Info("建立新的佈署", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
        err = r.Create(ctx, dep)
        if err != nil {
            logger.Error(err, "建立新佈署失敗", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
            return ctrl.Result{}, err
        }
        return ctrl.Result{}, nil
    }

    // 確保佈署的大小與規格一致
    replicas := egApp.Spec.ReplicaCount
    if *found.Spec.Replicas != replicas {
        found.Spec.Replicas = &replicas
        err = r.Update(ctx, found)
        if err != nil {
            logger.Error(err, "更新佈署失敗", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
            return ctrl.Result{}, err
        }
        return ctrl.Result{Requeue: true}, nil
    }

    // 更新 egApp 狀態中的 Pod 名稱
    podList := &corev1.PodList{}
    listOpts := []client.ListOption{
        client.InNamespace(egApp.Namespace),
        client.MatchingLabels(egApp.GetLabels()),
    }
    if err = r.List(ctx, podList, listOpts...); err != nil {
        logger.Error(err, "列出 Pods 失敗", "egApp.Namespace", egApp.Namespace, "egApp.Name", egApp.Name)
        return ctrl.Result{}, err
    }
    podNames := getPodNames(podList.Items)

    // 若有需要,更新狀態中的 Pods 資訊
    if !reflect.DeepEqual(podNames, egApp.Status.Pods) {
        egApp.Status.Pods = podNames
        err := r.Status().Update(ctx, egApp)
        if err != nil {
            logger.Error(err, "更新 egApp 狀態失敗")
            return ctrl.Result{}, err
        }
    }

    return ctrl.Result{}, nil
}

#### 內容解密
1. **Reconcile 方法**此方法為 Kubernetes 調節迴圈的核心部分負責將叢集的目前狀態調整至預期狀態
2. **資源檢查與建立**首先檢查 EGApp 資源是否存在若不存在則記錄資訊並傳回接著檢查相關的 Deployment 是否存在若不存在則根據 EGApp 的規格建立新的 Deployment
3. **狀態更新**比對 Deployment 的副本數是否符合 EGApp 的規格若不符合則進行更新同時將相關 Pod 的名稱更新至 EGApp 的狀態中

### 輔助函式實作

```go
func (r *EGAppReconciler) deploymentForEGApp(m *egplatformv1alpha1.EGApp) *appsv1.Deployment {
    ls := m.GetLabels()
    replicas := m.Spec.ReplicaCount
    deploy := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      m.Name,
            Namespace: m.Namespace,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: &replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: ls,
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: ls,
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{{
                        Image: "gcr.io/kuar-demo/kuard-amd64:1",
                        Name:  m.Spec.AppId,
                        Ports: []corev1.ContainerPort{{
                            ContainerPort: 8080,
                            Name:          "http",
                        }},
                    }},
                },
            },
        },
    }
    ctrl.SetControllerReference(m, deploy, r.Scheme)
    return deploy
}

func getPodNames(pods []corev1.Pod) []string {
    var podNames []string
    for _, pod := range pods {
        podNames = append(podNames, pod.Name)
    }
    return podNames
}

#### 內容解密:
1. **deploymentForEGApp 方法**:根據 EGApp 的規格建立對應的 Deployment 物件,並設定相關的標籤和容器資訊。
2. **getPodNames 方法**:從 Pod 清單中擷取 Pod 的名稱。

### 控制器設定

```go
func (r *EGAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&egplatformv1alpha1.EGApp{}).
        Complete(r)
}

#### 內容解密
1. **SetupWithManager 方法**設定控制器與 Manager 的關聯並指定監控的資源型別為 EGApp

測試與驗證

完成控制器邏輯後,可以透過 make run 命令在本機執行控制器程式碼,以驗證其正確性。接著,可以將其封裝成容器並佈署至叢集中進行實際操作驗證。

重點整理

  • 本文詳細介紹了控制器的實作細節,包括 Reconcile 流程、資源檢查與建立、狀態更新等關鍵步驟。
  • 同時提供了輔助函式的實作,如 deploymentForEGAppgetPodNames,以及控制器的設定方法 SetupWithManager
  • 最後,簡要說明瞭如何測試與驗證控制器的正確性。

Kubernetes Operator 開發與最佳實踐

Kubernetes Operator 是一種擴充套件 Kubernetes 功能的機制,允許開發者將自定義的應用邏輯與 Kubernetes 的核心功能整合。本文將探討 Operator 的開發流程、生命週期管理以及最佳實踐。

Operator 開發流程

Operator 的開發涉及多個步驟,包括定義自定義資源(CRD)、實作調節邏輯以及處理版本升級等。

自定義資源定義(CRD)

CRD 是 Operator 的基礎,它定義了 Operator 所管理的資源型別。例如:

apiVersion: egplatform.platform.evillgenius.com/v1alpha1
kind: EGApp
metadata:
  labels:
    app.Kubernetes.io/name: egapp
    app.Kubernetes.io/instance: egapp-sample
    app.Kubernetes.io/part-of: pe-app
    app.Kubernetes.io/managed-by: kustomize
    app.Kubernetes.io/created-by: pe-app
  name: egapp-sample
spec:
  appId: egapp-sample
  framework: go
  instanceType: lowMem
  environment: dev
  replicaCount: 2

調節邏輯實作

Operator 的核心是調節邏輯,它負責監控 CR 的變化並作出相應的處理。例如,當建立一個 EGApp 資源時,Operator 會建立一個 Deployment:

// EGApp Reconcile started...
// Creating a new deployment {"EGApp": "default/egapp-sample", "Deployment.Namespace": "default", "Deployment.Name": "egapp-sample"}

版本升級處理

隨著 Operator 的發展,可能需要支援多個版本的 CRD。當引入新版本時,需要實施轉換過程,以確保現有資源的相容性。這可以透過轉換 Webhook 來實作:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        service:
          namespace: egapp-conversion
          name: egapp
          path: /egapp-conversion
          port: 8081
        caBundle: "Hf8j0Kw...<base64-encoded PEM bundle>...tLS0K"

Operator 生命週期管理

Operator 的開發不是一次性的,而是一個持續的過程。CoreOS 和 RedHat 提出了 Operator Capability Levels,用於指導 Operator 的發展:

  1. Basic install:基本安裝功能。
  2. Automated application provisioning and configuration management:自動化的應用程式組態管理。
  3. Seamless upgrades:無縫升級。
  4. Full life cycle:完整的生命週期管理,包括備份和故障還原。
  5. Deep insights:深入的洞察力,包括指標、警示、日誌處理和工作負載分析。
  6. Auto pilot:自動駕駛,包括水平/垂直擴充套件、自動組態調整、異常檢測和排程調整。

最佳實踐

  1. 單一應用原則:一個 Operator 只管理一個應用。
  2. 多控制器原則:如果 Operator 管理多個 CRD,則應有多個控制器。
  3. 名稱空間無關:Operator 不應與特定的名稱空間繫結。
  4. 版本控制:使用語義版本控制,並遵循 Kubernetes API 版本控制。
  5. OpenAPI 規範:CRD 應遵循 OpenAPI 規範,以確保已知的模式。

內容解密:

上述最佳實踐是為了確保 Operator 的可維護性、可擴充套件性和可靠性。遵循這些原則,可以幫助開發者建立出高效、穩定的 Operator,從而更好地管理和維運 Kubernetes 上的應用。