Argo CD 从 label 换成 annotation 跟踪资源:复制资源被 prune、63 字节上限,以及切换后要显式同步
目录
一个跑 dev、QA、STG 的非生产集群把 Argo CD 从 2.12 升到了 3.6.1。Argo CD 3.0.0(2025-05-06 发布)把 resource tracking 的默认方式从 label 换成了 annotation,为了让升级这一步只换版本,升级前先在 argocd-cm 里把 application.resourceTrackingMethod 固定成 label。升级后核对资源时,monitoring 命名空间里有两个 StatefulSet 带着 argocd.argoproj.io/instance: kube-prometheus-stack,可 chart 渲染出来的清单里没有它们,它们是 Prometheus Operator 根据 Prometheus 和 Alertmanager 资源建出来的。
Argo CD 把一个资源算进哪个 Application,决定了这个资源会不会被比对、会不会被 prune。下面在一个 kind 集群里对比两种跟踪方式:Argo CD v3.6.1(2026-11-04 发布),用 argo-cd Helm chart 10.17.1 安装,示例应用是 argocd-example-apps 的 guestbook,固定在提交 8088f4c,同步策略是 automated 加 prune 和 selfHeal。
Argo CD 靠什么认定一个资源属于哪个 Application#
Resource Tracking 文档列了三种方式,由 argocd-cm 里的 application.resourceTrackingMethod 选择:
label:在每个由它同步的资源上写一个 label,值是 Application 的名字。upstream 默认的 key 是app.kubernetes.io/instance,argo-cd Helm chart 在 values.yaml 第 202 行 把它改成了argocd.argoproj.io/instance,开头那个集群和下面的实验用的都是这个 key。annotation:写一个argocd.argoproj.io/tracking-idannotation,3.0 起是默认值。annotation+label:用 annotation 跟踪,同时写一个只给其它工具参考的 label,按文档的说法这个 label 超过 63 个字符会被截断。
2.14 到 3.0 的升级指南解释了改默认值的原因:annotation「is more reliable and less prone to errors caused by external code copying tracking labels from one resource to another」。开头那两个 StatefulSet 正是被外部代码复制了 tracking label 的资源。
label 方式下,带着同名 label 的副本会被 prune#
实验集群先用 label 方式安装,Helm values 里只改了这一项:
configs:
cm:
application.resourceTrackingMethod: label
guestbook 同步完以后,guestbook-ui 这个 Service 和 Deployment 上都有 argocd.argoproj.io/instance=guestbook。接着模拟一个复制资源的工具:把 Service 导出、改个名字再建回去,label 原样带过去。
kubectl -n guestbook get svc guestbook-ui -o json \
| jq 'del(.metadata.uid,.metadata.resourceVersion,.metadata.creationTimestamp,.metadata.managedFields,.metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"],.spec.clusterIP,.spec.clusterIPs,.status) | .metadata.name="guestbook-ui-copy"' \
| kubectl create -f -
副本在建出来的同一秒被删掉。application controller 的日志(只保留时间、消息和相关字段):
07:31:18 Requesting app refresh caused by object update application=guestbook kind=Service name=guestbook-ui-copy
07:31:18 Tasks (dry-run) application=guestbook tasks="[Sync/0 resource /Service:guestbook/guestbook-ui-copy obj->nil (,,)]"
07:31:18 Adding resource result, status: 'Pruned', phase: 'Succeeded', message: 'pruned' application=guestbook kind=Service name=guestbook-ui-copy
同一秒里 Application 上的事件:
07:31:18 OperationStarted Initiated automated sync to '8088f4c0d970abb09e250248cc97e35623447cb5'
07:31:18 ResourceUpdated Updated sync status: Synced -> OutOfSync
07:31:18 OperationCompleted Partial sync operation to 8088f4c0d970abb09e250248cc97e35623447cb5 succeeded
07:31:18 ResourceUpdated Updated sync status: OutOfSync -> Synced
label 方式下 Argo CD 只看 label 的值。副本的 label 写着 guestbook,它就被算进 guestbook;Git 里没有这个对象,它就成了多余资源,应用变成 OutOfSync;prune: true 的自动同步随即把它删掉,应用又回到 Synced。事后翻 UI,只能看到一次「Partial sync operation」。
同样复制了 label、但带 ownerReference 的对象不会被这样处理。我又建了一个 ConfigMap,label 写 guestbook,ownerReference 指向 guestbook-ui 这个 Deployment。20 秒后它还在,应用保持 Synced,顶层资源列表里只有 Service 和 Deployment,controller 日志里也没有提到它:有 owner 的对象被当作 owner 的子资源,不参与 prune 判断。
开头那两个 StatefulSet 属于这种情况。Prometheus Operator v0.95.1(2026-11-06 发布)在 statefulset.go 第 85–97 行用 operator.WithLabels(objMeta.GetLabels()) 把 Prometheus 资源上的 label 全部复制到 StatefulSet,同一组调用里的 operator.WithManagingOwner(p) 设好了 ownerReference,所以它们只是在 UI 里挂在 kube-prometheus-stack 名下,没有被删。风险在没有 ownerReference 的副本上。文档举的例子是在命名空间之间复制资源的 HNC,还提到「several Helm charts and operators also use this label for generated manifests」;手工导出、改名、再 apply 一份,也是同样的结果。
label 方式下,应用名超过 63 字节就同步不了#
label 的值最长 63 字节。我建了两个 66 字节的应用名,前 64 个字节相同,只差结尾的 -a 和 -b,本来想看截断以后两个应用会不会互相认领资源。v3.6.1 的 label 方式下没有截断,同步直接失败:
one or more objects failed to apply, reason: Deployment.apps "guestbook-ui" is invalid: metadata.labels: Invalid value: "guestbook-payments-reconciliation-worker-with-a-long-team-prefix-a": must be no more than 63 bytes
两个应用都停在 OutOfSync / Missing,目标命名空间里一个对象也没建出来。文档里的「truncated」对应的是 annotation+label 方式下那个只供参考的 label。用 ApplicationSet 按「服务名-环境」拼应用名时,服务名一长就会碰到这个上限。
annotation 方式下,tracking-id 记的是资源自己的身份#
tracking-id 的格式在升级指南里:
argocd.argoproj.io/tracking-id: <app name>:<resource group>/<resource kind>:<resource namespace>/<resource name>
切到 annotation 并同步之后,guestbook 的两个资源上是这样的,core group 为空,所以 Service 的值里 / 前面没有内容:
Service guestbook:/Service:guestbook/guestbook-ui
Deployment guestbook:apps/Deployment:guestbook/guestbook-ui
再用上一节的命令复制一个 Service,这次复制的是长名字应用 lab-a 命名空间里那个已经带 annotation 的 guestbook-ui。副本的 annotation 里写的还是原来那个 Service 的名字:
{"name":"guestbook-ui-copy","tracking":"guestbook-payments-reconciliation-worker-with-a-long-team-prefix-a:/Service:lab-a/guestbook-ui"}
40 秒后副本还在,应用保持 Synced,没有触发同步。资源列表里多了这个副本,它没有同步状态:
[('Service', 'guestbook-ui', 'Synced'), ('Service', 'guestbook-ui-copy', None), ('Deployment', 'guestbook-ui', 'Synced')]
文档把这种情况叫「Non self-referencing annotations」:annotation 里的 name、namespace、group、kind 和资源本身对不上时,这个资源「will neither affect the application’s sync status nor be marked for pruning」。上一节那两个长名字的应用也是在这一步好的:改完设置 11 秒后两个都同步成功,annotation 里是完整的 66 字节应用名。
同一个集群里跑多个 Argo CD 实例时,annotation 方式还可以在 argocd-cm 里配 installationID,每个资源会多一个 argocd.argoproj.io/installation-id,不同实例里同名的 Application 不会互相认领资源。
从 label 切到 annotation:每个应用都要显式同步一次#
切换只改 argocd-cm 里的一个 key,Argo CD 不用重启:
data:
application.resourceTrackingMethod: annotation
升级指南的说法是「the Argo CD tracking annotation will only appear on an Application’s resources when either a new Git commit is made or the Application is explicitly synced」。实验里分三种情况看了一遍:
- 已经 Synced 的
guestbook:改设置后过了一个完整的 reconcile 周期(timeout.reconciliation120 秒,加 60 秒 jitter),应用还是 Synced,资源上仍然只有 label。自动同步只在应用 OutOfSync 时触发,这里没有触发。 - 来了新提交、但这个应用的清单没变:
rev-demo先固定在e5f4ed8,切换后把targetRevision改成8088f4c,这两次提交之间guestbook/目录没有改动。应用在新 revision 上直接是 Synced,最后一次同步操作停在e5f4ed8,label 也还在。很多服务共用一个清单仓库时,大部分应用在大部分提交里都是这种情况。 - 显式同步:
argocd app sync guestbook之后 label 被去掉,换成了上一节看到的 annotation。
只带 label 的资源,在下一次需要删除东西时会出问题,升级指南把它列成了「Potential for orphaned resources」。edge-demo 在 label 方式下部署了 guestbook,切到 annotation 后把它的 path 改成 kustomize-guestbook(资源名带 kustomize- 前缀),切换后的第一次同步本该删掉旧的 guestbook-ui,结果是:
KIND NAME LABEL TRACKING
Service guestbook-ui edge-demo <none>
Service kustomize-guestbook-ui <none> edge-demo:/Service:edge/kustomize-guestbook-ui
Deployment guestbook-ui edge-demo <none>
Deployment kustomize-guestbook-ui <none> edge-demo:apps/Deployment:edge/kustomize-guestbook-ui
新资源建出来了,旧的 Service 和 Deployment 还在跑,也不在应用的资源列表里。Argo CD 已经不看 label,又找不到 annotation,认不出它们是自己的。label 方式下同样的改动会把它们 prune 掉,第一节的实验已经看到了。
所以切换要按这个顺序来:
-
确认没有东西靠 tracking label 选资源。Service、Deployment、NetworkPolicy、PDB 的 selector 可以这样查,实验集群输出为空;监控规则和脚本里有没有按这个 label 过滤,要另外 grep。有依赖的话用
annotation+label。kubectl get svc,deploy,sts,ds,networkpolicy,pdb -A -o json \ | jq -r '.items[] | select(((.spec.selector // {}) | tostring | test("argocd.argoproj.io/instance")) or ((.spec.podSelector // {}) | tostring | test("argocd.argoproj.io/instance"))) | "\(.kind) \(.metadata.namespace)/\(.metadata.name)"' -
找出带
ApplyOutOfSyncOnly=true的应用,升级指南里有现成的jq命令。按指南的说法,这类应用切换后要在 UI 里取消这个选项再同步一次,CLI 做不到。 -
改设置,然后马上对所有应用显式同步一次。在这之前,不要合并任何会删除资源或删除应用的改动。实验集群里用的是
--core模式,两条命令都多带一个--core,而且 kubeconfig 当前 context 的 namespace 要设成argocd,否则会报configmap "argocd-cm" not found:argocd app list -o name | xargs -n1 argocd app sync -
确认没有资源只带 label、没有 annotation。资源类型按集群实际用到的补全:
kubectl get deploy,svc -A -l argocd.argoproj.io/instance -o json \ | jq -r '.items[] | select(.metadata.annotations["argocd.argoproj.io/tracking-id"] == null) | "\(.kind) \(.metadata.namespace)/\(.metadata.name)"'
实验集群在批量同步之后跑第 4 步,只剩下 edge-demo 留下的两个孤儿,这两个只能手工删除:
Deployment edge/guestbook-ui
Service edge/guestbook-ui
小结#
label 方式只看 label 的值,带着同名 label、又没有 ownerReference 的副本会被当成多余资源,开着 prune 的应用在一秒内就会删掉它;Prometheus Operator 复制了 label 却没出事,是因为它同时设了 ownerReference。应用名超过 63 字节时,label 方式在 v3.6.1 下直接同步失败。annotation 方式在 tracking-id 里记下资源自己的 group、kind、namespace 和 name,复制出来的对象对不上就被忽略,名字长度也不再受 label 的限制。从 label 切过去时,已经 Synced 的应用不会自己补上 annotation,和它无关的新提交也不会,要对每个应用显式同步一次,并在这之前避免删除资源的变更,否则旧资源会变成没人管的孤儿。
参考资料#
- Argo CD: Resource Tracking(v3.6.1) — 三种方式、label 方式的限制、Non self-referencing annotations、installationID
- Argo CD: 2.14 to 3.0 升级指南 — 默认值改为 annotation、tracking-id 格式、ApplyOutOfSyncOnly 和孤儿资源的警告
- Argo CD v3.0.0、v3.6.1 release
- argo-helm argo-cd chart 10.17.1 values.yaml — chart 默认的
application.instanceLabelKey - prometheus-operator v0.95.1 statefulset.go — 把
Prometheus资源的 label 复制到 StatefulSet,并设置 ownerReference - argoproj/argocd-example-apps @ 8088f4c — 实验用的
guestbook和kustomize-guestbook - kubernetes-sigs/hierarchical-namespaces — 文档里提到的会复制资源的工具