Kubernetes 工程師的 Argo CD 實戰指南

在 Kubernetes 的維運實務中,許多團隊的自動化起點都是 push 式 CI/CD:CI runner 建置完容器映像檔後,直接在 pipeline 裡執行 kubectl apply 或 helm upgrade 把變更推入叢集。

這套模式在規模擴大後痛點顯著:CI runner 必須持有叢集高權限憑證、手動 kubectl edit 造成的狀態漂移(drift)無法追蹤,且刪除 YAML 後叢集內常遺留孤兒資源。

pull 式 GitOps 從根本上反轉了這個流程——叢集內部的控制器主動比對 Git 儲存庫宣告的「目標期望狀態」與叢集的「實際運作狀態」,發現偏差便自動協調收斂。CI 的職責簡化為「產出映像檔並向 Git 提交變更」,不再直接碰觸叢集 API。

「唯一可信來源是 Git,叢集只是 Git 的即時投影。」

本文立足於 Argo CD v3.5.3 穩定版本,跳過基礎 Kubernetes 物件介紹,專注剖析 Argo CD 的核心運作架構、狀態同步與修復策略、Diff 調校陷阱,以及生產環境必備的權限邊界與避雷清單。


Argo CD 核心架構與元件職責

三大元件

Argo CD 的核心架構由三個關鍵元件協同運作:

 +------------------------------------+
 | Developer / CI                     |
 +------------------------------------+
                   |
                   | commit
                   v
 +------------------------------------+
 | Git Config Repo                    |
 +------------------------------------+
                   |
                   | polling (120s+jitter)
                   | or Git Webhook
                   v
 +------------------------------------+
 | Kubernetes Cluster                 |
 |                                    |
 |  +--------------------------+      |
 |  | argocd-repo-server       |      |
 |  | (render manifests)       |      |
 |  +--------------------------+      |
 |                |                   |
 |                v                   |
 |  +--------------------------+      |
 |  | application-controller   |      |
 |  | (compare Live/Target)    |      |
 |  +--------------------------+      |
 |                |                   |
 |                v                   |
 |  +--------------------------+      |
 |  | Live K8s Objects         |      |
 |  +--------------------------+      |
 +------------------------------------+

此外,標準安裝 manifest 還包含負責產生多應用程式的 argocd-applicationset-controller、用以暫存已渲染 manifest 的 argocd-redis,以及處理 SSO 與通知的附屬元件。

Sync Status 與 Health Status

在狀態判定上,務必釐清 Sync Status 與 Health Status 是兩組正交的面向(詳見資源健康判定機制):

一個 Pod 映像檔損毀、持續處於 CrashLoopBackOff 的服務,在 Argo CD 中完全可能是 Synced 卻同時處於 Degraded 的狀態。


核心物件:Application 與 AppProject 權限邊界

在宣告式管理中,所有部署設定均由兩個自訂資源定義(CRD)構成:

兩者的關係是:每個 Application 必定歸屬於一個 AppProject,由 AppProject 劃定它的活動範圍。

 +----------------------------------------------------+
 | AppProject (e.g., team-web)                        |
 | - Allowed Source Repos                             |
 | - Destination Clusters & Namespaces                |
 | - Resource Whitelist / Blacklist                   |
 | - Project-level RBAC Roles                         |
 |                                                    |
 |   +--------------------------------------------+   |
 |   | Application (e.g., guestbook-prod)         |   |
 |   | - spec.source (repo, path, targetRevision) |   |
 |   | - spec.destination (server, namespace)     |   |
 |   | - spec.syncPolicy (automated, retry)       |   |
 |   +--------------------------------------------+   |
 +----------------------------------------------------+

Application 定義規範

Application CRD 宣告了單一應用程式的來源與目的地:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook-staging
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: team-web
  source:
    repoURL: https://github.com/example-org/k8s-config.git
    targetRevision: main
    path: apps/guestbook/overlays/staging
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook-staging

實務設定需注意三項關鍵規則:

  1. 命名空間限制:Application 與 AppProject 預設必須建立在 Argo CD 本身所在的 namespace(通常為 argocd)。
  2. 目的地參數互斥:destination 中的 server 與 name 僅能擇一指定,同時宣告會導致解析錯誤。
  3. 刪除保護 Finalizer:若未加上 resources-finalizer.argocd.argoproj.io,刪除 Application 物件時叢集內對應的 Deployment 與 Service 不會被清理;加上此 finalizer 才會連帶觸發串聯刪除。

AppProject 多租戶安全邊界

AppProject 是多團隊共享 Argo CD 平台時的關鍵隔離邊界。未特別指定的 Application 會被劃入 default project,而 default project 預設允許任意來源儲存庫、任意目的叢集與所有資源種類(參考 AppProject 多租戶權限),在正式環境存在極高安全風險。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-web
  namespace: argocd
spec:
  description: Web 團隊專案邊界
  sourceRepos:
    - https://github.com/example-org/k8s-config.git
  destinations:
    - server: https://kubernetes.default.svc
      namespace: guestbook-*
  clusterResourceWhitelist:
    - group: ''
      kind: Namespace
  namespaceResourceBlacklist:
    - group: ''
      kind: ResourceQuota
    - group: networking.k8s.io
      kind: NetworkPolicy

安全地雷:若某個 AppProject 的 destinations 允許部署至 argocd namespace,該專案底下的應用程式就能藉由修改 Argo CD 自身設定取得整個平台的最高權限,因此必須嚴格限制可部署的 namespace 白名單。


同步策略、自我修復與導入階段

spec.syncPolicy 定義了控制器偵測到狀態差異時的自動化行為(參考自動同步與自我修復策略)。

spec:
  syncPolicy:
    automated:
      enabled: true
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

自動同步核心開關

生產環境推薦導入順序

在 GitOps 流程中,人工審查關卡在 Git 端——CI 對 config repo 發出 PR,經過 review 後合併。

合併之後,Argo CD 偵測到差異,若設定為自動同步(automated: true)就直接套用;若維持手動同步,則還需要有人在 Argo CD UI 或 CLI 額外點選「Sync」才會生效。

在團隊剛接觸 GitOps 時,切忌第一天就在正式環境全開自動同步。建議採取循序漸進的四階段節奏:

  1. 初期手動驗證:前一至二週維持手動同步,讓維運團隊熟悉觀察 diff 與 OutOfSync 提示,並找出各類靜態宣告瑕疵。
  2. 開發環境自動化:於 dev 環境啟用 automated 與 selfHeal,建立「任何救火都要回 Git 提交」的團隊工作共識。
  3. 啟用 Prune 清理:確認儲存庫結構穩定且無命名衝突後,在非生產環境開啟 prune: true。
  4. 生產環境權衡:若團隊已落實嚴謹的 PR Review 上線審查,正式環境可開啟自動同步;若需要特定維護時段發布,則搭配 Sync Windows 設定允許同步的時段。

精準編排:Sync Waves 與 Hooks 機制

當應用程式部署涉及嚴格先後相依(例如:資料庫 Migration 必須在 Web 服務更新前完成,或是 CRD 必須先於 Custom Resource 就緒),需仰賴 Sync Phases(Hooks) 與 Sync Waves 進行順序調度(參考 Sync Phases 與 Sync Waves)。

 Phase: PreSync
 +-----------------------------+
 | wave -1: DB Migration Job   |
 | (hook: PreSync)             |
 +-----------------------------+
                |
                v
        Wait for Success
                |
                v
 Phase: Sync (Main Apply)
 +-----------------------------+
 | wave 0: ConfigMap / Secret  |
 +-----------------------------+
                |
                v
 +-----------------------------+
 | wave 1: App Deployment      |
 +-----------------------------+

Hook 執行階段與刪除原則

透過在資源 metadata 加入 argocd.argoproj.io/hook annotation,可將資源納入特定階段:

Hook 階段執行時機典型用途與行為特性
PreSync主要 manifest 套用前執行 DB Migration;失敗時立即中斷同步,阻擋新版上線
Sync與主要 manifest 同步執行搭配一般資源套用;可用 sync-wave 精細排序
PostSync所有資源套用且處於 Healthy 後觸發部署後通知、快取預熱或整合測試
SyncFail同步失敗時執行清理作業或觸發告警通知

Hook 的清理策略由 argocd.argoproj.io/hook-delete-policy 控制。預設值為 BeforeHookCreation,會在下一次執行同名 hook 前清理舊資源;本次執行的 Job 無論成敗都會保留到下一次同步,失敗的 Migration Job 因此能留在叢集中供除錯與檢查 log。若改用 HookSucceeded,Job 在成功後就會被刪除。

Sync Wave 與排序規則

argocd.argoproj.io/sync-wave 支援整數(可為負數),數值越小越先套用。

整體同步排序規則遵循:Phase → Wave(小至大)→ 資源類型(Namespace 優先於一般資源)→ 資源名稱。Argo CD 會等待當前 wave 內的所有資源全數轉為 Healthy 後,才會邁向下一波次;反之,在 Prune 階段則採相反順序(wave 大者先刪除)。

實務設計守則:

  1. PreSync 階段執行的 Migration 腳本必須具備冪等性(Idempotency)與向後相容性(Backward Compatibility)。因為在 PreSync 跑完至新 Pod 啟動之間,舊版 Pod 仍在線上服務;且後續若執行程式碼 revert,資料庫 schema 並不會自動倒退。
  2. 若部署包含自訂 CRD 與 CR,建議將 CRD 分離或置於負數 wave,並搭配 SkipDryRunOnMissingResource=true,避免 Dry-run 階段因 schema 尚未就緒而中斷。

實例:DB Migration 先於 Web 服務更新

假設一個 Web 應用需要在部署新版前先跑完資料庫 schema 變更。整體編排如下:Migration Job 放在 PreSync 階段,確保它在任何主要資源套用前執行並成功;Web Deployment 則放在 Sync 階段的 wave 1,等 ConfigMap 等基礎資源(wave 0)就緒後才更新。

# db-migrate.yaml — PreSync Hook
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/sync-wave: "-1"
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: example-org/web-app:v2.3.0
          command: ["python", "manage.py", "migrate", "--no-input"]
      restartPolicy: Never
  backoffLimit: 0
---
# web-deployment.yaml — 主要 Sync 資源
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  annotations:
    argocd.argoproj.io/sync-wave: "1"
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: web
          image: example-org/web-app:v2.3.0

執行順序為:PreSync wave -1(Migration Job)→ 等待 Job 成功 → Sync wave 0(ConfigMap / Secret)→ Sync wave 1(Web Deployment)。若 Migration Job 失敗,整個同步中斷,舊版 Pod 繼續服務,不會讓新程式碼碰到不相容的 schema。


映像檔更新與 Git 儲存庫結構

在 GitOps 架構下,CI 建置完成新映像檔後,需透過明確的自動化策略更新 Git 儲存庫。

Config Repo 與 Source Repo 分離

最佳實踐是將 Kubernetes manifest 存放於獨立的 Config 儲存庫(又稱 GitOps Repo、Deploy Repo,或團隊慣稱的 Infra Repo),與存放應用程式原始碼的 Source Repo(App Repo)分開管理。

這不僅能避免單純調整設定時觸發昂貴的程式碼編譯,更能有效切割「開發寫入」與「生產部署」的存取權限。

在映像檔標籤管理上,嚴禁在正式環境使用 latest。manifest 在特定 Git revision 必須保持不可變性(Immutable);使用 latest 會破壞 Git 歷史與叢集運作的確定性關聯。

映像檔更新策略評析

機制運作方式優點限制與取捨
CI 提交 Config RepoCI 腳本修改 overlay 中的 image tag,直接 commit 或開啟 PR流程最透明、無額外維運負擔、適合絕大多數團隊CI 需 Config Repo 權限,需處理並行 push 衝突
Argo CD Image Updater獨立 controller 監控 Registry,發現新 tag 後自動寫回 GitCI 無需存取 Config Repo需設定 git write-back 模式;官方不建議用於關鍵正式環境

多數團隊建議以 「CI commit 至 Config Repo」 作為起點,dev 環境由 CI 直接提交,prod 環境則由 CI 自動開立 PR 走人工審核。若組織環境推進規則極度複雜,可再評估專屬的階段交付工具(如 Kargo)。

推薦儲存庫結構

以下是 Argo CD 社群最經典的 Config Repo 範本——以 Kustomize base/overlays 區分環境,並將 Application 與 AppProject 宣告集中在 argocd/ 目錄統一管理:

 k8s-config/
 +-- apps/
 |   +-- guestbook/
 |       +-- base/
 |       |   +-- kustomization.yaml
 |       |   +-- deployment.yaml
 |       |   +-- service.yaml
 |       +-- overlays/
 |           +-- dev/
 |           |   +-- kustomization.yaml
 |           +-- prod/
 |               +-- kustomization.yaml
 +-- argocd/
     +-- projects/
     |   +-- team-web.yaml
     +-- applications/
         +-- guestbook-dev.yaml
         +-- guestbook-prod.yaml

架構核心要點:


回退(Rollback)機制:正統途徑與緊急防護

在 GitOps 架構中,回退的標準作業流程與傳統操作截然不同。

正統回退:git revert

由於 Git 是唯一的真實來源,正統的回退方式是在 Config 儲存庫對有問題的 commit 執行 git revert,再透過 PR 合併回主分支。在生產環境中,不應直接 push 到 main,而是開一個 revert PR 走正常的 review 流程:

git revert <faulty-commit-sha>
# 開立 PR,經 review 後合併

此方式能保留完整問題修復紀錄,並與自動同步機制完美相容,不會產生叢集與 Git 分歧的狀態。

UI / CLI Rollback 的限制與緊急煞車程序

Argo CD UI 的「Rollback」功能與 git revert 的本質截然不同——它是告訴 controller 暫時同步到某個舊的 Git revision,不會修改 Git 本身。

這代表如果開著自動同步或 selfHeal,controller 很快會把叢集拉回 Git HEAD(即那個有問題的版本),rollback 就被覆蓋了。換言之,UI rollback 本質上是緊急止血,不是永久解。

官方文件也明確規定:開啟自動同步(automated sync)的應用程式不允許直接執行 rollback。

WARNING

UI Rollback 僅是緊急止血:開啟 automated 或 selfHeal 時,controller 會在數秒內把叢集拉回 Git HEAD,rollback 隨即被覆蓋。止血前務必先暫停自動同步。

若必須在緊急時刻透過 UI 介入救火,應落實四步煞車程序:

  1. 暫停自動同步:執行 argocd app set <APP> --sync-policy none 關閉自動收斂。
  2. 執行緊急還原:透過 UI 或執行 argocd app rollback <APP> 回退至穩定版本。
  3. 提交 Git Revert:事後務必在 Git 儲存庫提交 git revert,讓 Git 目標狀態與叢集運作狀態重新對齊。
  4. 恢復自動同步:確認狀態一致後重新啟用自動同步,避免留下未受 GitOps 控管的孤兒設定。

Diff 計算與 OutOfSync 深度調校

導入 Argo CD 時最常見的困擾是:明明剛同步完成,狀態卻立刻變回 OutOfSync,或是控制器與其他元件陷入來回覆寫的循環。

常見漂移根源

  1. HPA 管理 Pod 副本數:Deployment 宣告了 replicas: 3,而 HPA 將其動態擴展至 6。Argo CD 偵測到差異判定為 OutOfSync;若開啟 selfHeal,控制器會將其改回 3,隨後 HPA 又擴展至 6。官方最佳實踐:交由 HPA 管理時,Deployment manifest 應直接移除 replicas 欄位。
  2. Admission Webhook 與 Sidecar 注入:Service Mesh 注入的 init container 或 cert-manager 補上的 caBundle 欄位會造成比對偏差。
  3. 數值格式正規化:YAML 中宣告的 1000m 被 Kubernetes API server 轉為 1,或 3072Mi 被存為 3Gi,引發字面比對不一致。

ignoreDifferences 與關鍵設定

處理差異的優先原則是能修改 manifest 規格就先修正 manifest;若屬第三方 controller 無法避免的變動,再透過 ignoreDifferences 忽略:

spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      name: guestbook
      jsonPointers:
        - /spec/replicas
    - group: apps
      kind: Deployment
      jqPathExpressions:
        - .spec.template.spec.initContainers[] | select(.name == "istio-init")
  syncPolicy:
    syncOptions:
      - RespectIgnoreDifferences=true

WARNING

ignoreDifferences 預設僅影響 diff 計算,不影響同步套用:同步時控制器仍會嘗試套用 Git 中的數值。必須明確加上 syncOptions: [RespectIgnoreDifferences=true],才能在同步時真正略過指定欄位。

Server-Side Diff 與 Server-Side Apply

Argo CD 預設採用 Legacy 三方比對策略。自 v3.1.0 起,Server-Side Diff 已正式升級為 Stable 狀態。

Server-Side Diff 會對資源執行 dry-run Server-Side Apply(SSA),將 API Server 回傳的預測結果與 live 狀態進行比對。

其核心優勢在於:Kubernetes API Server 的 Admission Webhook 與 schema 預設值會直接參與計算,且 SSA 透過 fieldManager 管理欄位所有權,大幅降低因 Webhook 變更欄位造成的誤報。

實務上常將 Server-Side Diff 與 Server-Side Apply 搭配啟用:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  annotations:
    argocd.argoproj.io/compare-options: ServerSideDiff=true
spec:
  syncPolicy:
    syncOptions:
      - ServerSideApply=true

Secret 安全管理最佳實踐

在 GitOps 架構中,「所有物件進 Git」絕對不包含明文的 Kubernetes Secret(base64 僅為編碼而非加密)。官方金鑰安全管理指南將實踐模式劃分為兩大陣營:

 [Git Repo: Encrypted CR / External Ref]
         |
         | (Argo CD syncs CR as is, no plaintext exposure)
         v
 [K8s Cluster: Operator / ESO pulls secret from Vault/KMS]
         |
         v
 [K8s Secret Object Created In-Cluster]

做法一:目的叢集端解密(強烈推薦)

Git 儲存庫僅保存加密過的自訂資源或外部金鑰參照,由叢集內部 Operator 在地生成 Secret 物件:

工具Git 宣告內容適合情境
External Secrets Operator(ESO)ExternalSecret(參照外部金鑰庫)具備雲端 Key Vault(AWS/GCP/Vault),需集中輪替
Sealed SecretsSealedSecret(叢集公鑰加密)無外部金鑰庫、追求最精簡的在地解密方案
Secrets Store CSI DriverSecretProviderClass(Volume 掛載)敏感憑證不希望以 K8s Secret 物件持久化於 etcd

架構優勢:Argo CD 完全無需接觸敏感明文,且金鑰輪替與應用程式同步流程徹底解耦。

做法二:Repo-Server 渲染時注入(潛在反模式)

透過 Config Management Plugin(如 argocd-vault-plugin)在 manifest 渲染階段將金鑰注入。風險在於:渲染後的明文 manifest 會暫存在 argocd-redis 快取中,且能透過 gRPC 介面直接讀取。除非有歷史包袱,新專案強烈建議一律採用目的端解密方案。


生產實務踩雷與除錯清單

在生產環境維運 Argo CD 時,建議隨時掌握以下常見問題的查修方向:


結語:讓叢集成為 Git 的忠實投影

Argo CD 想兌現的承諾很簡單:叢集狀態永遠與 Git 一致。

要做到這點,靠的是團隊先建立「所有變更都回 Git」的紀律,再按節奏把 selfHeal、prune 逐一打開。急著全開,反而可能在第一次誤刪事件後失去團隊的信任。

流程跑順之後,生產部署就是一張 PR 合併後靜靜完成的事——無聊、可預期,正是它該有的樣子。