問題背景

即使加密後,有些組織仍不希望在Git中儲存任何形式的憑證。那麼,如何在完全遵循GitOps原則的同時,避免在Git中儲存任何形式的憑證呢?

External Secrets解決方案

External Secrets是一個最初由GoDaddy建立的開放原始碼專案,其目標是將秘密儲存在外部服務或金鑰管理系統中,然後只在Git中儲存對這些秘密的參照。

目前,External Secrets支援多種系統,包括AWS Secrets Manager、HashiCorp Vault、Google Secrets Manager、Azure Key Vault等。這種方法為外部API提供了使用者友好的抽象,用於儲存和管理秘密的生命週期。

ExternalSecrets是一個Kubernetes控制器,負責從外部API源協調Secrets到叢集中。這提供了一種安全與符合GitOps原則的方式來管理敏感資料。

整合Argo CD、HashiCorp Vault和External Secret

在下面的部分中,我將展示如何將Argo CD與HashiCorp Vault和External Secret整合,以建立一個完整的GitOps安全管理解決方案。這種整合提供了最高階別的安全性,同時仍然保持GitOps的宣告式方法的優勢。

HashiCorp Vault的角色

HashiCorp Vault是一個專為保護敏感資料而設計的工具。它提供了一個統一的介面來存取秘密,同時提供嚴格的存取控制和詳細的稽核日誌。在我們的架構中,Vault將作為中央秘密儲存,而External Secrets將作為Vault和Kubernetes之間的橋樑。

實作步驟概述

  1. 在Kubernetes叢集中佈署HashiCorp

GitOps 中的敏感資料管理與多叢集佈署策略

在 Kubernetes 環境中實施 GitOps 時,安全管理敏感資料和實作多叢集佈署是兩個常見的挑戰。本文將探討如何使用 External Secrets Operator 安全管理敏感資料,以及如何利用 Argo CD 的 ApplicationSet 實作多叢集佈署策略。

安全管理敏感資料:External Secrets Operator

在 GitOps 實踐中,我們追求將所有設定儲存在 Git 中,但敏感資料如密碼、API 金鑰等不適合直接儲存在 Git 倉函式庫這時 External Secrets Operator 就派上用場了。

External Secrets 的工作原理

External Secrets Operator 建立了一座橋樑,連線 Kubernetes 與外部金鑰管理系統(如 HashiCorp Vault、AWS Secrets Manager 等)。它透過兩個核心資源實作這一功能:

  1. SecretStore:定義了外部後端系統的連線設定
  2. ExternalSecret:指定從外部系統取得哪些敏感資料,以及如何將其轉換為 Kubernetes Secret

這種設計允許我們將敏感資料的參照(而非資料本身)儲存在 Git 中,大提高了安全性。

安裝 External Secrets Operator

使用 Helm 安裝 External Secrets Operator 非常直接:

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --create-namespace

這段命令首先增加 External Secrets 的 Helm 圖表 倉函式庫後安裝 External Secrets Operator。-n external-secrets 指定安裝的名稱空間,--create-namespace 引數確保該名稱空間存在,如果不存在則自動建立。這是一種標準的 Helm 安裝模式,適用於大多數 Kubernetes 元件。

安裝成功後,你會看到類別似下面的輸出:

NAME: external-secrets
LAST DEPLOYED: Fri Sep 2 13:09:53 2022
NAMESPACE: external-secrets
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
external-secrets has been deployed successfully!

值得一提的是,你也可以透過 OperatorHub.io 使用 OLM (Operator Lifecycle Manager) 安裝 External Secrets Operator。

與 HashiCorp Vault 整合範例

讓我們看一個與 HashiCorp Vault 整合的例項。首先,安裝 Vault 並取得 Token:

export VAULT_TOKEN=<YOUR_TOKEN>
kubectl create secret generic vault-token \
  --from-literal=token=$VAULT_TOKEN \
  -n external-secrets

這裡建立了一個 Kubernetes Secret 來儲存 Vault 的存取令牌。--from-literal=token=$VAULT_TOKEN 從環境變數直接設定 Secret 的值,而不是從檔案讀取,這是在指令碼中建立簡單 Secret 的常用方式。

然後建立一個 SecretStore 資源,建立與 Vault 的連線:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-secretstore
  namespace: default
spec:
  provider:
    vault:
      server: "http://vault.local:8200"  # Vault 伺服器的位置
      path: "secret"
      version: "v2"
      auth:
        tokenSecretRef:
          name: "vault-token"  # 包含 Vault 令牌的 Kubernetes Secret 名稱
          key: "token"  # Secret 中儲存令牌的鍵名
          namespace: external-secrets

這個 YAML 定義了一個 SecretStore 資源,指定了如何連線到 Vault 伺服器。spec.provider.vault.server 指定了 Vault 伺服器的 URL,path 是 Vault 中的路徑,version 指定了 Vault KV 金鑰引擎的版本。auth.tokenSecretRef 部分則指定了如何取得授權 Token:從 external-secrets 名稱空間的 vault-token Secret 中讀取 token 鍵的值。

建立這個資源:

kubectl create -f vault-secretstore.yaml

現在,在 Vault 中建立一個金鑰:

vault kv put secret/pacman-secrets pass=pacman

這個命令在 Vault 的 secret/pacman-secrets 路徑下建立了一個鍵值對,其中鍵為 pass,值為 pacman。這是 Vault KV 引擎的標準使用方式,用於儲存簡單的鍵值對資料。

接下來,建立一個 ExternalSecret 資源參照這個 Vault 中的金鑰:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: pacman-externalsecrets
  namespace: default
spec:
  refreshInterval: "15s"
  secretStoreRef:
    name: vault-secretstore
    kind: SecretStore
  target:
    name: pacman-externalsecrets
  data:
  - secretKey: token
    remoteRef:
      key: secret/pacman-secrets
      property: pass

這個 ExternalSecret 定義瞭如何從外部金鑰管理系統取得資料並轉換為 Kubernetes Secret。refreshInterval 指定了同步間隔,每 15 秒檢查一次外部系統的變更。secretStoreRef 參照了前面建立的 SecretStore。target.name 指定了要建立的 Kubernetes Secret 的名稱。data 部分定義了對映關係:從 Vault 的 secret/pacman-secrets 路徑取得 pass 屬性的值,並將其儲存在 Kubernetes Secret 的 token 鍵下。

建立這個資源:

kubectl create -f pacman-externalsecrets.yaml

最後,使用 Argo CD 佈署使用這個 Secret 的應用:

argocd app create pacman \
  --repo https://github.com/gitops-cookbook/pacman-kikd-manifests.git \
  --path 'k8s/externalsecrets' \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default \
  --sync-policy auto

這個命令建立了一個 Argo CD 應用,從指定的 Git 倉函式庫徑取得 Kubernetes 清單檔案,佈署到當前叢集的 default 名稱空間。--sync-policy auto 表示變更會自動同步,無需手動干預。這是 Argo CD 的標準應用建立流程。

使用 Argo CD Webhooks 自動觸發應用佈署

問題與解決方案

Argo CD 預設每三分鐘輪詢一次 Git 倉函式庫測變更,但在某些情況下,我們希望在 Git 變更發生時立即佈署應用,無需等待輪詢間隔。

Argo CD 支援根據 Webhook 的事件驅動方法,可以接收來自 GitHub、GitLab 或 Bitbucket 等 Git 伺服器的 Webhook 通知。Argo CD 的 Webhook 端點是 /api/webhooks

使用 Gitea 測試 Webhooks

為了在本地測試 Webhooks,我們可以使用 Helm 安裝輕量級的 Git 伺服器 Gitea:

helm repo add gitea-charts https://dl.gitea.io/charts/
helm install gitea gitea-charts/gitea

這裡首先增加了 Gitea 的 Helm 圖表 倉函式庫後安裝 Gitea。Gitea 是一個用 Go 編寫的輕量級 Git 伺服器,非常適合這種測試場景。安裝後,你可以使用連線埠轉發存取 Gitea UI:kubectl port-forward svc/gitea-http 3000:3000

安裝後,將 Pac-Man 清單倉函式庫 Gitea,然後設定 Argo CD 應用:

argocd app create pacman-webhook \
  --repo http://gitea-http.default.svc:3000/gitea_admin/pacman-kikd-manifests.git \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default \
  --path k8s \
  --sync-policy auto

這個命令建立了一個使用 Gitea 倉函式庫Argo CD 應用。注意倉函式庫RL 使用了叢集內的服務名稱,因為 Argo CD 和 Gitea 都在同一個 Kubernetes 叢集中執行。

接下來,在 Gitea 中設定 Webhook:

  1. 在倉函式庫右上角點選 Settings
  2. 選擇 Webhooks 標籤
  3. 設定 Webhook:
    • Payload URL: http://localhost:9090/api/webhooks
    • Content type: application/json

每當你推播變更到 Gitea 倉函式庫Argo CD 將立即同步應用,而不是等待下一個輪詢週期。

使用 ApplicationSet 佈署到多個叢集

問題與解決方案

在企業環境中,通常需要將應用佈署到多個 Kubernetes 叢集(如開發、測試、生產環境)。Argo CD 的 ApplicationSet 資源可以簡化這個過程。

ApplicationSet 允許你"範本化"Argo CD 應用資源,主要用於:

  • 將應用佈署到多個 Kubernetes 叢集
  • 從一個或多個 Git 倉函式庫多個應用

ApplicationSet 使用生成器(generators)來提供範本中的引數值。

建立 ApplicationSet

以下是一個 ApplicationSet 範例,用於將應用佈署到 staging 和 prod 環境:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: bgd-app
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      - cluster: staging
        url: https://kubernetes.default.svc
        location: default
      - cluster: prod
        url: https://kubernetes.default.svc
        location: app
  template:
    metadata:
      name: '{{cluster}}-app'
    spec:
      project: default
      source:
        repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
        targetRevision: main
        path: ch08/bgd-gen/{{cluster}}
      destination:
        server: '{{url}}'
        namespace: '{{location}}'
      syncPolicy:
        syncOptions:
        - CreateNamespace=true

這個 ApplicationSet 使用 list 生成器定義了兩個環境:staging 和 prod。generator 部分定義了將要替換到範本中的引數值。template 部分定義了 Argo CD Application 資源的範本,其中包含佔位符(如 {{cluster}}{{url}}{{location}}),這些佔位符將被生成器提供的值替換。

這樣,從一個 ApplicationSet 定義,Argo CD 會生成兩個 Application 資源,分別對應 staging 和 prod 環境。

應用這個 ApplicationSet:

kubectl apply -f bgd-application-set.yaml

這將建立兩個 Argo CD Application 資源:

  1. staging-app:佈署到 default 名稱空間
  2. prod-app:佈署到 app 名稱空間

我們可以檢查建立的應用:

# 先登入
argocd login --insecure --grpc-web $argoURL --username admin --password $argoPass
argocd app list

輸出應類別似於:

NAME          CLUSTER                       NAMESPACE
prod-app      https://kubernetes.default.svc app
staging-app   https://kubernetes.default.svc default

如果需要刪除這些應用,只需刪除 ApplicationSet:

kubectl delete -f bgd-application-set.yaml

ApplicationSet 生成器類別

在撰寫本文時,Argo CD 支援八種生成器:

  1. List:透過固定的叢集列表生成應用定義(上面範例使用的就是這種)
  2. Cluster:根據 Argo CD 中註冊的叢集動態生成應用
  3. Git:根據 Git 倉函式庫錄結構或檔案生成應用
  4. Matrix:組合兩個生成器的輸出,生成笛卡爾積
  5. Merge:合併多個生成器的輸出
  6. **SCM

Argo CD ApplicationSet 生成器種類別與應用場景

在 Kubernetes 的 GitOps 實踐中,Argo CD 已成為標準工具之一,而 ApplicationSet 則是其中的核心功能,能夠大幅簡化多環境應用佈署。在我實作大規模自動化佈署時,深入理解各種生成器的特性與適用場景至關重要。

ApplicationSet 支援多種生成器類別,每種都有其獨特的使用場景:

Cluster 生成器

根據 Argo CD 中定義的叢集清單生成應用定義。這適合於管理跨多個叢集的相同應用佈署,尤其在多雲環境中特別有用。

Git 生成器

從 Git 儲存函式庫 JSON/YAML 屬性檔案或根據儲存函式庫錄結構生成應用定義。這是我最常使用的生成器之一,特別適合於結構化的多環境組態管理。

SCM Provider 生成器

從組織內的儲存函式庫應用定義。當您需要自動佈署組織中多個儲存函式庫用時,這非常有效。

Pull Request 生成器

從開放的提取請求生成應用定義。這對於實作預覽環境和程式碼審查流程自動化極為有價值。

Cluster Decision Resource 生成器

使用鴨子類別(duck-typing)技術生成應用定義。適用於高度自定義的佈署邏輯實作。

Matrix 生成器

組合兩個獨立生成器的值。當需要根據多維度條件生成應用時非常有用。

Merge 生成器

合併來自兩個或更多生成器的值。適合於需要組合不同來源設定的場景。

使用 Git 生成器實作目錄結構化佈署

在前面的例子中,我們從固定列表元素建立了應用物件,這在環境數量較少時是可行的。但當面臨多環境佈署時,使用 Git 生成器能夠實作更靈活的動態設定。

讓我們將之前的範例遷移到 Git 生成器。我們的 Git 目錄結構如下:

bgd-gen
├── staging
│   ├── ...yaml
└── prod
    ├── ...yaml

根據目錄結構的 ApplicationSet 實作

現在建立一個新的 ApplicationSet 定義,為設定的 Git 儲存函式庫每個目錄生成一個應用:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: openshift-gitops
spec:
  generators:
  - git:
      repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
      revision: main
      directories:
      - path: ch08/bgd-gen/*
  template:
    metadata:
      name: '{{path[0]}}{{path[2]}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
        targetRevision: main
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'

這個 ApplicationSet 設定中:

  • git.repoURL 指定要讀取的 Git 倉函式庫 directories.path 使用萬用字元 ch08/bgd-gen/* 比對所有子目錄
  • 範本部分定義瞭如何根據找到的目錄生成 Application 物件
  • {{path}} 代表完整的目錄路徑
  • {{path.basename}} 提取路徑的最右邊部分(目錄名)作為名稱空間

應用此資源後,Argo CD 會為比對的兩個目錄(staging 和 prod)建立兩個應用:

NAME        CLUSTER                      NAMESPACE
ch08prod    https://kubernetes.default.svc  prod
ch08staging https://kubernetes.default.svc  staging

此生成器在應用由多個元件組成時特別有用,例如當佈署檔案按服務、資料函式庫取等分別放在不同目錄中。或者管理叢集中需要安裝的所有營運商,如:

app
├── tekton-operator
│   ├── ...yaml
├── prometheus-operator
│   ├── ...yaml
└── istio-operator
    ├── ...yaml

根據設定檔的 Git 生成器

Git 生成器不僅可以根據目錄結構,還可以根據 JSON/YAML 設定檔建立 Application 物件。

例如,JSON 設定檔:

{
  "cluster": {
    "name": "staging",
    "address": "https://1.2.3.4"
  }
}

對應的 ApplicationSet 定義:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
spec:
  generators:
  - git:
      repoURL: https://github.com/example/app.git
      revision: HEAD
      files:
      - path: "app/**/config.json"
  template:
    metadata:
      name: '{{cluster.name}}-app'
    ....
  • files.path 使用 app/**/config.json 模式比對所有子目錄中的 config.json 檔案
  • 範本中可以使用 {{cluster.name}} 這樣的語法注入 JSON 檔案中定義的值
  • 這個 ApplicationSet 會為每個比對的 config.json 檔案生成一個 Application

這種方法比根據目錄的方法更靈活,因為可以在設定檔中定義任意結構的引數。

使用 Pull Request 生成器實作預覽環境

在現代 CI/CD 實踐中,為每個提取請求(PR)自動佈署預覽環境是提高開發效率的關鍵做法。ApplicationSet 的 Pull Request 生成器完美解決了這一需求。

建立 PR 預覽環境的 ApplicationSet

讓我們建立一個 ApplicationSet,它會對標記為 “preview” 的 GitHub PR 做出反應:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: myapps
  namespace: openshift-gitops
spec:
  generators:
  - pullRequest:
      github:
        owner: gitops-cookbook
        repo: gitops-cookbook-sc
        labels:
        - preview
      requeueAfterSeconds: 60
  template:
    metadata:
      name: 'myapp-{{branch}}-{{number}}'
    spec:
      source:
        repoURL: 'https://github.com/gitops-cookbook/gitops-cookbook-sc.git'
        targetRevision: '{{head_sha}}'
        path: ch08/bgd-pr
      project: default
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{branch}}-{{number}}'
  • pullRequest.github 設定指定了 GitHub 組織/使用者和倉函式庫 labels 設定只有帶有 “preview” 標籤的 PR 才會觸釋出署
  • requeueAfterSeconds: 60 設定每 60 秒輪詢一次檢查新的 PR
  • 範本中使用 {{branch}}{{number}}{{head_sha}} 變數來參照 PR 的分支名稱、編號和提交 SHA
  • 每個 PR 會在單獨的名稱空間中佈署,格式為 {{branch}}-{{number}}

應用此設定後,初始狀態下不會建立任何應用,因為還沒有帶 “preview” 標籤的 PR。一旦建立並標記了 PR,大約一分鐘後 ApplicationSet 控制器會檢測到變更並建立相應的 Application 物件。

當 PR 關閉時,對應的 Application 物件會自動移除,從而實作完整的生命週期管理。

支援的 PR 提供者與事件觸發

目前 ApplicationSet 支援以下 PR 提供者:

  • GitHub
  • Bitbucket
  • Gitea
  • GitLab

雖然 ApplicationSet 控制器預設透過輪詢來檢測變更,但也支援使用 webhook 事件。設定 webhook 可以讓系統在 PR 建立或更新時立即回應,而不必等待輪詢週期。

整合 Argo Rollouts 實作高階佈署策略

在生產環境中,簡單的滾動更新常不足以滿足零停機佈署的需求。Argo Rollouts 提供了藍綠佈署、金絲雀釋出等高階佈署技術,與 Argo CD 結合使用可以實作強大的漸進式交付流程。

安裝 Argo Rollouts

首先,我們需要在叢集中安裝 Argo Rollouts:

kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/download/v1.2.2/install.yaml

雖然不是必須的,但強烈建議安裝 Argo Rollouts Kubectl 外掛來視覺化佈署過程。

Rollout 資源介紹

Argo Rollouts 引入了一個名為 Rollout 的新 Kubernetes 資源,它類別似於 Deployment 物件(支援所有 Deployment 選項),但增加了一些欄位來設定高階佈署策略。

Rollout 資源支援多種佈署策略:

  1. 藍綠佈署:建立新版本的完整副本,在驗證後切換流量
  2. 金絲雀發布:逐步將流量從舊版本轉移到新版本
  3. 流量映象:將流量同時傳送到新舊版本,但只回傳舊版本的回應
  4. 暗金絲雀:在不影響使用者的情況下測試新版本

Argo Rollouts 還整合了多種流量管理解決方案(如 Istio、Ambassador、Traefik 等)和分析提供者(如 Prometheus、Datadog、New Relic 等),可以根據指標自動推進或回復佈署。

實作金絲雀發布流程

金絲雀發布是一種漸進式佈署策略,先將少量流量導向新版本,在確認無問題後再逐步增加流量比例。這種方法可以在早期發現問題並最小化影響範圍。

一個典型的金絲雀發布流程包括以下步驟:

  1. 佈署新版本但初始不接收流量
  2. 將少量流量(如 5%)導向新版本
  3. 分析新版本的效能和錯誤率
  4. 如果指標良好,逐步增加流量比例(如 20%、50%、100%)
  5. 如果發現問題,立即回復到舊版本

Argo Rollouts 可以自動執行這個過程,並根據預定義的指標決定是否繼續推進佈署。

結合 ApplicationSet 與 Rollouts 的最佳實踐

在我的實踐中,結合 ApplicationSet 與 Argo Rollouts 可以實作強大而靈活的佈署流程:

  1. 使用 ApplicationSet 管理多環境的應用定義,確保設定一致性
  2. 在每個環境中使用 Rollout 而非 Deployment 資源
  3. 根據環境類別設定不同的佈署策略:
    • 開發環境:簡單的滾動更新
    • 測試環境:完整的藍綠佈署以驗證新版本
    • 生產環境:謹慎的金絲雀發布,結合自動分析

這種組合方法可以在保持設定簡單性的同時,實作複雜的佈署流程。特別是在微服務架構中,管理數十甚至上百個服務的佈署時,這種自動化程度可以大降低維運負擔。

透過 ApplicationSet 的強大生成器功能和 Argo Rollouts 的高階佈署策略,我們可以實作真正的 GitOps 自動化,讓開發團隊專注於程式碼開發,而不必擔心複雜的佈署細節。隨著應用規模和複雜性的增長,這種自動化程度變得尤為重要,能夠顯著提高團隊的開發效率和系統的可靠性。

Kubernetes 漸進式佈署:掌握 Argo Rollouts 的優雅發布策略

在現代微服務架構中,安全與可控的應用佈署是確保系統穩定性的關鍵。傳統的 Kubernetes Deployment 雖然提供了基本的滾動更新能力,但對於需要精細控制的生產環境來說仍然不夠。當我們需要更精確地控制流量轉移、支援手動批准或自動化進度控制時,Argo Rollouts 便成為一個強大的解決方案。

在使用 Argo Rollouts 的過程中,我發現它不僅擴充套件了 Kubernetes 的佈署能力,更為團隊提供了一個可靠與可預測的發布流程。今天,我想分享如何利用 Argo Rollouts 實作金絲雀發布策略,以及如何將其與 Istio 整合,實作更精確的流量控制。

為什麼選擇漸進式佈署?

在開始深入技術細節前,我們先探討為什麼漸進式佈署對於現代應用至關重要:

  1. 風險降低:將新版本逐步引入到生產環境,可以將潛在問題的影響範圍限制在一小部分使用者
  2. 即時監控:在佈署過程中,可以持續監控新版本的效能和穩定性
  3. 快速回復:發現問題時,能夠迅速回復到穩定版本,最小化停機時間
  4. 使用者經驗保障:確保大多數使用者始終能夠使用穩定版本的服務

而 Argo Rollouts 正是為解決這些挑戰而設計的工具,它擴充套件了 Kubernetes 原生的佈署能力,提供了更多進階的佈署策略。