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。
NOTE
「唯一可信來源是 Git,叢集只是 Git 的即時投影。」
本文立足於 Argo CD v3.5.3 穩定版本,跳過基礎 Kubernetes 物件介紹,專注剖析 Argo CD 的核心運作架構、狀態同步與修復策略、Diff 調校陷阱,以及生產環境必備的權限邊界與避雷清單。
Argo CD 核心架構與元件職責
三大元件
Argo CD 的核心架構由三個關鍵元件協同運作:
- API Server(
argocd-server):提供 Web UI、CLI 與外部系統串接的 gRPC/REST API 端點。負責應用程式生命週期管理、權限控制(RBAC)、憑證儲存(K8s Secret)以及接收來自 Git 的 webhook 推播。 - Repository Server(
argocd-repo-server):負責維護 Git 儲存庫的本機快取。傳入 repo URL、target revision、目錄路徑與參數後,在此執行 Helm 樣板渲染或 Kustomize build,產出最終的標準 Kubernetes manifest。 - Application Controller(
argocd-application-controller):作為 Kubernetes 自訂控制器,持續比對叢集 live 狀態與 Git target 狀態,精確計算差異、觸發狀態收斂,並調度 PreSync、Sync、PostSync 等生命週期 hook。
+------------------------------------+
| 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 是兩組正交的面向(詳見資源健康判定機制):
- Sync Status(
Synced/OutOfSync):代表叢集物件定義是否與 Git 完全一致。 - Health Status(
Healthy/Progressing/Degraded):代表 Pod、Service 等資源在叢集內是否真正正常提供服務。
一個 Pod 映像檔損毀、持續處於 CrashLoopBackOff 的服務,在 Argo CD 中完全可能是 Synced 卻同時處於 Degraded 的狀態。
核心物件:Application 與 AppProject 權限邊界
在宣告式管理中,所有部署設定均由兩個自訂資源定義(CRD)構成:
- Application 描述「把什麼東西部署到哪裡」——一份 Application 對應一組 Git 來源路徑與一個目標叢集 namespace,是 Argo CD 最基本的部署單元。
- AppProject 則是 Application 的權限圍牆——它規定底下的 Application 能從哪些 Git 儲存庫拉取設定、能部署到哪些叢集與 namespace,以及能操作哪些 Kubernetes 資源種類。
兩者的關係是:每個 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
實務設定需注意三項關鍵規則:
- 命名空間限制:
Application與AppProject預設必須建立在 Argo CD 本身所在的 namespace(通常為argocd)。 - 目的地參數互斥:
destination中的server與name僅能擇一指定,同時宣告會導致解析錯誤。 - 刪除保護 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
自動同步核心開關
automated.enabled:開啟自動同步。設為false時,其餘子選項均不會觸發。prune:預設為關閉。開啟後,Git 中移除的資源才會從叢集中刪除。allowEmpty:預設為false,可避免因路徑設定錯誤導致 target 清單為空時,一口氣誤刪叢集內所有資源。selfHeal:預設為關閉。開啟後,任何人透過kubectl edit或手動修改叢集產生的 drift,將在數秒內被控制器強制拉回 Git 的期望狀態。retry:自動同步失敗時採用指數退避演算法重試。注意:同一 commit 若未設定 retry,同步失敗後將不再重複嘗試。
生產環境推薦導入順序
在 GitOps 流程中,人工審查關卡在 Git 端——CI 對 config repo 發出 PR,經過 review 後合併。
合併之後,Argo CD 偵測到差異,若設定為自動同步(automated: true)就直接套用;若維持手動同步,則還需要有人在 Argo CD UI 或 CLI 額外點選「Sync」才會生效。
在團隊剛接觸 GitOps 時,切忌第一天就在正式環境全開自動同步。建議採取循序漸進的四階段節奏:
- 初期手動驗證:前一至二週維持手動同步,讓維運團隊熟悉觀察 diff 與
OutOfSync提示,並找出各類靜態宣告瑕疵。 - 開發環境自動化:於 dev 環境啟用
automated與selfHeal,建立「任何救火都要回 Git 提交」的團隊工作共識。 - 啟用 Prune 清理:確認儲存庫結構穩定且無命名衝突後,在非生產環境開啟
prune: true。 - 生產環境權衡:若團隊已落實嚴謹的 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 大者先刪除)。
實務設計守則:
- PreSync 階段執行的 Migration 腳本必須具備冪等性(Idempotency)與向後相容性(Backward Compatibility)。因為在 PreSync 跑完至新 Pod 啟動之間,舊版 Pod 仍在線上服務;且後續若執行程式碼 revert,資料庫 schema 並不會自動倒退。
- 若部署包含自訂 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 歷史與叢集運作的確定性關聯。
NOTE
延伸閱讀:OCI 入門:容器世界的共同語言
映像檔更新策略評析
| 機制 | 運作方式 | 優點 | 限制與取捨 |
|---|---|---|---|
| CI 提交 Config Repo | CI 腳本修改 overlay 中的 image tag,直接 commit 或開啟 PR | 流程最透明、無額外維運負擔、適合絕大多數團隊 | CI 需 Config Repo 權限,需處理並行 push 衝突 |
| Argo CD Image Updater | 獨立 controller 監控 Registry,發現新 tag 後自動寫回 Git | CI 無需存取 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
架構核心要點:
- 以目錄區分環境,避免以 branch 區分環境:所有環境追蹤同一個
main分支,透過 Kustomize overlay 管理差異。若用 branch 區分環境,將導致版本推進陷入 cherry-pick 與 merge conflict 的困境。 - Application 宣告納入版控:將 Application 與 AppProject 檔案置於
argocd/目錄,使平台設定本身具備可審查與可重建性。當應用程式數量規模擴大時,可進一步引入ApplicationSet的 Git Directory Generator,自動掃描目錄產生對應 Application。
回退(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 介入救火,應落實四步煞車程序:
- 暫停自動同步:執行
argocd app set <APP> --sync-policy none關閉自動收斂。 - 執行緊急還原:透過 UI 或執行
argocd app rollback <APP>回退至穩定版本。 - 提交 Git Revert:事後務必在 Git 儲存庫提交
git revert,讓 Git 目標狀態與叢集運作狀態重新對齊。 - 恢復自動同步:確認狀態一致後重新啟用自動同步,避免留下未受 GitOps 控管的孤兒設定。
Diff 計算與 OutOfSync 深度調校
導入 Argo CD 時最常見的困擾是:明明剛同步完成,狀態卻立刻變回 OutOfSync,或是控制器與其他元件陷入來回覆寫的循環。
常見漂移根源
- HPA 管理 Pod 副本數:Deployment 宣告了
replicas: 3,而 HPA 將其動態擴展至 6。Argo CD 偵測到差異判定為OutOfSync;若開啟selfHeal,控制器會將其改回 3,隨後 HPA 又擴展至 6。官方最佳實踐:交由 HPA 管理時,Deployment manifest 應直接移除replicas欄位。 - Admission Webhook 與 Sidecar 注入:Service Mesh 注入的 init container 或 cert-manager 補上的
caBundle欄位會造成比對偏差。 - 數值格式正規化: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 Secrets | SealedSecret(叢集公鑰加密) | 無外部金鑰庫、追求最精簡的在地解密方案 |
| Secrets Store CSI Driver | SecretProviderClass(Volume 掛載) | 敏感憑證不希望以 K8s Secret 物件持久化於 etcd |
架構優勢:Argo CD 完全無需接觸敏感明文,且金鑰輪替與應用程式同步流程徹底解耦。
做法二:Repo-Server 渲染時注入(潛在反模式)
透過 Config Management Plugin(如 argocd-vault-plugin)在 manifest 渲染階段將金鑰注入。風險在於:渲染後的明文 manifest 會暫存在 argocd-redis 快取中,且能透過 gRPC 介面直接讀取。除非有歷史包袱,新專案強烈建議一律採用目的端解密方案。
生產實務踩雷與除錯清單
在生產環境維運 Argo CD 時,建議隨時掌握以下常見問題的查修方向:
- 刪除 Application 但資源未清理:檢查
metadata.finalizers是否包含resources-finalizer.argocd.argoproj.io。 - Push 後遲遲未觸發部署:Argo CD 預設輪詢間隔為 120 秒加上隨機 jitter(最多 180 秒)。需即時反應應在 Git 儲存庫設定 Webhook。
- CR 報
the server could not find the requested resource:若 CR 所屬的 CRD 是在同一次同步中由第三方 Controller 動態生成,應在該 CR 加上argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true。 - 應用程式卡在
Progressing狀態:部分 Ingress 或採用OnDelete策略的 StatefulSet 不會主動回寫預期的 status 欄位,需調整 Controller 設定或自訂 Resource Health Lua 腳本。 Manifest generation error (cached)錯誤:渲染失敗的結果會被 Redis 快取以避免重試風暴。除錯時需在 UI 點選 Hard Refresh(或執行argocd app get <APP> --hard-refresh),並調閱argocd-repo-serverPod 的即時日誌。helm ls查無已部署的 Chart:Argo CD 僅使用helm template渲染靜態 YAML 並自行套用,不會在叢集中建立 Helm release 紀錄,此屬正常設計行為。
結語:讓叢集成為 Git 的忠實投影
Argo CD 想兌現的承諾很簡單:叢集狀態永遠與 Git 一致。
要做到這點,靠的是團隊先建立「所有變更都回 Git」的紀律,再按節奏把 selfHeal、prune 逐一打開。急著全開,反而可能在第一次誤刪事件後失去團隊的信任。
流程跑順之後,生產部署就是一張 PR 合併後靜靜完成的事——無聊、可預期,正是它該有的樣子。
NOTE