Argo CD 持续调谐:从 Application 到生产级 GitOps 控制面
值班工程师为了止血,把生产 Deployment 的副本数从 3 手工改成 5。几秒后它又变回 3,Argo CD 页面显示 Synced;另一次发布里,页面同样是绿色,真实请求却持续返回 503。前一个现象不是 Kubernetes “不听命令”,而是持续调谐器在恢复 Git 中的期望状态;后一个现象则提醒团队:Git revision、渲染结果、集群对象、资源健康和业务成功是五层不同证据,任何一个绿色图标都不能替代整条发布链。
把 Argo CD 接入团队之前,要先准备一个可随时重建的 Kubernetes 测试集群、一个只含示例清单的 Git 仓库,以及能够创建 CRD 和控制面 RBAC 的安装身份。测试仓库使用 https://git.example.com/your-org/platform-config.git 一类脱敏地址,目标 namespace 使用 gitops-lab;生产仓库凭证、目标集群 token 和私钥不进入 Application YAML、命令历史或截图。安装版本必须固定到完整 patch,CLI 与 server 保持同版,并在执行前从 Releases 和对应 minor 的安装页核对发布资产、Kubernetes 测试矩阵与升级提示。
先看懂控制器到底在调谐什么
Argo CD 的核心不是网页,而是 Application。它把四件事绑定起来:source 指向 Git、Helm 或 OCI 中的期望来源,destination 指向目标集群与 namespace,project 选择授权边界,syncPolicy 决定是否以及怎样把差异收敛。repo-server 获取 source 并调用 Helm、Kustomize 等工具生成清单;application-controller 比较 desired 与 live,执行 apply 或删除;argocd-server 为 CLI、API 和 UI 提供入口。Redis 主要承载可重建缓存,不是权威状态库。
一次调谐可以按“取 revision → 渲染 manifests → 计算 diff → 执行 sync → 观察 health”理解。Synced 只说明当前解析出的期望集合与 live state 没有 Argo CD 认定的差异;Healthy 来自资源健康规则,例如 Deployment 的 observed generation 和副本状态;真实请求成功仍要由 smoke test、指标或业务事务证明。排障时必须记录实际 commit SHA、生成工具版本、Application condition、operation state 和目标对象事件,不能只截一张 UI。
AppProject 则是平台团队必须先设计的安全壳。它限制允许的 source repository、destination、集群级或 namespace 级资源以及项目角色。默认项目若允许任意仓库、任意目标和任意资源,那么“可以创建 Application”几乎等于“可以借控制器向集群写入任意对象”。团队应按租户或风险域创建项目,硬编码可接受的仓库与目标,再把 Application 创建权授给应用团队。
在隔离集群安装同版控制面与 CLI
Argo CD 有几种常见形态。install.yaml 是非 HA、多租户且带集群级权限的快速入口,适合本地验证;ha/install.yaml 增加受支持组件副本和 Redis HA,适合单个 Kubernetes 故障域内的生产控制面;namespace-install 形态减少本集群权限,CRD 需单独管理;Core 不含 API server 和 UI,适合单集群管理员直接通过 kubeconfig 操作。非 HA 不是缩小版生产架构,HA 也不等于跨站点灾备。
下面用完整版本变量安装测试控制面。不要把 stable、latest 或 master 当成可重复的供应链身份;升级到新的 patch 时,先读对应 release 与逐 minor upgrade notes,再修改变量。--server-side --force-conflicts 会接管字段所有权,所以生产升级前必须保存 diff,不能机械执行。
export ARGOCD_VERSION=v3.4.2
kubectl create namespace argocd
kubectl apply --server-side --force-conflicts \
-n argocd \
-f "https://raw.githubusercontent.com/argoproj/argo-cd/${ARGOCD_VERSION}/manifests/install.yaml"
kubectl -n argocd wait --for=condition=Available deployment/argocd-server --timeout=300s
kubectl -n argocd get pods
kubectl -n argocd port-forward svc/argocd-server 8080:443CLI 从同一 release 的操作系统资产安装,并校验 checksum 或签名来源;Windows、macOS 与 Linux 的下载方式见 CLI 安装入口。端口转发建立后,另开终端读取一次性初始密码、登录并立刻轮换。接入 SSO 和新管理员凭证后删除初始 Secret,防止它长期成为旁路入口。
argocd version --client
INITIAL_PASSWORD="$(argocd admin initial-password -n argocd | head -n 1)"
argocd login localhost:8080 --username admin --password "${INITIAL_PASSWORD}" --insecure
argocd account update-password
kubectl -n argocd delete secret argocd-initial-admin-secret
argocd version
argocd account get-user-info预期证据不是“Pod 都 Running”这么简单:argocd version 应同时显示同版 client/server,Deployment 应 Available,账号信息应对应刚登录的身份。若 UI 能开而 CLI 报 gRPC 错误,先检查 Ingress 是否正确承载 HTTP/2,必要时验证 --grpc-web;若自定义 namespace 安装后 controller 报 forbidden,检查 ClusterRoleBinding 中 ServiceAccount namespace 是否也随之修改。
用 AppProject 和 Application 接入真实仓库
先在仓库固定一个最小目录 apps/demo/,其中包含 ConfigMap、Deployment 与 Service。镜像使用不可变 digest,targetRevision 在实验中填写实际 commit SHA。branch、tag 和 HEAD 都是移动引用,适合持续跟踪环境分支,但审计记录仍要保存最终解析出的 SHA。私有仓库通过 repo credential Secret、External Secret 或受管身份注入,凭证绝不写进 repoURL。
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: demo-team
namespace: argocd
spec:
sourceRepos:
- https://git.example.com/your-org/platform-config.git
destinations:
- server: https://kubernetes.default.svc
namespace: gitops-lab
clusterResourceWhitelist: []
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Service
- group: apps
kind: Deployment
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: demo-api
namespace: argocd
spec:
project: demo-team
source:
repoURL: https://git.example.com/your-org/platform-config.git
targetRevision: <commit-sha>
path: apps/demo
destination:
server: https://kubernetes.default.svc
namespace: gitops-lab
syncPolicy:
syncOptions:
- CreateNamespace=true
- FailOnSharedResource=true提交对象后先执行 argocd app diff demo-api,确认只出现预期的三个资源,再手动 argocd app sync demo-api。随后用 argocd app get demo-api -o yaml 保存 status.sync.revision、status.conditions 与 status.operationState,用 kubectl -n gitops-lab rollout status deployment/demo-api 检查工作负载,最后通过端口转发或测试入口请求健康端点。只有 commit、render、object、health 和请求结果一致,项目接入才闭环。
仓库认证失败通常表现为 ComparisonError 或 repository connection error;目标不在 AppProject allowlist 会被项目策略拒绝;控制器凭据缺少 Kubernetes RBAC 则在 sync 阶段出现 forbidden。三者分别属于 source、policy 与 destination write,不要用“重新同步”掩盖分类。修复后清除 condition、重新 diff,并确认实际 revision 未被移动分支悄悄改变。
把手动同步升级成受控自动调谐
自动同步、prune 和 self-heal 是三个独立开关。automated.enabled: true 允许 OutOfSync 时自动应用;prune 允许删除 Git 中消失且仍被该 Application 跟踪的对象;selfHeal 允许 live 漂移触发再同步。开启 automated 不会隐式开启后两者。enabled: false 会关闭自动同步,即使其余三个字段仍是 true;兼容语义会把 enabled: null 视为已启用,因此团队清单应显式写布尔值,不能依赖空值默认。
自动同步通常只对同一 commit SHA 与参数组合尝试一次;同一组合上一次失败后不会自动再试。启用 self-heal 后,控制器可以在 self-heal timeout 后再次尝试,3.4 文档给出的默认值是 5 秒,实际值仍由 application-controller 参数决定。retry.limit 与 backoff 负责一次同步操作的失败重试;若还设置 retry.refresh: true,重试期间发现新 revision 会刷新目标。三套节奏要分别监控,不能把持续失败包装成“控制器终会自愈”。
spec:
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: true
allowEmpty: false
retry:
limit: 3
backoff:
duration: 10s
factor: 2
maxDuration: 2m
syncOptions:
- FailOnSharedResource=true正向实验先只开 self-heal:等待 Application 稳定后执行 kubectl -n gitops-lab scale deployment/demo-api --replicas=5,观察它回到 Git 声明的副本数,并核对 operation state。停止条件是目标重新 Synced、Deployment observed generation 收敛、请求仍成功,且没有持续产生新 operation;若同一字段在数轮调谐中来回变化,说明还有 HPA、另一个控制器或人工 field manager 竞争,应立即暂停 automated,而不是提高重试次数。
反例是把 self-heal 当安全防护。拥有更高权限的 actor 可以持续改写字段,控制器只会不断争抢;多来源 Application 中,即使 self-heal 关闭,另一个 source revision 的变化也可能触发同步并覆盖现场修改。紧急止血要先明确谁是临时 owner:暂停自动同步、在 Git 中提交临时状态或用受审计的 break-glass 流程,并设置恢复自动化的时间和责任人。
给 prune 和 finalizer 装上刹车
prune 处理“已跟踪资源从期望集合消失”,删除 Application 则由 finalizer 与 cascade 语义控制,两条删除链不能混为一谈。resources-finalizer.argocd.argoproj.io 会让删除 Application 时前台级联清理受管资源,resources-finalizer.argocd.argoproj.io/background 采用后台级联;argocd app delete --cascade=false 可以只删 Application 而保留工作负载。
高风险对象还要区分两组资源注解:argocd.argoproj.io/sync-options: Prune=confirm 要求普通 prune 获得删除确认,Prune=false 则把该对象排除在 prune 之外;Delete=false 或 Delete=confirm 约束的是删除整个 Application 时的资源删除。保护 prune 的注解不能替代 Application deletion 保护,反过来也一样。namespace、PVC、CRD 和共享基础设施必须为两条删除链分别指定 owner、审批入口和最终清理责任。
第一次 prune 实验只删除一个无状态 ConfigMap。执行前保存 argocd app resources demo-api,确认它确实由当前 Application 跟踪;把清单从 Git 删除后先看 diff,不立刻打开全局 prune。允许删除的停止条件应同时满足:删除集合非空且不超过审批阈值、对象不含 Namespace/PVC/CRD/共享资源、目标 revision 已评审、业务验证可继续。任一条件不满足就停止同步,恢复 Git 文件或关闭 automated.prune。
危险反例是误把空渲染当成“应用已下线”。Helm values 路径错误、Kustomize 目录重命名或生成插件失败都可能让结果为空;若同时设置 allowEmpty: true 和 prune,整组资源可能被合法地删除。allowEmpty 只适合空集合确实是业务建模结果、旧新集合 diff 已审查且删除预算通过的场景。即使 CLI 提示将删除,也不能靠操作者临场肉眼承担整组资源的安全性。
Replace=true 与 Force=true 也不是普通的 apply 性能开关。前者可能用 replace/create 更新对象,后者与 replace 组合时可通过 delete/create 重建资源;Job 重跑可能需要这种语义,StatefulSet、Service、PVC 或持久身份对象则可能因此中断甚至丢数据。PruneLast=true 只把 prune 放到隐式最后一波,PrunePropagationPolicy 只改变 Kubernetes 删除传播方式,它们都不会判断对象是否该删。生产策略应拒绝应用仓库自行放宽这些选项,例外必须带对象类型、恢复动作与破坏性实验记录。
资源跟踪默认使用 tracking annotation。多个 Argo CD 实例管理同一集群时要配置不同 installationID;启用 FailOnSharedResource=true 可在双 owner 时失败。仅 label 跟踪受 63 字符长度和其他工具改写影响,迁移跟踪方式后要重新 sync,让新标记真正写入资源,再考虑移除旧标记。
用正反实验区分 Sync、Health 与业务成功
构造一个确定失败的镜像 digest 或 readiness 路径,可以看到 Application 可能已经 Synced,Deployment 却 Progressing 或 Degraded,请求仍失败。此时 source fetch 与 apply 已经成功,问题位于 workload health 或业务层;继续点 sync 不会修复错误镜像。先读 argocd app get demo-api --show-operation,再看 Deployment condition、Pod event、容器日志和真实请求,修复应回到 Git 提交而不是直接改 live object。
反向再删除一个受管 Service,但保持 Git 不变。关闭 self-heal 时应看到 OutOfSync;打开后对象会被重建。Argo CD 3.4 的 Missing 聚合语义需要特别留意:部分资源缺失时,Application health 不一定显示 Missing,缺失主要体现在 Sync status 和资源树。依赖 health == Missing 的告警会漏报,应该联合检查 OutOfSync、资源缺失和业务探测。版本变化见 3.3 到 3.4 升级说明。
团队可把最小证据固化为发布检查:实际 revision 与审批提交一致;diff 没有共享资源和超预算删除;operation phase 成功且没有 condition;工作负载到达预期 generation;服务端点返回预期响应。健康规则对 CRD 不完整时,可在 argocd-cm 配置 Lua health,但脚本必须有 Healthy、Progressing、Degraded 的测试样例。开启 Lua 标准库会扩大能力面,应按控制面代码审查,而不是把任意脚本交给应用仓库。
设计可恢复的回滚而不是只点 History
Argo CD 的历史回滚会把某个历史部署状态重新同步,但 automated sync 开启时不能直接执行 rollback,而且 Git 中的移动分支仍可能很快把旧状态覆盖。稳定做法是 revert 产生问题的配置提交,保持镜像 digest 不变性,让回滚也经过评审和同一证据链。数据库不可逆迁移、外部消息和业务数据不会因为 Kubernetes 对象回退而自动补偿,必须由应用发布设计向前兼容和独立恢复动作。
紧急回退可以按固定顺序执行:暂停 automated;确认当前 operation 已结束而非仍在 apply;选择已知可用 revision;预览 old/new manifests 与删除集合;同步;验证工作负载和真实请求;最后把 Git 恢复为该期望状态再重新开启自动化。停止条件是旧版本无法读取新数据、回退将删除持久对象、历史渲染依赖已不可重现,或当前 operation 尚未终止。遇到这些信号时应转向前滚修复或业务补偿。
卸载同样先决定资源归属。若工作负载要留存,先停自动同步,移除 Application 资源 finalizer 或使用非级联删除,确认新 owner 已接管 tracking/field ownership,再删除 Application。随后撤销 repo credential、cluster credential、SSO 客户端、Ingress、网络策略和审计入口,最后删除 Argo CD 控制面与 CRD。直接删除 namespace 可能让 finalizer 卡住,也可能留下集群级 RBAC 和仍可使用的外部凭据。
从单实例走向 HA、容量与成本治理
生产形态通常由多租户 HA 控制面、repo-server、application-controller、argocd-server、ApplicationSet controller、Dex/SSO 与 Redis HA 组成。官方 HA 清单依赖至少三个不同节点来满足反亲和,但三个节点仍可能位于同一故障域;它提高组件可用性,不提供跨区域控制面灾备。权威对象最终在 Kubernetes/etcd,Redis 可重建,备份 Redis 不能替代备份 Application、AppProject、ConfigMap、Secret 和 RBAC。
容量模型不能只看 Pod 平均 CPU。repo-server 的压力来自仓库 clone、Helm/Kustomize/CMP 并发、内存和 /tmp;application-controller 的压力来自 Application 数、受管资源数、目标集群数、list/watch cache、diff 和 apply 队列。监控至少关联 reconcile queue、manifest generation 时延与错误、Git 请求、Kubernetes API 限流、缓存重建、工作队列积压、内存和临时磁盘。--parallelismlimit 需要按真实仓库压测,盲目加大并发可能先耗尽内存或 apiserver 配额。
成本主要落在控制面节点、跨集群网络、Git/OCI 流量、仓库渲染、日志与指标保留,以及平台值守。团队应给 Application 数、单应用资源数、单仓库渲染时间、单集群 QPS 和告警基数建立基线;增长后先识别 repo 瓶颈还是 controller 瓶颈,再决定拆仓、缓存、限并发或 sharding。Argo CD 3.4 的新 sharding 算法与动态分配仍属 Alpha,不能只因为“集群多”就把实验能力作为生产承诺。
把权限、凭证和审计变成日常发布制度
权限应分成四层:身份系统决定谁能登录;Argo CD RBAC 决定谁能查看、同步或管理 Application;AppProject 限定 source、destination 与资源种类;目标 Kubernetes RBAC 决定控制器实际能够写入哪些资源。任何一层过宽都可能击穿上层设计。应用团队通常只需要在受限项目内查看和同步自己的 Application,不需要管理 repository/cluster Secret、修改 AppProject 或创建集群级资源。
仓库凭证与集群凭证是两类高敏资产。优先使用短期身份、GitHub App/工作负载身份或外部 Secret 注入,按仓库与项目分域,设置轮换、撤销和离职回收。日志、diff、渲染输出和支持包也可能暴露 Secret 参数或内网地址;平台 owner 要定义脱敏、保留期限、访问审计和事故销毁方式。共享 admin 账号、把 token 写进 Application、把 cluster Secret 提交 Git,都是应被策略门禁直接拒绝的反例。
日常变更至少留下 Git PR、实际 revision、操作者、sync operation、删除确认、业务验证和回滚提交。break-glass 操作要有时限,结束后撤销临时角色并把 live 修复回写 Git,否则 self-heal 恢复后事故会重演。Application、AppProject、repo/cluster Secret、SSO/RBAC 和通知配置都要有明确 owner;无 owner 的控制对象不应继续自动调谐生产。
用升级与退出演练检验架构选择
升级不是只改 controller 镜像。完整 manifest 还会修改 CRD、RBAC、参数、内置 Helm/Kustomize、Dex 和遥测依赖;CRD 与 controller 二进制应作为同一事务处理。升级前固定现有 manifest、values 和镜像 digest,执行控制面导出,检查导出对象计数与关键 Secret/ConfigMap 是否存在,再在隔离环境回放真实 Application。argocd admin export 指错 namespace 也可能返回成功,因此“命令退出码为零”不是备份可恢复的证据。
每次跨 minor 都应逐跳阅读 升级说明,回归 repo render diff、sync/prune/self-heal、custom health、SSO、目标集群认证、指标告警和恢复导入。若新版本改变 CRD schema、health 聚合、生成工具输出或缓存编码,简单把 Deployment 镜像改回旧版可能无法恢复;应预先决定前滚修复、控制面数据恢复和停止自动同步的窗口。
选型也在退出演练中显现。单集群、少量应用且只有一个管理员时,Core 或更轻量的控制器可能更合适;多租户、需要 UI/API、项目授权和大量 Application 时,多租户 HA 才值得承担成本。只需要流水线一次性 apply、无法接受常驻高权控制器,或期望控制器自动解决数据库与业务回滚时,Argo CD 都不是答案。真正可长期维护的终点,是团队能暂停调谐、导出权威对象、保留或删除工作负载、撤销全部凭证、归还字段所有权,并证明没有孤儿资源和继续计费的基础设施。
