一、理解ArgoCD接管集群的“暴力”本质

很多团队在用过Helm或者直接kubectl apply部署应用后,想着把现有Kubernetes集群交给ArgoCD统一管理。这个想法很美好,但实际操作中第一眼就容易翻车——ArgoCD默认认为自己是资源的“唯一主人”,一旦它发现集群里的资源状态跟它期待的Git仓库里描述的不一样,就会立刻按照Git里的配置进行“纠正”。这种纠正可能包括重建Pod、删除旧资源、甚至替换掉你本不想动的东西,导致线上业务直接断连。

举个例子,假设你有一个叫nginx的Deployment,已经在集群里跑了一年。你把它写进Git仓库里的ArgoCD Application配置里,然后执行argocd app sync,结果ArgoCD发现Pod的标签跟你Git里定义的不完全一致(比如多了个version: v1),它二话不说就把旧Pod全删了,重新创建新Pod。这个过程里客户端连接全部丢失,业务中断几十秒。这就是很多新手踩的第一个坑。

要避免这种情况,首先得搞明白ArgoCD的“同步”到底在干什么。它本质上是把Git仓库里的期望状态和集群里的实际状态做对比,然后通过kubectl apply --server-side或者客户端apply来保证一致性。如果启用了自动同步,它会每隔几分钟自动跑一次对比。特别关键的是“修剪(Prune)”机制:当集群里有多余的资源(不在Git里定义),ArgoCD会默认把它们删掉。这对于净置新集群没问题,但对于已有资源的集群就像“清场”一样危险。

二、武装你的现有集群:三大核心防御手段

2.1 关闭自动修剪(Prune)

最直接的办法就是在Application配置里把automated.prune设为false。这样ArgoCD只会新增或更新资源,绝对不会删除任何已在集群中存在的东西。同时建议把automated.selfHeal也设为false,这样它不会主动修复你手动改过的配置,给你留出缓冲时间。

以下是一个完整的Shell示例(技术栈:Shell),展示了如何创建一个安全的Application YAML并通过kubectl apply提交到ArgoCD:

# 技术栈:Shell
# 创建一个ArgoCD Application,关闭自动修剪和自我修复
cat <<'EOF' | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-existing-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/my-org/my-repo.git'
    targetRevision: HEAD
    path: ./k8s-manifests
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: production
  syncPolicy:
    automated:
      prune: false          # 核心:禁止删除
      selfHeal: false       # 核心:禁止自动修复
    syncOptions:
      - CreateNamespace=true # 如果namespace不存在则创建
EOF

这段配置提交后,ArgoCD会监控Git仓库里的文件,但只会往集群里“添加”或“更新”资源,而不会删除任何现有的Pod、Service、Deployment等。注意这里还加上了syncOptions.CreateNamespace=true,避免因为namespace不存在而报错。

2.2 使用资源注解进行局部保护

有时候你并不想全局关闭修剪,而是希望某些关键资源(比如数据库StatefulSet)不受ArgoCD控制。这时候可以在这些资源上添加一个特殊注解:argocd.argoproj.io/sync-options: Prune=false。ArgoCD在修剪之前会检查每个资源是否有这个注解,如果有,就跳过它。

这个注解可以加在资源本身的YAML里,也可以加在ArgoCD Application的ignoreDifferences里。但是最稳妥的方式是直接改动集群里的资源,因为ArgoCD默认不会去修改资源的注解(除非你的Git里定义了注解)。

下面是用kubectl annotate命令给一个已有的StatefulSet加上保护注解的例子(技术栈:Shell):

# 技术栈:Shell
# 给已有的StatefulSet加上禁止修剪的注解
kubectl annotate statefulset my-database \
  argocd.argoproj.io/sync-options=Prune=false \
  --overwrite -n production

# 验证注解是否生效
kubectl get statefulset my-database -n production -o yaml | grep -A2 'sync-options'

注意:这个注解只影响修剪行为,不影响更新。如果你Git里的定义和现有资源有差异,ArgoCD仍然会更新它。如果想要彻底禁止ArgoCD对该资源做任何修改,可以考虑使用managed-by标签(但需要配合Project策略,比较复杂),或者把资源移出Application的spec.source.path范围。

2.3 利用忽略差异(IgnoreDifferences)

很多业务中断其实不是被删除,而是被“更新”导致的。比如你有一个Deployment,里面有一个容器镜像的标签是:latest,但实际运行的时候容器运行时可能会自动追加一些环境变量(比如KUBERNETES_SERVICE_HOST),这些是Kubernetes自动注入的,Git里根本没有定义。ArgoCD检测到差异后就会尝试更新Pod,触发滚动更新,造成短暂波动。

解决方案是用ignoreDifferences告诉ArgoCD忽略某些字段。常用的是忽略Deployment的spec.template.spec.containers[*].env中的某些注入项,或者忽略所有由kube-controller-manager自动添加的字段。

下面是一个完整的Application配置示例,展示了如何忽略指定字段(技术栈:Shell):

# 技术栈:Shell
# 创建Application时配置忽略差异
cat <<'EOF' | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-with-ignore
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/my-org/my-repo.git'
    targetRevision: HEAD
    path: ./app
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: app
  syncPolicy:
    automated:
      prune: false
      selfHeal: false
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/template/spec/containers/0/env   # 忽略第一个容器的所有环境变量
        - /spec/template/metadata/annotations    # 忽略Pod模板上的注解
    - group: ""
      kind: Service
      jsonPointers:
        - /spec/clusterIP                        # 忽略clusterIP变化
EOF

注意:jsonPointers是JSON路径,可以指定到具体字段。当然,你也可以用managedFields来忽略所有由Kubernetes自动管理的字段,但那样可能过于粗放。

三、实际迁移步骤:从“只读观察”到“全权接管”

3.1 第一阶段:只读模式(Read-Only)

先把Application配好,但把syncPolicy.automated整个删掉,或者只保留prune: falseselfHeal: false。然后手动执行一次argocd app sync --dry-run,看看ArgoCD报告了哪些差异。这能让你在完全不碰集群的情况下,了解ArgoCD打算做什么。

3.2 第二阶段:人工比对并打补丁

根据干跑的结果,把Git仓库里的YAML文件改成跟集群实际状态一致。比如集群里有个Service的type: NodePort,而Git里写的是ClusterIP,那就改Git。特别要注意metadata.labelsmetadata.annotations这些容易被忽略的字段。同时,对于关键资源,加上前面说的Prune=false注解。

3.3 第三阶段:开启自动同步但保留安全网

当确认所有差异都是预期内的之后,可以打开automated.prune: falseautomated.selfHeal: false,但保留自动同步(只同步automated: {})。这样ArgoCD会定期把Git里的变更应用到集群,但不会修复意外改动,也不会删东西。运行一周观察无误,再谨慎地开启selfHeal: true,最后再考虑prune: true(前提是你已经清理干净了集群里所有多余的资源)。

3.4 第四阶段:使用Resource Exclusions做最后防线

如果某个命名空间里的资源你完全不想让ArgoCD碰,可以在ArgoCD的argocd-cm ConfigMap里配置resource.exclusions。比如排除整个kube-system命名空间,或者排除所有helm.sh/release-name注解的资源。

下面是一个ConfigMap补丁示例(技术栈:Shell):

# 技术栈:Shell
# 给argocd-cm添加一个排除规则,禁止ArgoCD管理kube-system命名空间里的任何资源
kubectl edit configmap argocd-cm -n argocd

# 在data下新增字段
# data:
#   resource.exclusions: |
#     - apiGroups:
#       - "*"
#       kinds:
#       - "*"
#       clusters:
#       - "*"
#       namespaces:
#       - kube-system

保存后等待ArgoCD重载配置(通常30秒内),然后ArgoCD就会完全忽略kube-system里的一切。注意:这个配置对已经同步的资源无效,需要先删除已有Application或者重新调整Application的destination.namespace

四、注意事项与常见误区

  • 不要一股脑把整个集群交给一个Application:对于现有集群,建议分命名空间或分应用创建多个小Application,每个只控制有限的资源。一旦出问题,影响范围小。
  • 小心syncOptions: ServerSideApply=true:虽然服务端apply可以减少冲突,但如果你的Git里缺少某些字段(比如managedFields),ArgoCD可能会把Kubernetes自动生成的字段删除,导致资源重建。建议在项目稳定后才启用。
  • 注意ArgoCD的权限问题:如果ArgoCD ServiceAccount权限太大(比如cluster-admin),它可能会删除CRD、Namespace等基础设施。建议通过RBAC限制它的操作范围,比如只允许管理特定命名空间。
  • 灰度策略:在敏感服务上可以先创建Application但syncPolicy为空,只做监控。然后使用argocd app sync --local手动触发同步,观察Pod的滚动更新情况。

五、文章总结

将现有Kubernetes集群纳入ArgoCD管理,本质上是一场风险控制游戏。核心原则是“先观察,后行动,留后路”。通过关闭自动修剪、局部注解保护、忽略非关键差异,以及分阶段迁移,可以最大程度避免资源被意外重建。建议新手从只读模式开始,熟悉ArgoCD的差异报告后,再逐步开放自动同步。记住:ArgoCD是一个工具,不是上帝。你应该始终握住“最后一道闸”——比如在关键资源上加上Prune=false注解,并且定期审查Application的同步状态。只有把安全机制和业务需求平衡好,才能真正享受到GitOps带来的版本控制和自动化红利。