十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Argo CD 调谐(Reconcile)优化实战:从高频资源更新到 CPU 尖峰的完整治理方案

Argo CD 调谐(Reconcile)优化实战:从高频资源更新到 CPU 尖峰的完整治理方案 Argo CD 调谐Reconcile优化实战从高频资源更新到 CPU 尖峰的完整治理方案【免费下载链接】argo-cdDeclarative Continuous Deployment for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/ar/argo-cd导读Argo CD 默认会在其管理的每一个资源发生变化时触发对应 Application 的重新调谐Refresh/Reconcile而 Kubernetes 控制器如 Deployment、CronJob 的父控制器往往会周期性更新它们 watch 的资源导致argocd-application-controller陷入无休止的对比计算、CPU 占用持续居高不下。本文基于 Argo CD 官方操作手册中的 Reconcile Optimization 章节结合本仓库的控制器与缓存源码系统讲解如何通过argocd-cmConfigMap 的ignoreResourceUpdates机制忽略无关字段更新、识别高变更频率high-churn资源、以及为未跟踪资源启用忽略策略最终让 Application 只在真正有意义的变更发生时才会被重新调谐。背景为什么 Application 会被“无意义”地反复调谐默认情况下Argo CD 对资源的跟踪粒度是“资源级”的只要某个属于 Application 的资源对象发生任何变化事件处理器就会为该 Application 排队一次刷新。正如 controller/appcontroller.go 中的事件回调所示控制器会把这次变更以结构化日志Requesting app refresh caused by object update记录下来并携带api-version、kind、namespace、name、comparison-level等字段。问题在于 Kubernetes 生态中存在大量“高噪声”更新Deployment 的status字段会随滚动更新、副本数伸缩频繁变化各类 Operator如 External Secrets Operator会周期性刷新status中的时间戳字段ReplicaSet 的status几乎每时每刻都在变动。这些更新对 GitOps 而言往往毫无信息量目标状态desired state没有变化健康状态也没有变化却白白触发了一次完整的清单对比diff。当集群规模较大、Application 数量较多时argocd-application-controller的 CPU 就会持续走高。为此Argo CD 提供了一套“资源更新忽略Ignore Resource Updates”机制当一次资源更新的内容全部落在被忽略的字段上、且该资源的健康状态health没有变化时所属 Application 将不会被重新调谐。底层原理manifestHash Health 的双重判断要正确配置和验证忽略规则首先需要理解 Argo CD 是如何判定“这一次更新是否值得调谐”的。核心逻辑位于 controller/cache/cache.go 的skipResourceUpdate函数func skipResourceUpdate(oldInfo, newInfo *ResourceInfo) bool { if oldInfo nil || newInfo nil { return false } isSameHealthStatus : (oldInfo.Health nil newInfo.Health nil) || oldInfo.Health ! nil newInfo.Health ! nil oldInfo.Health.Status newInfo.Health.Status isSameManifest : oldInfo.manifestHash ! newInfo.manifestHash ! oldInfo.manifestHash newInfo.manifestHash return isSameHealthStatus isSameManifest }可以看出只有同时满足以下两个条件这次资源更新才会被跳过即不触发 Application 调谐健康状态未改变更新前后资源的 health status 相同或均为空规范化后的 manifest 哈希未改变将资源按照忽略规则jsonPointers / jqPathExpressions / managedFieldsManagers规范化normalize后计算哈希哈希相同说明“有意义的字段”没有任何变化。哈希计算由 controller/cache/info.go 的generateManifestHash完成它基于normalizers.NewIgnoreNormalizer构建规范化器先按忽略规则剔除字段再序列化为 JSON 并计算 xxhash。也就是说你的忽略规则最终会以“规范化器”的形式直接影响哈希结果——这就是为什么配置了 ignoreResourceUpdates 后被忽略字段的变动不会改变哈希、从而不会触发调谐。缓存层的事件处理入口在 controller/cache/cache.go当ignoreResourceUpdatesEnabled为 true 且skipResourceUpdate返回 true 时事件被直接丢弃并记录 debug 日志Ignoring change of object because none of the watched resource fields have changed随后不再向 Application 队列投递刷新请求。系统级开关resource.ignoreResourceUpdatesEnabled默认行为与关闭方式该特性默认是开启的。从 util/settings/settings.go 的GetIsIgnoreResourceUpdatesEnabled实现可以看到当argocd-cm中未配置resource.ignoreResourceUpdatesEnabled时返回true只有显式配置才会被解析为布尔值。如果需要关闭整个机制例如排查问题时希望恢复“任何更新都触发调谐”的旧行为在argocd-cmConfigMap 中显式置为false即可apiVersion: v1 kind: ConfigMap metadata: name: argocd-cm namespace: argocd data: resource.ignoreResourceUpdatesEnabled: false启用后的必要前提需要注意的是忽略判断依赖资源的 manifest 哈希而哈希并非对每个资源都会生成。在 controller/cache/cache.go 的shouldHashManifest中只有满足以下条件之一的资源才会生成哈希资源属于某个 Application被资源跟踪标记即 tracked resource资源本身是Application类型用于 Application of Applications 场景资源带有argocd.argoproj.io/ignore-resource-updates: true注解这正是后文“未跟踪资源”方案能在源码层生效的原因。因此对于普通被跟踪资源只要开启了ignoreResourceUpdatesEnabled机制即自动生效对于依赖资源dependent resources如 Deployment 创建的 ReplicaSet 与 Pod必须显式添加注解才会参与哈希计算。按 Group/Kind 精确配置忽略字段Argo CD 允许针对指定的 group 与 kind使用两类路径表达式精确描述“哪些字段的更新应当被忽略”RFC 6902 JSON Patch 指针jsonPointers如/status/refreshTimeJQ 路径表达式jqPathExpressions如.status.refreshTime。两者等价可二选一或混用。配置位置是argocd-cm的resource.customizations.ignoreResourceUpdates.group_Kind键。以下示例忽略 ExternalSecret 资源的status.refreshTime字段——External Secrets Operator 会定期刷新该时间戳但刷新本身不代表任何实际状态变化data: resource.customizations.ignoreResourceUpdates.external-secrets.io_ExternalSecret: | jsonPointers: - /status/refreshTime # JQ equivalent of the above: # jqPathExpressions: # - .status.refreshTime这些配置在运行时会被 util/settings/settings.go 的GetIgnoreResourceUpdatesOverrides汇总为ResourceOverride映射随后注入到缓存层的resourceOverrides最终由generateManifestHash中的规范化器消费。也就是说每条 ignoreResourceUpdates 规则都会直接影响该 kind 资源的哈希计算。全局应用对所有资源生效如果希望忽略规则作用于该 Argo CD 实例所管理的一切 Application 的所有被跟踪资源可以使用all这个特殊组名data: resource.customizations.ignoreResourceUpdates.all: | jsonPointers: - /status上面的示例会让所有资源的整个/status字段都不再触发调谐。这适用于那些你明确知道“状态字段无论如何都不值得重新对比”的场景——例如只关注 spec 的纯配置类资源。⚠️ 谨慎使用全局忽略/status意味着只要健康状态不变任何 status 变化都不再触发调谐。请结合下文的日志排查手段确认这不是你想要监控的信号。复用系统级 ignoreDifferencesignoreDifferencesOnResourceUpdates如果你已经在argocd-cm中通过resource.customizations.group_Kind.ignoreDifferences配置过 diff 忽略例如忽略镜像的imagePullPolicy、忽略lastTransitionTime等那么这些规则默认会自动复用到资源更新忽略中无需复制一份配置。从 util/settings/settings.go 的源码可以看出当 compare options 中的ignoreDifferencesOnResourceUpdates为 true 时每个资源覆盖项的IgnoreDifferences.JQPathExpressions、JSONPointers、ManagedFieldsManagers会被逐一并入IgnoreResourceUpdates。这个默认行为可以通过以下配置关闭apiVersion: v1 kind: ConfigMap metadata: name: argocd-cm data: resource.compareoptions: | ignoreDifferencesOnResourceUpdates: false关闭后ignoreDifferences只影响 diff 展示与同步结果不再参与“是否触发调谐”的判断。该开关对应的配置结构定义在 util/settings/settings.go 的CompareOption中字段名IgnoreDifferencesOnResourceUpdates。默认忽略的元数据字段无论是否配置任何自定义规则以下三个元数据字段始终被全局忽略不参与调谐判断/metadata/generation/metadata/resourceVersion/metadata/managedFields这一默认行为同样由 util/settings/settings.go 保证GetIgnoreResourceUpdatesOverrides在返回结果前会为*/*追加这三条IgnoreDiffItemOverride。其中resourceVersion几乎每次写操作都会变化不忽略它将导致任何字段更新都触发调谐generation会随 spec 变更递增但本身不是变更的内容managedFields由服务端字段管理机制频繁改写与业务状态无关。这解释了为什么“只看一眼 YAML diff 感觉没什么变化Argo CD 却一直在调谐”时多半还有其他字段如 status 下的时间戳、条件数组在变动。定位需要忽略的资源从日志到字段级 diff第一步统计高变更频率的资源类型argocd-application-controller会在资源变更触发刷新时输出日志。在日志中搜索Requesting app refresh caused by object update日志条目携带结构化字段api-version与kind以及namespace、name、comparison-level、server等见 controller/appcontroller.go。按api-version/kind分组统计刷新次数即可快速找出高变更频率high-churn的资源类型。 注意日志级别对于 GitOps 托管的资源managed resources该日志在info级别输出对于孤儿资源与依赖资源orphan / dependent resources则在debug级别输出。将 application-controller 的日志级别调至debug可获得最完整的信息并通过comparison-level字段判断这次资源更新的重要程度CompareWithRecent表示真正触发了一次实际对比。第二步用 diff 找出具体变化的字段确定高变更资源后用“前后快照对比”的方法观察具体是哪些字段在变kubectl get resource -o yaml /tmp/before.yaml # Wait a minute or two. kubectl get resource -o yaml /tmp/after.yaml diff /tmp/before.yaml /tmp/after.yamldiff 输出会直观地告诉你哪些字段频繁变动典型的如/status下的时间戳、条件数组、observedGeneration等随后便可将这些字段加入ignoreResourceUpdates规则。验证忽略规则是否生效每当 Argo CD 因为“被忽略的资源更新”而跳过一次刷新时控制器会输出如下 debug 日志由 controller/cache/cache.go 发出Ignoring change of object because none of the watched resource fields have changed在 application-controller 日志中搜索该行即可确认规则已被应用。该日志同样位于debug级别因此需要将控制器日志级别配置为debug。同时日志中的namespace、name、api-version、kind、server字段可以帮助你确认“正在被忽略的是哪个资源”。实战示例忽略 argoproj.io/Application 的噪声更新在多级 GitOpsApplication of Applications或与 ApplicationSet 配合的场景下Application 资源自身也可能成为高频更新的来源。例如父 ApplicationSet 频繁变更ownerReferences、reconciledAt反复被刷新、conditions中的lastTransitionTime被反复重写——这些都不代表实质变化。完整的示例配置如下apiVersion: v1 kind: ConfigMap metadata: name: argocd-cm data: resource.customizations.ignoreResourceUpdates.argoproj.io_Application: | jsonPointers: # Ignore when ownerReferences change, for example when a parent ApplicationSet changes often. - /metadata/ownerReferences # Ignore reconciledAt, since by itself it doesnt indicate any important change. - /status/reconciledAt jqPathExpressions: # Ignore lastTransitionTime for conditions; helpful when SharedResourceWarnings are being regularly updated but not # actually changing in content. - .status?.conditions[]?.lastTransitionTime注意这里同时展示了 jsonPointers 与 jqPathExpressions 的混合用法JQ 表达式支持条件子句与数组遍历?空值安全操作符、[]遍历表达能力更强适合处理conditions这类数组嵌套结构。未跟踪资源Untracked Resources的忽略方案ignoreResourceUpdates配置只作用于被跟踪资源tracked resources。依赖资源dependent resources——例如由 Deployment 创建的 ReplicaSet 与 Pod——不会自动参与忽略判断它们的任何变更依然会触发所属 Application 的调谐。如果你希望对某个未跟踪资源应用忽略规则需要在其 manifest 上显式添加注解argocd.argoproj.io/ignore-resource-updates: true从源码看该注解正是 controller/cache/cache.go 中定义的常量AnnotationIgnoreResourceUpdates而shouldHashManifestcontroller/cache/cache.go会为携带该注解且值为true的未跟踪资源生成 manifest 哈希使其进入skipResourceUpdate的判断流程。注解值会按布尔解析解析失败时按false处理即不启用忽略。完整示例CronJob 及其生成的 Job/PodCronJob 是典型的高噪声依赖链CronJob 按分钟创建 JobJob 再创建 Podstatus字段频繁变化。下面的示例为 CronJob 的 Job 模板与 Pod 模板同时加上注解并结合argocd-cm中的 ignoreResourceUpdates 规则忽略batch/Job与Pod的整个/status字段apiVersion: batch/v1 kind: CronJob metadata: name: hello namespace: test-cronjob spec: schedule: * * * * * jobTemplate: metadata: annotations: argocd.argoproj.io/ignore-resource-updates: true spec: template: metadata: annotations: argocd.argoproj.io/ignore-resource-updates: true spec: containers: - name: hello image: busybox:1.28 imagePullPolicy: IfNotPresent command: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure对应的argocd-cm配置resource.customizations.ignoreResourceUpdates.batch_Job: | jsonPointers: - /status resource.customizations.ignoreResourceUpdates.Pod: | jsonPointers: - /status 提醒忽略/status只影响“是否触发调谐”不会影响 diff 展示。如果该 Job 的status变化如完成状态是你需要监控的健康信号请改为更细粒度的字段而不是整段忽略。补充提示已被跳过的高频资源类型除上述可配置的忽略机制外源码中还内置了一张“直接跳过重新排队”的资源清单见 controller/cache/cache.go 的ignoredRefreshResources目前包含Endpoints。Endpoints 由 EndpointSlice 控制器高频重写且其变化通常与 Application 的 GitOps 状态无关因此 Argo CD 对它的更新直接不做 Application 重新排队处理。这提醒我们对于集群内置的极高频资源直接跳过往往是更合理的默认策略。排查与运维建议小结先观察再配置将 application-controller 日志级别调至debug用Requesting app refresh caused by object update定位 high-churn 资源用Ignoring change of object because none of the watched resource fields have changed验证规则生效优先复用 ignoreDifferences保持ignoreDifferencesOnResourceUpdates为默认开启避免双份维护确需分离语义时再关闭字段粒度宁细勿粗尽量忽略具体的时间戳、条件字段而非整段/status避免掩盖真正重要的状态变化未跟踪资源显式加注解对依赖资源Job、Pod 等在模板层加argocd.argoproj.io/ignore-resource-updates: true并结合resource.customizations规则才能生效健康状态是最后防线即使所有字段都被忽略只要资源健康状态发生变化Application 依然会被调谐见skipResourceUpdate的isSameHealthStatus判断因此健康检查health assessment始终保有最终兜底能力。通过上述系统级开关、按 kind 的字段级规则、ignoreDifferences 复用以及未跟踪资源注解四层手段可以将argocd-application-controller从“无意义高频调谐”中解放出来显著降低 CPU 消耗同时保证真正影响应用状态的变化永远不会被漏掉。【免费下载链接】argo-cdDeclarative Continuous Deployment for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/ar/argo-cd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表