返回文章列表

Helm Operator 實戰:Kubernetes 資源自動化管理與安全實務

深入探討 Helm Operator 的建置與應用,從自定義資源定義到操作員生命週期管理,涵蓋完整的 Kubernetes 資源自動化方案與 GPG 數位簽章驗證機制,提供企業級的安全實務指南

DevOps 容器化技術

在 Kubernetes 生態系統的演進過程中,資源管理的自動化需求日益迫切。隨著應用程式架構的複雜度提升,傳統的手動操作模式已無法滿足現代雲端原生應用的部署與維護需求。Helm Operator 的出現為這個挑戰提供了優雅的解決方案,它結合了 Helm 的套件管理能力與 Kubernetes Operator 模式的自動化特性,讓開發團隊能夠以宣告式的方式定義應用程式的期望狀態,並由操作員自動維持實際狀態與期望狀態的一致性。本文將深入探討 Helm Operator 的核心概念與實作技巧,從自定義資源定義的建立、操作員的部署與管理,到完整的生命週期控制機制。此外,我們也將探討 Helm 生態系統中的安全性議題,特別是如何透過 GPG 數位簽章機制來驗證 Helm 下載的來源與完整性,確保整個自動化流程的安全可靠。

Operator 模式的核心理念與架構設計

Kubernetes Operator 模式代表了雲端原生應用管理的重要演進。在傳統的 Kubernetes 資源管理模式中,使用者需要手動執行一系列的 kubectl 指令來建立、更新或刪除資源。這種操作方式不僅繁瑣,更缺乏對應用程式領域知識的封裝。Operator 模式的提出,正是為了將應用程式的運維知識程式化,讓複雜的管理邏輯能夠被自動執行。

Operator 的核心機制建立在 Kubernetes 的控制迴圈概念之上。每個 Operator 都是一個持續運行的控制器程式,它不斷地觀察特定資源的狀態,並在偵測到實際狀態與期望狀態存在差異時,採取必要的協調動作來消除這個差異。這種持續的狀態協調機制,確保了應用程式能夠始終維持在預期的運行狀態,即使面對手動修改或意外故障也能自動恢復。

在 Helm Operator 的架構中,這個協調機制與 Helm 的套件管理功能深度整合。當使用者建立一個自定義資源時,Operator 會將這個資源的規格轉換為 Helm Chart 的安裝參數,並透過 Helm 執行實際的部署操作。後續當自定義資源被修改時,Operator 會偵測到這個變更並觸發 Helm 的升級流程。當自定義資源被刪除時,Operator 則會執行 Helm 的解除安裝操作。這種設計讓使用者能夠透過簡單的自定義資源操作,來控制完整的 Helm 發布生命週期。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi 300
skinparam shadowing false
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 14
skinparam roundcorner 8
skinparam minClassWidth 140

package "使用者操作層" {
  actor "叢集管理員" as Admin
  actor "應用開發者" as Developer
}

package "Kubernetes API 層" {
  rectangle "API Server" as API
  database "etcd\n狀態儲存" as ETCD
}

package "Operator 控制層" {
  rectangle "Helm Operator\n控制器" as Operator {
    component "狀態監控\n模組" as Monitor
    component "協調引擎" as Reconcile
    component "Helm 整合\n介面" as HelmAPI
  }
}

package "資源定義層" {
  rectangle "Custom Resource\nDefinition (CRD)" as CRD
  rectangle "Custom Resource\n(CR)" as CR
}

package "應用程式層" {
  rectangle "Helm Chart\n倉庫" as ChartRepo
  rectangle "部署的\n應用程式" as App
}

Admin --> CRD : 建立 CRD
Developer --> CR : 建立/修改/刪除 CR

CR --> API : 提交變更
API --> ETCD : 儲存狀態

Monitor --> API : 監控 CR 變更
Monitor --> Reconcile : 觸發協調
Reconcile --> HelmAPI : 執行 Helm 操作

HelmAPI --> ChartRepo : 取得 Chart
HelmAPI --> App : 部署/更新/刪除

App --> API : 回報狀態
API --> CR : 更新狀態欄位

@enduml

此架構圖清楚展示了 Helm Operator 系統的完整運作流程。使用者透過建立或修改自定義資源來表達期望狀態,這些變更被儲存在 Kubernetes API Server 中。Operator 的監控模組持續觀察自定義資源的狀態變化,當偵測到變更時觸發協調引擎。協調引擎分析期望狀態與實際狀態的差異,並透過 Helm 整合介面執行必要的操作。最終,部署的應用程式狀態會被回報到自定義資源的狀態欄位中,形成完整的回饋迴圈。

自定義資源定義的設計與實作

在建置 Helm Operator 之前,首要任務是設計合適的自定義資源定義。CRD 本質上是 Kubernetes API 的擴展機制,讓我們能夠定義新的資源類型,就如同使用內建的 Pod、Service 等資源一樣。一個設計良好的 CRD 應該清晰地表達應用程式的期望狀態,同時提供足夠的彈性來因應不同的部署場景。

以 Guestbook 應用程式為例,我們需要定義一個 CRD 來描述這個應用程式的部署規格。這個 CRD 應該包含應用程式的核心配置項目,例如副本數量、容器映像版本、資源限制等。同時,CRD 也應該定義狀態欄位,用於記錄部署的實際狀態,讓使用者能夠查詢應用程式的運行情況。

# Guestbook CRD 定義
# 定義 Guestbook 應用程式的自定義資源規格
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # CRD 的完整名稱
  # 遵循 <plural>.<group> 的命名規範
  name: guestbooks.charts.helm.k8s.io
spec:
  # 定義 API 群組
  # 用於組織相關的資源類型
  group: charts.helm.k8s.io
  # 定義資源名稱的各種形式
  names:
    # 資源類型的種類名稱
    # 用於 YAML 檔案中的 kind 欄位
    kind: Guestbook
    # 資源類型的清單種類名稱
    listKind: GuestbookList
    # 複數形式的名稱
    # 用於 API 路徑中
    plural: guestbooks
    # 單數形式的名稱
    # 用於 CLI 顯示
    singular: guestbook
    # 短名稱
    # 方便使用者在 kubectl 指令中使用
    shortNames:
      - gb
  # 定義資源的範圍
  # Namespaced 表示資源屬於特定命名空間
  # Cluster 則表示叢集級別的資源
  scope: Namespaced
  # 定義支援的 API 版本
  versions:
    # 第一個版本定義
    - name: v1alpha1
      # 標記此版本為已服務
      # 表示可以透過 API 存取
      served: true
      # 標記此版本為儲存版本
      # 資源會以此版本的格式儲存在 etcd 中
      storage: true
      # 定義資源的結構描述
      schema:
        openAPIV3Schema:
          type: object
          # 定義必要欄位
          required:
            - spec
          properties:
            # 規格欄位定義
            # 包含使用者定義的期望狀態
            spec:
              type: object
              properties:
                # Helm Chart 相關設定
                chart:
                  type: object
                  properties:
                    # Chart 倉庫的 URL
                    repository:
                      type: string
                      description: "Helm Chart 倉庫位址"
                    # Chart 名稱
                    name:
                      type: string
                      description: "Helm Chart 名稱"
                    # Chart 版本
                    version:
                      type: string
                      description: "Helm Chart 版本"
                  required:
                    - repository
                    - name
                    - version
                # 應用程式配置值
                values:
                  type: object
                  # 允許任意的配置值
                  # 這些值會被傳遞給 Helm Chart
                  x-kubernetes-preserve-unknown-fields: true
                  properties:
                    # 副本數量
                    replicaCount:
                      type: integer
                      description: "應用程式副本數量"
                      minimum: 1
                      default: 2
                    # 容器映像設定
                    image:
                      type: object
                      properties:
                        repository:
                          type: string
                          description: "容器映像倉庫"
                        tag:
                          type: string
                          description: "容器映像標籤"
                        pullPolicy:
                          type: string
                          description: "映像拉取策略"
                          enum:
                            - Always
                            - IfNotPresent
                            - Never
                    # 資源限制
                    resources:
                      type: object
                      properties:
                        limits:
                          type: object
                          properties:
                            cpu:
                              type: string
                            memory:
                              type: string
                        requests:
                          type: object
                          properties:
                            cpu:
                              type: string
                            memory:
                              type: string
            # 狀態欄位定義
            # 由 Operator 維護,記錄實際狀態
            status:
              type: object
              properties:
                # 部署狀態
                phase:
                  type: string
                  description: "當前部署階段"
                  enum:
                    - Pending
                    - Installing
                    - Installed
                    - Upgrading
                    - Upgraded
                    - Failed
                    - Uninstalling
                # 狀態訊息
                message:
                  type: string
                  description: "狀態描述訊息"
                # Helm 發布資訊
                release:
                  type: object
                  properties:
                    name:
                      type: string
                      description: "Helm 發布名稱"
                    version:
                      type: integer
                      description: "Helm 發布版本號"
                    status:
                      type: string
                      description: "Helm 發布狀態"
                # 條件清單
                # 記錄各種狀態條件
                conditions:
                  type: array
                  items:
                    type: object
                    properties:
                      type:
                        type: string
                      status:
                        type: string
                      lastTransitionTime:
                        type: string
                        format: date-time
                      reason:
                        type: string
                      message:
                        type: string
      # 定義額外的輸出欄位
      # 這些欄位會在 kubectl get 指令中顯示
      additionalPrinterColumns:
        - name: Chart
          type: string
          description: Helm Chart name
          jsonPath: .spec.chart.name
        - name: Version
          type: string
          description: Helm Chart version
          jsonPath: .spec.chart.version
        - name: Status
          type: string
          description: Deployment status
          jsonPath: .status.phase
        - name: Age
          type: date
          jsonPath: .metadata.creationTimestamp

這個 CRD 定義展示了完整的資源結構設計。規格欄位清楚定義了使用者可以配置的所有項目,包含 Helm Chart 的來源資訊與應用程式的配置值。狀態欄位則提供了詳細的部署狀態資訊,讓使用者能夠追蹤應用程式的部署進度與運行狀態。透過 OpenAPI v3 結構描述的定義,Kubernetes 能夠在資源建立時進行驗證,確保提供的配置值符合預期的格式與範圍。

建立 CRD 之後,使用者就能夠像使用任何內建資源一樣來操作自定義資源。以下是一個 Guestbook 自定義資源的範例。

# Guestbook 自定義資源範例
# 定義一個具體的 Guestbook 應用程式部署
apiVersion: charts.helm.k8s.io/v1alpha1
kind: Guestbook
metadata:
  # 資源名稱
  # 在同一命名空間內必須唯一
  name: my-guestbook
  # 命名空間
  namespace: production
  # 標籤
  # 用於資源的組織與查詢
  labels:
    app: guestbook
    environment: production
    managed-by: helm-operator
  # 註解
  # 可以儲存額外的元資料
  annotations:
    description: "Production Guestbook deployment"
    contact: "[email protected]"
spec:
  # 指定要使用的 Helm Chart
  chart:
    # Chart 倉庫位址
    repository: https://charts.example.com
    # Chart 名稱
    name: guestbook
    # Chart 版本
    # 使用語意化版本號
    version: 1.2.3
  # 應用程式配置值
  # 這些值會覆寫 Chart 的預設值
  values:
    # 設定副本數量為 3
    replicaCount: 3
    # 容器映像設定
    image:
      repository: gcr.io/google-samples/gb-frontend
      tag: "v5"
      pullPolicy: IfNotPresent
    # 服務設定
    service:
      type: LoadBalancer
      port: 80
    # 資源限制
    resources:
      limits:
        cpu: "500m"
        memory: "512Mi"
      requests:
        cpu: "250m"
        memory: "256Mi"
    # 自動擴展設定
    autoscaling:
      enabled: true
      minReplicas: 3
      maxReplicas: 10
      targetCPUUtilizationPercentage: 80
    # Ingress 設定
    ingress:
      enabled: true
      className: nginx
      annotations:
        cert-manager.io/cluster-issuer: letsencrypt-prod
      hosts:
        - host: guestbook.example.com
          paths:
            - path: /
              pathType: Prefix
      tls:
        - secretName: guestbook-tls
          hosts:
            - guestbook.example.com

這個自定義資源範例展示了如何透過宣告式的方式來定義應用程式的完整部署規格。使用者只需要建立這個 YAML 檔案並套用到叢集中,Operator 就會自動處理所有的部署細節,包含從 Chart 倉庫下載 Chart、渲染範本、建立 Kubernetes 資源等。

Helm Operator 的實作與部署

Operator 本身也是一個需要部署到 Kubernetes 叢集中的應用程式。一個完整的 Operator 部署包含多個元件,包含 Operator 的控制器程式、必要的 RBAC 權限設定、服務帳戶等。使用 Helm Chart 來管理 Operator 的部署是一個理想的選擇,因為它能夠將所有相關資源組織在一起,並提供靈活的配置能力。

# Helm Operator Chart 的 values.yaml
# 定義 Operator 部署的配置參數

# Operator 映像設定
image:
  # 映像倉庫位址
  repository: quay.io/helmoperator/helm-operator
  # 映像標籤
  # 建議使用固定版本而非 latest
  tag: "1.4.0"
  # 映像拉取策略
  pullPolicy: IfNotPresent

# 映像拉取密鑰
# 如果映像倉庫需要認證
imagePullSecrets: []

# 副本數量
# Operator 通常只需要單一副本
# 多副本可能導致資源協調衝突
replicaCount: 1

# 資源限制
resources:
  limits:
    cpu: "200m"
    memory: "256Mi"
  requests:
    cpu: "100m"
    memory: "128Mi"

# 節點選擇器
# 可以指定 Operator 運行在特定節點上
nodeSelector: {}

# 容忍度設定
# 允許 Operator 在有污點的節點上運行
tolerations: []

# 親和性設定
affinity: {}

# 服務帳戶設定
serviceAccount:
  # 是否建立服務帳戶
  create: true
  # 服務帳戶名稱
  # 如果不指定,會自動生成
  name: ""
  # 服務帳戶的註解
  annotations: {}

# RBAC 設定
rbac:
  # 是否建立 RBAC 資源
  create: true

# Operator 特定設定
operator:
  # 監控的命名空間
  # 留空表示監控所有命名空間
  watchNamespace: ""
  # Helm 倉庫快取目錄
  helmRepositoryCache: /var/helm/cache
  # 日誌級別
  logLevel: info
  # 協調間隔
  # Operator 檢查資源狀態的頻率
  reconcileInterval: 3m
  # 工作者數量
  # 並行處理的協調請求數量
  workers: 4

# 監控設定
metrics:
  # 是否啟用 Prometheus 指標
  enabled: true
  # 指標伺服器埠
  port: 8080
  # ServiceMonitor 設定
  serviceMonitor:
    enabled: false
    interval: 30s

這個 values.yaml 檔案定義了 Operator 部署的所有可配置參數。透過調整這些參數,使用者能夠根據叢集環境與需求來客製化 Operator 的行為。例如,在大型叢集中可以增加工作者數量來提升並行處理能力,或是調整協調間隔來平衡資源檢查的頻率與系統負載。

Operator 的核心邏輯實作在控制器程式中。雖然完整的控制器實作涉及大量的程式碼,但其基本結構相對固定,主要包含資源監控、狀態協調與錯誤處理等模組。

# Operator Deployment 定義
# 部署 Operator 控制器程式
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helm-operator
  namespace: helm-operator-system
  labels:
    app.kubernetes.io/name: helm-operator
    app.kubernetes.io/component: controller
spec:
  # 副本數量
  replicas: 1
  # 選擇器
  selector:
    matchLabels:
      app.kubernetes.io/name: helm-operator
  # Pod 範本
  template:
    metadata:
      labels:
        app.kubernetes.io/name: helm-operator
    spec:
      # 服務帳戶
      serviceAccountName: helm-operator
      # 容器定義
      containers:
        - name: operator
          # 容器映像
          image: quay.io/helmoperator/helm-operator:1.4.0
          imagePullPolicy: IfNotPresent
          # 指令參數
          args:
            # 日誌級別
            - --log-level=info
            # 監控命名空間
            # 留空表示監控所有命名空間
            - --watch-namespace=
            # 協調間隔
            - --reconcile-interval=3m
            # 工作者數量
            - --workers=4
          # 環境變數
          env:
            # Helm 倉庫快取目錄
            - name: HELM_REPOSITORY_CACHE
              value: /var/helm/cache
            # Helm 配置目錄
            - name: HELM_REPOSITORY_CONFIG
              value: /var/helm/config/repositories.yaml
          # 埠設定
          ports:
            # 指標埠
            - name: metrics
              containerPort: 8080
              protocol: TCP
          # 健康檢查
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8081
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:
            httpGet:
              path: /readyz
              port: 8081
            initialDelaySeconds: 5
            periodSeconds: 10
          # 資源限制
          resources:
            limits:
              cpu: "200m"
              memory: "256Mi"
            requests:
              cpu: "100m"
              memory: "128Mi"
          # 掛載卷
          volumeMounts:
            # Helm 快取目錄
            - name: helm-cache
              mountPath: /var/helm/cache
            # Helm 配置目錄
            - name: helm-config
              mountPath: /var/helm/config
      # 卷定義
      volumes:
        # Helm 快取卷
        - name: helm-cache
          emptyDir: {}
        # Helm 配置卷
        - name: helm-config
          emptyDir: {}
      # 安全上下文
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        fsGroup: 65532

這個 Deployment 定義展示了 Operator 程式的完整部署規格。Operator 以一般的 Kubernetes Deployment 形式運行,這讓它能夠享有 Kubernetes 提供的所有管理能力,包含自動重啟、滾動更新等。健康檢查的設定確保了 Operator 在出現問題時能夠被及時發現與重啟。資源限制的設定則防止 Operator 消耗過多的叢集資源。

RBAC 權限的正確設定對於 Operator 的運作至關重要。Operator 需要足夠的權限來讀取自定義資源、建立與管理應用程式相關的 Kubernetes 資源。

# Operator RBAC 設定
# 定義 Operator 所需的權限

---
# 服務帳戶
apiVersion: v1
kind: ServiceAccount
metadata:
  name: helm-operator
  namespace: helm-operator-system

---
# ClusterRole 定義
# 定義叢集級別的權限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: helm-operator
rules:
  # 自定義資源權限
  # 需要完整的 CRUD 權限
  - apiGroups:
      - charts.helm.k8s.io
    resources:
      - guestbooks
    verbs:
      - create
      - delete
      - get
      - list
      - patch
      - update
      - watch
  # 自定義資源狀態更新權限
  - apiGroups:
      - charts.helm.k8s.io
    resources:
      - guestbooks/status
    verbs:
      - get
      - patch
      - update
  # Deployment 管理權限
  - apiGroups:
      - apps
    resources:
      - deployments
    verbs:
      - create
      - delete
      - get
      - list
      - patch
      - update
      - watch
  # Service 管理權限
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - create
      - delete
      - get
      - list
      - patch
      - update
      - watch
  # ConfigMap 管理權限
  # Helm 使用 ConfigMap 儲存發布資訊
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
      - delete
      - get
      - list
      - patch
      - update
      - watch
  # Secret 管理權限
  # Helm 也可能使用 Secret 儲存發布資訊
  - apiGroups:
      - ""
    resources:
      - secrets
    verbs:
      - create
      - delete
      - get
      - list
      - patch
      - update
      - watch
  # Events 建立權限
  # 用於記錄操作事件
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch

---
# ClusterRoleBinding
# 將 ClusterRole 綁定到服務帳戶
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: helm-operator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: helm-operator
subjects:
  - kind: ServiceAccount
    name: helm-operator
    namespace: helm-operator-system

這個 RBAC 設定定義了 Operator 運作所需的完整權限集合。ClusterRole 的設計遵循最小權限原則,只授予 Operator 必要的操作權限。對於自定義資源,Operator 需要完整的 CRUD 權限來管理資源的生命週期。對於應用程式相關的資源,Operator 需要建立、更新與刪除的權限來執行 Helm Chart 的部署操作。

狀態協調機制的深度解析

Operator 的核心價值在於其持續的狀態協調能力。理解這個機制的運作原理,對於設計可靠的 Operator 至關重要。當使用者建立或修改自定義資源時,Kubernetes API Server 會將這個事件通知給所有監控該資源的控制器。Operator 接收到通知後,會將這個協調請求加入工作佇列中,由工作者執行緒從佇列中取出請求並執行協調邏輯。

協調邏輯的第一步是獲取自定義資源的當前狀態。Operator 需要讀取資源的規格欄位來了解期望狀態,同時也需要讀取狀態欄位來了解上一次協調的結果。接著,Operator 會查詢叢集中實際部署的資源狀態,例如 Deployment、Service 等,來確定實際狀態。

透過比對期望狀態與實際狀態,Operator 能夠判斷需要執行什麼操作。如果這是一個新建立的自定義資源,且叢集中還沒有對應的 Helm 發布,Operator 會執行 Helm 安裝操作。如果自定義資源的規格已被修改,而對應的 Helm 發布已存在,Operator 會執行 Helm 升級操作。如果自定義資源被標記為刪除,Operator 會執行 Helm 解除安裝操作。

在每次協調操作完成後,Operator 都會更新自定義資源的狀態欄位,記錄當前的部署狀態、Helm 發布資訊、以及任何錯誤訊息。這個狀態更新讓使用者能夠追蹤應用程式的部署進度,也為下一次的協調提供了基準。

讓我們透過一個具體的情境來觀察狀態協調機制的運作。假設使用者手動修改了 Guestbook Deployment 的副本數量,將其從 2 增加到 3。

# 手動修改 Deployment 副本數量
# 這個操作會暫時改變實際狀態
kubectl patch deployment example-guestbook \
  -p '{"spec":{"replicas":3}}' \
  -n production

# 查詢 Deployment 狀態
# 此時會看到 3 個副本正在運行
kubectl get deployment example-guestbook -n production

# 輸出範例:
# NAME                READY   UP-TO-DATE   AVAILABLE   AGE
# example-guestbook   3/3     3            3           5m

# 等待片刻後再次查詢
# Operator 會偵測到狀態不一致並進行協調
sleep 30

# 再次查詢 Deployment 狀態
kubectl get deployment example-guestbook -n production

# 輸出範例:
# NAME                READY   UP-TO-DATE   AVAILABLE   AGE
# example-guestbook   2/2     2            2           6m

# 查看協調事件
# 可以看到 Operator 執行的協調操作記錄
kubectl get events -n production --field-selector involvedObject.name=my-guestbook

# 輸出範例:
# LAST SEEN   TYPE      REASON           OBJECT                MESSAGE
# 1m          Normal    Reconciling      guestbook/my-guestbook   Detected drift in deployment replicas
# 1m          Normal    Upgraded         guestbook/my-guestbook   Helm release upgraded to restore desired state

這個情境清楚展示了 Operator 的自動協調能力。當使用者手動修改 Deployment 時,短暫地改變了實際狀態。Operator 在下一次協調週期中偵測到這個偏移,並執行 Helm 升級操作來恢復期望狀態。這種機制確保了應用程式始終維持在自定義資源所定義的期望狀態,即使面對手動修改也能自動恢復。

如果使用者確實想要增加副本數量,正確的做法是修改自定義資源的規格。

# 更新 Guestbook 自定義資源
# 修改副本數量的期望值
apiVersion: charts.helm.k8s.io/v1alpha1
kind: Guestbook
metadata:
  name: my-guestbook
  namespace: production
spec:
  chart:
    repository: https://charts.example.com
    name: guestbook
    version: 1.2.3
  values:
    # 將副本數量從 2 更新為 3
    replicaCount: 3
    image:
      repository: gcr.io/google-samples/gb-frontend
      tag: "v5"
# 套用更新後的自定義資源
kubectl apply -f guestbook-cr.yaml

# 查看自定義資源狀態
# 可以看到 Operator 正在處理升級
kubectl get guestbook my-guestbook -n production -o yaml

# 狀態欄位會顯示升級進度
# status:
#   phase: Upgrading
#   message: Helm release is being upgraded
#   release:
#     name: my-guestbook
#     version: 2
#     status: pending-upgrade

# 等待升級完成
kubectl wait --for=condition=Ready \
  guestbook/my-guestbook \
  -n production \
  --timeout=5m

# 驗證 Deployment 副本數量
# 現在應該看到 3 個副本在運行
kubectl get deployment example-guestbook -n production

透過這種方式更新副本數量,Operator 會以受控的方式執行升級操作。Helm 會渲染更新後的範本,計算需要變更的資源,並按照滾動更新策略逐步完成變更。整個過程中,自定義資源的狀態欄位會持續更新,記錄升級的進度與結果。

GPG 數位簽章與 Helm 安全性

在討論完 Helm Operator 的技術實作後,我們需要關注整個生態系統的安全性議題。Helm 作為 Kubernetes 的套件管理工具,其下載與使用涉及多個潛在的安全風險點。攻擊者可能試圖在傳輸過程中篡改 Helm 二進位檔案,或是提供偽造的 Chart 套件。為了應對這些威脅,Helm 專案採用了 GPG 數位簽章機制來確保軟體的來源真實性與內容完整性。

數位簽章的核心概念建立在非對稱加密技術之上。軟體作者使用私鑰對檔案進行簽章,產生一個獨特的簽章檔案。使用者則使用作者的公鑰來驗證這個簽章,確認檔案確實來自宣稱的作者且內容未被篡改。這個機制之所以安全,是因為私鑰只有作者持有,任何人都無法在不知道私鑰的情況下偽造有效的簽章。

Helm 專案的所有官方發布版本都附帶了維護者的數位簽章。這些簽章檔案與二進位檔案一起發布在 GitHub Releases 頁面上。使用者在下載 Helm 後,可以透過驗證簽章來確保下載的檔案是官方發布的版本,而不是被篡改或偽造的檔案。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi 300
skinparam shadowing false
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 14
skinparam roundcorner 8

actor "軟體作者" as Author
actor "軟體使用者" as User
participant "私鑰" as PrivateKey
participant "公鑰" as PublicKey
participant "簽章檔案" as Signature
participant "軟體檔案" as Software

== 簽章產生階段 ==

Author -> Software : 準備發布檔案
Author -> PrivateKey : 使用私鑰
PrivateKey -> Software : 計算檔案雜湊值
PrivateKey -> Signature : 使用私鑰加密雜湊值
Author -> Signature : 發布簽章檔案
Author -> PublicKey : 公開發布公鑰

== 簽章驗證階段 ==

User -> Software : 下載軟體檔案
User -> Signature : 下載簽章檔案
User -> PublicKey : 取得作者公鑰

User -> PublicKey : 使用公鑰解密簽章
PublicKey -> Signature : 取得原始雜湊值

User -> Software : 計算下載檔案的雜湊值
User -> User : 比對兩個雜湊值

alt 雜湊值相符
    User -> User : 驗證成功\n檔案可信
else 雜湊值不符
    User -> User : 驗證失敗\n檔案可能被篡改
end

@enduml

此流程圖展示了數位簽章的完整生命週期。軟體作者在發布時使用私鑰對檔案進行簽章,產生簽章檔案。使用者在下載後使用公鑰驗證簽章,透過比對雜湊值來確認檔案的真實性與完整性。這個機制確保了即使攻擊者能夠攔截下載流量,也無法在不被發現的情況下篡改檔案內容。

實際執行 Helm 下載驗證需要幾個步驟。首先,我們需要在本機安裝 GPG 工具。GPG 是 OpenPGP 標準的開源實作,提供了完整的加密與簽章功能。

# 在不同作業系統上安裝 GPG

# macOS 系統使用 Homebrew
brew install gnupg

# Ubuntu/Debian 系統使用 apt
sudo apt update
sudo apt install gnupg

# CentOS/RHEL 系統使用 dnf
sudo dnf install gnupg

# Windows 系統使用 Chocolatey
choco install gnupg

# 驗證安裝
# 顯示 GPG 版本資訊確認安裝成功
gpg --version

# 輸出範例:
# gpg (GnuPG) 2.3.8
# libgcrypt 1.10.1
# ...

安裝 GPG 後,下一步是產生自己的 GPG 金鑰對。雖然驗證 Helm 下載不需要自己的金鑰對,但了解金鑰產生過程有助於理解整個機制的運作。

# 產生 GPG 金鑰對
# 這會啟動互動式的金鑰產生精靈
gpg --generate-key

# 精靈會要求輸入以下資訊:
# 1. 真實姓名(用於識別金鑰擁有者)
# 2. 電子郵件地址(用於金鑰通訊)
# 3. 密碼(用於保護私鑰)

# 金鑰產生完成後會顯示金鑰資訊
# 包含金鑰 ID 與指紋

# 查看已產生的金鑰
gpg --list-keys

# 輸出範例:
# /home/user/.gnupg/pubring.kbx
# ------------------------------
# pub   rsa3072 2024-01-15 [SC] [expires: 2026-01-14]
#       1234567890ABCDEF1234567890ABCDEF12345678
# uid           [ultimate] Your Name <[email protected]>
# sub   rsa3072 2024-01-15 [E] [expires: 2026-01-14]

# 匯出公鑰
# 公鑰可以分享給其他人用於驗證您的簽章
gpg --armor --export [email protected] > my-public-key.asc

# 檢視公鑰內容
cat my-public-key.asc

了解金鑰產生流程後,我們可以開始驗證 Helm 下載。首先從 GitHub Releases 頁面下載 Helm 二進位檔案與對應的簽章檔案。

# 設定變數方便後續使用
HELM_VERSION="v3.13.0"
PLATFORM="linux-amd64"

# 下載 Helm 二進位壓縮檔
wget https://get.helm.sh/helm-${HELM_VERSION}-${PLATFORM}.tar.gz

# 下載對應的簽章檔案
wget https://get.helm.sh/helm-${HELM_VERSION}-${PLATFORM}.tar.gz.asc

# 驗證檔案已下載
ls -lh helm-${HELM_VERSION}-${PLATFORM}.tar.gz*

# 輸出範例:
# -rw-r--r-- 1 user user 15M Nov 20 10:00 helm-v3.13.0-linux-amd64.tar.gz
# -rw-r--r-- 1 user user 833 Nov 20 10:00 helm-v3.13.0-linux-amd64.tar.gz.asc

下載完成後,我們需要匯入 Helm 維護者的公鑰。Helm 專案的公鑰可以從 Keybase 或其他公鑰伺服器取得。

# 下載 Helm 維護者的公鑰
# 這個公鑰由 Helm 官方維護者管理
curl -o helm-release-key.asc https://raw.githubusercontent.com/helm/helm/main/KEYS

# 或者從 Keybase 取得
# curl -o helm-release-key.asc https://keybase.io/bacongobbler/pgp_keys.asc

# 匯入公鑰到 GPG 金鑰環
# 金鑰環是 GPG 儲存金鑰的本地資料庫
gpg --import helm-release-key.asc

# 輸出範例:
# gpg: key C1A60D2CB3D8C51F: public key "Helm Project (Official Helm Release Key) <[email protected]>" imported
# gpg: Total number processed: 1
# gpg:               imported: 1

# 驗證金鑰已成功匯入
gpg --list-keys "Helm Project"

# 輸出範例:
# pub   rsa4096 2023-01-01 [SC]
#       A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0
# uid           [ unknown] Helm Project (Official Helm Release Key) <[email protected]>
# sub   rsa4096 2023-01-01 [E]

公鑰匯入成功後,就可以執行簽章驗證。GPG 會使用公鑰解密簽章檔案,計算下載檔案的雜湊值,並比對兩者是否一致。

# 驗證 Helm 下載的簽章
# --verify 參數指定要驗證簽章
gpg --verify helm-${HELM_VERSION}-${PLATFORM}.tar.gz.asc

# 成功的驗證會顯示類似以下的輸出:
# gpg: assuming signed data in 'helm-v3.13.0-linux-amd64.tar.gz'
# gpg: Signature made Mon 20 Nov 2023 10:00:00 AM UTC
# gpg:                using RSA key A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0
# gpg: Good signature from "Helm Project (Official Helm Release Key) <[email protected]>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: A1B2 C3D4 E5F6 G7H8 I9J0  K1L2 M3N4 O5P6 Q7R8 S9T0

# 關鍵訊息解析:
# - "Good signature" 表示簽章驗證成功
# - 檔案內容與簽章一致,未被篡改
# - WARNING 訊息是正常的,因為我們沒有明確信任這個金鑰
# - 在生產環境中應該透過其他管道驗證金鑰指紋的真實性

如果驗證失敗,GPG 會明確指出問題。

# 驗證失敗的輸出範例(檔案被篡改):
# gpg: Signature made Mon 20 Nov 2023 10:00:00 AM UTC
# gpg:                using RSA key A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0
# gpg: BAD signature from "Helm Project (Official Helm Release Key) <[email protected]>" [unknown]

# "BAD signature" 表示檔案可能被篡改
# 此時不應該使用這個下載檔案
# 應該重新下載或報告安全問題

驗證成功後,我們可以安全地使用下載的 Helm 二進位檔案。

# 解壓縮 Helm 壓縮檔
tar -zxvf helm-${HELM_VERSION}-${PLATFORM}.tar.gz

# 將 Helm 二進位檔案移動到系統路徑
sudo mv ${PLATFORM}/helm /usr/local/bin/helm

# 設定執行權限
sudo chmod +x /usr/local/bin/helm

# 驗證 Helm 安裝
helm version

# 輸出範例:
# version.BuildInfo{Version:"v3.13.0", GitCommit:"...", GitTreeState:"clean", GoVersion:"go1.21.0"}

# 清理下載檔案
rm -rf ${PLATFORM}
rm helm-${HELM_VERSION}-${PLATFORM}.tar.gz*
rm helm-release-key.asc

透過這個完整的驗證流程,我們確保了下載的 Helm 二進位檔案確實來自官方發布,且內容未被篡改。這種安全實務應該成為所有生產環境部署的標準程序,特別是在處理關鍵基礎設施時更是不可或缺。

Helm Operator 與安全實務的結合,為 Kubernetes 環境提供了完整的自動化與安全保障。透過 Operator 模式,我們能夠將複雜的應用程式管理邏輯封裝為可重用的控制器,讓開發團隊能夠專注於業務邏輯而非基礎設施細節。同時,透過 GPG 數位簽章機制,我們確保了整個工具鏈的安全性,從 Helm 的下載安裝到 Chart 的部署使用都能夠被驗證與信任。這些技術的綜合應用,構成了現代雲端原生應用管理的最佳實務基礎。