GitOps 安全實踐的核心挑戰

在雲端原生應用程式的開發與維運領域中,GitOps 已經成為標準的基礎設施管理範式。這種以 Git 作為唯一真實來源的方法論,將所有系統配置與應用程式定義納入版本控制系統,實現了完整的變更追溯與審計能力。然而,在實際的企業環境中,敏感資料的管理始終是 GitOps 實踐中最具挑戰性的議題之一。

傳統的做法是將敏感資料加密後儲存在 Git 倉儲中,透過 Sealed Secrets 或 SOPS 等工具在部署時進行解密。儘管這種方法提供了基本的安全保障,但許多組織基於合規性要求或安全政策,仍然不願意將任何形式的憑證儲存在版本控制系統中。這些組織傾向於使用專門的金鑰管理系統來集中管理所有敏感資料,並透過嚴格的存取控制策略來限制憑證的使用範圍。

External Secrets Operator 的出現為這個問題提供了優雅的解決方案。這個由 GoDaddy 最初開發並後來捐贈給開源社群的專案,建立了 Kubernetes 與外部金鑰管理系統之間的橋樑。透過這種架構設計,Git 倉儲中僅需儲存對敏感資料的參照而非資料本身,從根本上解決了憑證外洩的風險。當應用程式需要存取敏感資料時,External Secrets Operator 會即時從外部系統擷取最新的憑證值,並將其轉換為標準的 Kubernetes Secret 資源供 Pod 使用。

這種設計模式帶來了多重優勢。首先是安全性的大幅提升,敏感資料永遠不會以任何形式出現在 Git 倉儲中。其次是集中化管理能力,所有憑證都統一儲存在專業的金鑰管理系統中,便於實施統一的存取控制與稽核策略。第三是憑證輪換的簡化,當需要更新憑證時,僅需在金鑰管理系統中修改,External Secrets Operator 會自動同步新值到 Kubernetes 叢集中,無需修改 Git 倉儲或重新部署應用程式。

External Secrets Operator 的架構設計

External Secrets Operator 採用了 Kubernetes Operator 模式,透過自訂資源定義擴展了 Kubernetes 的 API 表面。其核心架構包含兩個關鍵的 CRD 資源,分別是 SecretStore 與 ExternalSecret。這種分層設計將連線配置與資料取得邏輯清晰分離,提供了良好的可維護性與重用性。

SecretStore 資源定義了如何連線到外部金鑰管理系統。它包含了端點位置、認證方式、存取路徑等配置資訊。一個 SecretStore 可以被多個 ExternalSecret 資源共用,避免了重複配置的問題。SecretStore 支援命名空間級別與叢集級別兩種範圍,ClusterSecretStore 可以被任何命名空間中的 ExternalSecret 資源參照,適合需要跨命名空間共用連線配置的場景。

ExternalSecret 資源則定義了要從外部系統擷取哪些資料,以及如何將這些資料轉換為 Kubernetes Secret。它透過 secretStoreRef 欄位參照一個 SecretStore,指定使用哪個外部系統。data 區段定義了資料對應關係,可以將外部系統中的多個鍵值對映射到單一 Secret 的不同欄位中。refreshInterval 參數控制同步週期,External Secrets Operator 會定期檢查外部系統的資料變更並更新對應的 Secret。

# 部署 External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets \
  external-secrets/external-secrets \
  --namespace external-secrets-system \
  --create-namespace \
  --set installCRDs=true \
  --set webhook.port=9443 \
  --set certController.requeueInterval=30s

# 驗證部署狀態
kubectl get pods -n external-secrets-system
kubectl get crds | grep external-secrets

# 查看 Operator 日誌
kubectl logs -n external-secrets-system deployment/external-secrets \
  --tail=100 -f

installCRDs 參數確保自訂資源定義會隨著 Operator 一起安裝,這對於初次部署至關重要。webhook 配置啟用了動態準入控制,可以在資源建立時進行驗證,防止無效配置被套用到叢集中。certController 負責管理 webhook 所需的 TLS 憑證,requeueInterval 設定了憑證輪換的檢查週期。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16

package "Kubernetes 叢集" {
  component "External Secrets Operator" as eso
  
  database "SecretStore CRD" as ss {
    [連線配置]
    [認證資訊]
    [存取路徑]
  }
  
  database "ExternalSecret CRD" as es {
    [資料映射規則]
    [同步週期]
    [目標 Secret]
  }
  
  component "Kubernetes Secret" as k8s_secret
  component "應用程式 Pod" as app_pod
}

cloud "外部金鑰管理系統" as external {
  database "HashiCorp Vault" as vault
  database "AWS Secrets Manager" as aws
  database "Azure Key Vault" as azure
  database "GCP Secret Manager" as gcp
}

es -up-> ss: 參照
eso -down-> es: 監聽變更
eso -up-> external: 擷取敏感資料
eso -down-> k8s_secret: 建立/更新
app_pod -up-> k8s_secret: 掛載使用

@enduml

HashiCorp Vault 整合實務

HashiCorp Vault 是企業級的金鑰管理解決方案,提供了豐富的功能包含動態憑證生成、租約管理、秘密輪換等。將 Vault 與 External Secrets Operator 整合能夠建立起完整的敏感資料生命週期管理機制。

Vault 採用路徑導向的資料組織方式,每個秘密都儲存在特定的路徑下。KV Secrets Engine 是最常用的引擎類型,提供了版本化的鍵值儲存能力。v2 版本支援秘密的歷史版本追蹤與軟刪除,相較於 v1 版本提供了更完整的資料保護機制。

認證是整合過程中的關鍵環節。Vault 支援多種認證方式,包含 Token、Kubernetes Service Account、AppRole 等。在 Kubernetes 環境中,使用 Service Account 認證是最佳實踐,它利用 Kubernetes 原生的身份驗證機制,無需額外管理長效憑證。Vault 的 Kubernetes Auth Method 能夠驗證 Service Account Token 的有效性,並根據預先配置的策略授予相應的存取權限。

# 部署開發模式的 Vault
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

helm install vault hashicorp/vault \
  --namespace vault-system \
  --create-namespace \
  --set "server.dev.enabled=true" \
  --set "server.dev.devRootToken=root-token" \
  --set "injector.enabled=false"

# 等待 Vault Pod 就緒
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=vault \
  -n vault-system --timeout=300s

# 取得 Root Token
export VAULT_TOKEN="root-token"
export VAULT_ADDR="http://vault.vault-system.svc.cluster.local:8200"

# 在 Vault 中啟用 KV v2 引擎
kubectl exec -n vault-system vault-0 -- \
  vault secrets enable -path=secret kv-v2

# 建立測試用的秘密資料
kubectl exec -n vault-system vault-0 -- \
  vault kv put secret/database/config \
    username=dbadmin \
    password=SuperSecretP@ssw0rd \
    host=postgres.database.svc.cluster.local \
    port=5432

# 驗證秘密已建立
kubectl exec -n vault-system vault-0 -- \
  vault kv get secret/database/config

生產環境的部署需要更嚴謹的配置。高可用性配置透過多個 Vault 實例組成的叢集來實現,使用 Raft 或 Consul 作為儲存後端。TLS 加密應該在所有通訊通道上啟用,包含客戶端到伺服器以及伺服器之間的通訊。自動解封機制透過 Auto Unseal 功能實現,可以利用雲端 KMS 服務自動解封 Vault,避免人工介入的需求。

建立 SecretStore 資源以連線到 Vault。這個資源定義了 External Secrets Operator 如何與 Vault 通訊,包含伺服器位置、認證方式、KV 引擎版本等關鍵配置。

apiVersion: v1
kind: Secret
metadata:
  name: vault-token
  namespace: external-secrets-system
type: Opaque
stringData:
  token: "root-token"
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: default
spec:
  provider:
    vault:
      server: "http://vault.vault-system.svc.cluster.local:8200"
      path: "secret"
      version: "v2"
      auth:
        tokenSecretRef:
          name: "vault-token"
          key: "token"
          namespace: "external-secrets-system"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: default
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: database-credentials
    creationPolicy: Owner
    template:
      engineVersion: v2
      data:
        connection-string: |
          postgresql://{{ .username }}:{{ .password }}@{{ .host }}:{{ .port }}/myapp
  dataFrom:
    - extract:
        key: database/config

ExternalSecret 資源的 template 功能提供了強大的資料轉換能力。透過 Go template 語法,可以將從 Vault 擷取的多個欄位組合成複雜的配置格式,例如資料庫連線字串、設定檔內容等。creationPolicy 設定為 Owner 表示 External Secrets Operator 會管理目標 Secret 的完整生命週期,當 ExternalSecret 被刪除時,對應的 Secret 也會被自動清理。

# 套用配置
kubectl apply -f vault-secretstore.yaml

# 驗證 SecretStore 狀態
kubectl get secretstore -n default
kubectl describe secretstore vault-backend -n default

# 檢查產生的 Secret
kubectl get secret database-credentials -n default
kubectl get secret database-credentials -n default -o jsonpath='{.data.connection-string}' | base64 -d

# 監控同步狀態
kubectl get externalsecret database-credentials -n default -w
@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16

participant "External Secrets\nOperator" as eso
participant "SecretStore" as ss
participant "ExternalSecret" as es
participant "Vault API" as vault
database "Vault Storage" as vault_storage
participant "Kubernetes\nSecret" as k8s_secret

eso -> es: 監聽資源變更
activate eso
es -> ss: 讀取連線配置
activate ss
ss --> es: 返回 Vault 端點與認證
deactivate ss

eso -> vault: 使用 Token 認證
activate vault
vault -> vault_storage: 驗證權限
activate vault_storage
vault_storage --> vault: 授權成功
deactivate vault_storage
vault --> eso: 返回認證憑證
deactivate vault

eso -> vault: GET /v1/secret/data/database/config
activate vault
vault -> vault_storage: 擷取秘密資料
activate vault_storage
vault_storage --> vault: 返回加密資料
deactivate vault_storage
vault --> eso: 返回秘密資料
deactivate vault

eso -> eso: 套用 Template 轉換
eso -> k8s_secret: 建立/更新 Secret
activate k8s_secret
k8s_secret --> eso: 更新確認
deactivate k8s_secret
deactivate eso

note right of eso
  同步週期: 1 小時
  自動重試: 啟用
  錯誤通知: 事件記錄
end note

@enduml

Argo CD 的 Webhook 整合機制

Argo CD 預設採用輪詢機制來檢測 Git 倉儲的變更,預設的輪詢間隔為三分鐘。這種機制雖然簡單可靠,但在需要快速回應程式碼變更的場景中會產生不必要的延遲。Webhook 機制提供了事件驅動的替代方案,當 Git 倉儲發生變更時會立即通知 Argo CD,觸發同步流程。

Webhook 的運作基於 HTTP 回調機制。Git 平台在特定事件發生時會向預先配置的端點發送 POST 請求,請求內容包含了事件類型、倉儲資訊、提交詳情等資料。Argo CD 的 Webhook 端點位於 /api/webhook,能夠處理來自 GitHub、GitLab、Bitbucket、Gitea 等主流 Git 平台的通知。

在本地開發環境中測試 Webhook 功能需要額外的工具支援。由於 Git 平台需要能夠存取 Argo CD 的 Webhook 端點,而本地部署的 Argo CD 通常沒有公開的網路位址,因此需要使用隧道工具如 ngrok 或在本地部署 Git 伺服器。Gitea 是一個輕量級的 Git 伺服器實作,非常適合用於 Webhook 功能的本地測試。

# 部署 Gitea Git 伺服器
helm repo add gitea-charts https://dl.gitea.io/charts/
helm repo update

helm install gitea gitea-charts/gitea \
  --namespace git-system \
  --create-namespace \
  --set service.http.type=ClusterIP \
  --set gitea.admin.username=gitea_admin \
  --set gitea.admin.password=gitea_password \
  --set gitea.admin.email=admin@example.com

# 等待 Gitea 就緒
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=gitea \
  -n git-system --timeout=300s

# 建立連接埠轉發以存取 Gitea
kubectl port-forward -n git-system svc/gitea-http 3000:3000 &

# 登入 Gitea 並建立測試倉儲
# 瀏覽器開啟 http://localhost:3000
# 使用管理員帳號登入並建立新倉儲

在 Gitea 中建立倉儲後,需要將測試用的 Kubernetes manifests 推送到倉儲中。這些 manifests 可以是任何有效的 Kubernetes 資源定義,例如 Deployment、Service、ConfigMap 等。

# 克隆測試倉儲
git clone https://github.com/gitops-cookbook/pacman-kikd-manifests.git
cd pacman-kikd-manifests

# 修改遠端倉儲位址為 Gitea
git remote set-url origin http://localhost:3000/gitea_admin/pacman-manifests.git

# 推送到 Gitea
git push origin main

# 建立 Argo CD Application
argocd app create pacman-webhook \
  --repo http://gitea-http.git-system.svc.cluster.local:3000/gitea_admin/pacman-manifests.git \
  --path k8s \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace pacman \
  --sync-policy automated \
  --auto-prune \
  --self-heal

# 驗證 Application 建立成功
argocd app get pacman-webhook
argocd app sync pacman-webhook

Webhook 的配置在 Gitea 的倉儲設定中完成。需要指定 Argo CD 的 Webhook 端點位址,並選擇要觸發通知的事件類型。由於 Argo CD 與 Gitea 都在同一個 Kubernetes 叢集中,可以使用叢集內部的服務位址進行通訊,無需暴露到公網。

# Gitea Webhook 配置
# 在 Gitea UI 中進行配置:
# 1. 進入倉儲設定
# 2. 選擇 Webhooks 標籤
# 3. 新增 Webhook
#    - Payload URL: http://argocd-server.argocd.svc.cluster.local/api/webhook
#    - Content Type: application/json
#    - Secret: 可選,用於驗證請求來源
#    - Trigger: Push events
#    - Active: 啟用

# 測試 Webhook
# 修改倉儲中的任何檔案並提交
git commit -am "Test webhook trigger"
git push origin main

# 觀察 Argo CD 同步狀態
argocd app wait pacman-webhook --sync --health --timeout 300

# 檢查 Argo CD Server 日誌
kubectl logs -n argocd deployment/argocd-server --tail=50 | grep webhook

Webhook 機制大幅降低了變更感知的延遲,從分鐘級別降低到秒級別。在持續部署的實踐中,這種即時性對於快速疊代與問題修復至關重要。同時,Webhook 也減少了不必要的輪詢請求,降低了 Git 平台的負載與 API 配額消耗。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16

actor "開發者" as dev
participant "Git 倉儲\n(Gitea)" as git
participant "Webhook\n處理器" as webhook
participant "Argo CD\nAPI Server" as argocd
participant "Application\nController" as controller
participant "Kubernetes\nAPI" as k8s

dev -> git: 推送程式碼變更
activate git
git -> git: 偵測 Push 事件
git -> webhook: POST /api/webhook
activate webhook
note right
  事件資料:
  - 倉儲資訊
  - 提交 SHA
  - 分支名稱
  - 變更檔案
end note

webhook -> argocd: 解析事件資料
activate argocd
argocd -> argocd: 識別相關 Application
argocd -> controller: 觸發同步請求
deactivate argocd
deactivate webhook
deactivate git

activate controller
controller -> git: 取得最新 Manifests
activate git
git --> controller: 返回 YAML 檔案
deactivate git

controller -> controller: 比對期望狀態
controller -> k8s: 套用變更
activate k8s
k8s --> controller: 確認資源更新
deactivate k8s

controller -> argocd: 更新同步狀態
activate argocd
argocd --> dev: 通知部署完成
deactivate argocd
deactivate controller

@enduml

ApplicationSet 的多叢集管理策略

在企業級的 Kubernetes 部署場景中,通常需要管理多個叢集與環境。這些叢集可能分佈在不同的雲端供應商、地理區域或資料中心,每個叢集承載著特定階段的工作負載,例如開發、測試、預發佈與生產環境。手動管理每個環境中的 Application 資源不僅繁瑣且容易出錯,ApplicationSet 提供了範本化的解決方案來簡化這個過程。

ApplicationSet 是 Argo CD 的自訂資源,它引入了生成器概念來動態建立 Application 資源。生成器能夠從各種來源讀取參數,包含靜態列表、Git 倉儲結構、叢集註冊表等,然後將這些參數注入到 Application 範本中,產生多個實際的 Application 資源。這種方式實現了配置的集中管理與環境的標準化。

List 生成器是最直觀的類型,適合管理數量固定且變化不頻繁的環境。它在 ApplicationSet 定義中直接列出所有環境的參數,包含叢集位址、命名空間、特定配置等。這種方式的優勢是簡單明確,適合小規模的多環境管理。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-environment-app
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - list:
        elements:
          - env: development
            cluster: https://kubernetes.default.svc
            namespace: dev
            replicas: "1"
            resources: |
              requests:
                cpu: 100m
                memory: 128Mi
              limits:
                cpu: 200m
                memory: 256Mi
          - env: staging
            cluster: https://kubernetes.default.svc
            namespace: staging
            replicas: "2"
            resources: |
              requests:
                cpu: 200m
                memory: 256Mi
              limits:
                cpu: 500m
                memory: 512Mi
          - env: production
            cluster: https://prod-cluster.example.com
            namespace: prod
            replicas: "5"
            resources: |
              requests:
                cpu: 1000m
                memory: 1Gi
              limits:
                cpu: 2000m
                memory: 2Gi
  template:
    metadata:
      name: 'myapp-{{.env}}'
      labels:
        environment: '{{.env}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/example/app-manifests.git
        targetRevision: main
        path: 'overlays/{{.env}}'
        helm:
          parameters:
            - name: replicaCount
              value: '{{.replicas}}'
            - name: resources
              value: '{{.resources}}'
      destination:
        server: '{{.cluster}}'
        namespace: '{{.namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
          allowEmpty: false
        syncOptions:
          - CreateNamespace=true
          - PrunePropagationPolicy=foreground
        retry:
          limit: 5
          backoff:
            duration: 5s
            factor: 2
            maxDuration: 3m

goTemplate 選項啟用了 Go template 語法,相較於傳統的字串替換方式提供了更強大的邏輯處理能力。syncPolicy 的 automated 設定實現了完全自動化的同步,prune 選項會刪除 Git 中已移除的資源,selfHeal 選項會自動修復手動修改的資源。retry 配置定義了同步失敗時的重試策略,採用指數退避演算法避免過於頻繁的重試。

Git 目錄生成器根據 Git 倉儲的目錄結構來動態生成 Application。這種方式特別適合基於 Kustomize overlays 或 Helm values 的多環境管理模式。當需要新增環境時,僅需在 Git 倉儲中建立對應的目錄結構,ApplicationSet 會自動偵測並建立新的 Application。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - git:
        repoURL: https://github.com/example/cluster-config.git
        revision: main
        directories:
          - path: 'addons/*'
            exclude: 'addons/README.md'
  template:
    metadata:
      name: '{{.path.basename}}'
      labels:
        addon-type: '{{.path.basename}}'
    spec:
      project: infrastructure
      source:
        repoURL: https://github.com/example/cluster-config.git
        targetRevision: main
        path: '{{.path.path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

這個範例會為 addons 目錄下的每個子目錄建立一個 Application。path.basename 提取目錄名稱作為 Application 名稱與命名空間,path.path 則提供完整的目錄路徑用於指定 source。這種設計特別適合管理叢集附加元件,例如監控系統、日誌收集器、Ingress 控制器等基礎設施元件。

Git 檔案生成器則根據特定模式的設定檔來產生 Application。這提供了更精細的控制能力,可以在設定檔中定義複雜的參數結構。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: microservices-fleet
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - git:
        repoURL: https://github.com/example/services-config.git
        revision: main
        files:
          - path: 'services/**/config.json'
  template:
    metadata:
      name: '{{.service.name}}-{{.environment}}'
      labels:
        service: '{{.service.name}}'
        team: '{{.service.team}}'
        environment: '{{.environment}}'
    spec:
      project: '{{.service.team}}'
      source:
        repoURL: '{{.service.repoURL}}'
        targetRevision: '{{.service.version}}'
        path: 'deploy/{{.environment}}'
        helm:
          parameters:
            - name: image.tag
              value: '{{.service.version}}'
            - name: resources.limits.cpu
              value: '{{.service.resources.cpu}}'
            - name: resources.limits.memory
              value: '{{.service.resources.memory}}'
      destination:
        server: '{{.cluster.url}}'
        namespace: '{{.service.namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

對應的設定檔範例:

{
  "environment": "production",
  "cluster": {
    "url": "https://prod-cluster.example.com",
    "name": "prod-us-west"
  },
  "service": {
    "name": "api-gateway",
    "team": "platform",
    "namespace": "api",
    "repoURL": "https://github.com/example/api-gateway.git",
    "version": "v2.5.0",
    "resources": {
      "cpu": "2000m",
      "memory": "2Gi"
    }
  }
}
@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 150

package "ApplicationSet 架構" {
  component "ApplicationSet\nController" as controller
  
  package "生成器類型" {
    component "List Generator" as list_gen
    component "Git Directory\nGenerator" as git_dir_gen
    component "Git File\nGenerator" as git_file_gen
    component "Cluster Generator" as cluster_gen
    component "Pull Request\nGenerator" as pr_gen
  }
  
  component "Application\nTemplate" as template
  
  package "產生的 Applications" {
    component "dev-app" as dev
    component "staging-app" as staging
    component "prod-app" as prod
  }
}

controller -down-> list_gen: 執行
controller -down-> git_dir_gen: 執行
controller -down-> git_file_gen: 執行
controller -down-> cluster_gen: 執行
controller -down-> pr_gen: 執行

list_gen -right-> template: 注入參數
git_dir_gen -right-> template: 注入參數
git_file_gen -right-> template: 注入參數

template -down-> dev: 產生
template -down-> staging: 產生
template -down-> prod: 產生

@enduml

Pull Request 預覽環境的自動化

在現代軟體開發流程中,Pull Request 是程式碼審查與協作的核心機制。為每個 PR 自動建立獨立的預覽環境能夠大幅提升審查效率,讓審查者能夠在真實的執行環境中驗證變更的效果。ApplicationSet 的 Pull Request 生成器專門為這個場景設計,能夠自動為標記的 PR 建立臨時環境。

Pull Request 生成器持續監控 Git 倉儲中的 PR 狀態,當偵測到符合條件的 PR 時,會自動建立對應的 Application 資源。當 PR 合併或關閉時,Application 會被自動刪除,確保資源不會被浪費。這種完整的生命週期管理使得預覽環境的維護成本降到最低。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: pr-preview
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - pullRequest:
        github:
          owner: example-org
          repo: web-application
          tokenRef:
            secretName: github-token
            key: token
          labels:
            - preview
          requeueAfterSeconds: 30
        requeueAfterSeconds: 60
  template:
    metadata:
      name: 'preview-pr-{{.number}}'
      labels:
        pr-number: '{{.number}}'
        branch: '{{.branch}}'
        author: '{{.author}}'
    spec:
      project: preview
      source:
        repoURL: 'https://github.com/example-org/web-application.git'
        targetRevision: '{{.head_sha}}'
        path: deploy/kubernetes
        helm:
          parameters:
            - name: image.tag
              value: 'pr-{{.number}}'
            - name: ingress.hosts[0].host
              value: 'pr-{{.number}}.preview.example.com'
      destination:
        server: https://kubernetes.default.svc
        namespace: 'preview-pr-{{.number}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
      info:
        - name: 'Pull Request'
          value: 'https://github.com/example-org/web-application/pull/{{.number}}'
        - name: 'Preview URL'
          value: 'https://pr-{{.number}}.preview.example.com'

這個配置會為每個標記為 preview 的 PR 建立獨立的預覽環境。requeueAfterSeconds 設定了輪詢間隔,雖然 ApplicationSet 支援 Webhook,但輪詢機制提供了更可靠的備援方案。info 區段在 Argo CD UI 中顯示額外資訊,方便團隊成員快速存取預覽環境。

為了完整實現預覽環境功能,還需要配置 Ingress 資源與 DNS 解析。通常採用萬用字元 DNS 記錄將所有預覽子網域指向 Ingress Controller,然後透過 Host-based routing 將請求路由到對應的服務。

# 預覽環境的 Ingress 範本
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: preview-ingress
  namespace: preview-pr-{{.number}}
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - pr-{{.number}}.preview.example.com
      secretName: preview-pr-{{.number}}-tls
  rules:
    - host: pr-{{.number}}.preview.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app
                port:
                  number: 80

玄貓認為,GitOps 與外部金鑰管理系統的整合是雲端原生安全實踐的重要里程碑。透過 External Secrets Operator,我們能夠在不犧牲 GitOps 原則的前提下實現企業級的敏感資料管理。ApplicationSet 的引入則大幅簡化了多環境與多叢集的管理複雜度,使得大規模部署變得可控且可維護。Pull Request 預覽環境的自動化進一步提升了開發團隊的協作效率,讓程式碼審查過程更加直觀與可靠。這些技術的組合應用構建了完整的現代化部署管道,在安全性、效率與可靠性之間達到了良好的平衡。隨著 Kubernetes 生態系統的持續演進,預期會有更多創新的工具與模式出現,但核心的 GitOps 理念與安全最佳實踐將繼續指引技術選型的方向。持續關注社群動態,適時採納經過驗證的新技術,是保持技術競爭力與系統現代化的關鍵。