ArgoCD Synced/OutOfSync 반복 - 고아 리소스 충돌

App 이름 변경 후 고아 cert-controller가 동일한 ValidatingWebhookConfiguration에 경쟁적으로 CA를 주입하며 ArgoCD가 Synced/OutOfSync를 반복했다. 고아 Controller를 삭제하여 해결했다.

환경: ArgoCD 2.x, EKS, external-secrets Helm chart 날짜: 2026-03-02

상황

external-secrets ArgoCD App 이름에 서비스별 prefix를 추가하는 작업을 하고 있었다(external-secretssvc-a-external-secrets). 이름을 바꾸고 Sync를 완료했는데, App이 ArgoCD UI에서 0.5초마다 Synced/OutOfSync를 반복하는 깜박임이 발생했다.

처음엔 단순한 Sync 오류라고 생각했지만, 같은 작업을 한 svc-b-external-secrets는 정상이었다. 같은 방식으로 작업했는데 한쪽만 문제라는 건, 작업 자체가 아니라 클러스터 상태에 차이가 있다는 뜻이었다.

관찰한 사실

Synced/OutOfSync를 반복한다는 건, ArgoCD가 Git의 desired state와 클러스터의 actual state를 비교할 때마다 차이가 생겼다 사라졌다를 반복한다는 뜻이다. 무엇이 OutOfSync인지 먼저 특정해야 했다.

kubectl get application svc-a-external-secrets -o json \
  | jq '.status.resources[] | select(.status != "Synced")'

ValidatingWebhookConfiguration/secretstore-validate가 OutOfSync였다. 이 리소스는 cert-controller가 CA 인증서를 주입해서 관리하는 webhook 설정이다. 이 값이 깜박인다는 건, CA 인증서 값이 계속 바뀌고 있다는 뜻이었다.

가설과 검증 과정

가설: cert-controller가 하나 이상 실행되고 있다

CA 값이 계속 바뀌려면, 누군가가 반복적으로 값을 덮어쓰고 있어야 한다. cert-controller가 하나뿐이라면 CA를 한 번 설정하고 안정될 텐데, 두 개 이상이라면 서로 다른 CA를 번갈아 주입하면서 무한 루프가 생길 수 있다고 생각했다.

클러스터의 Deployment를 확인해보니 의심이 맞았다.

kubectl -n external-secrets get deploy

두 세트가 공존하고 있었다: svc-a-external-secrets-*(새 App이 만든 것)와 external-secrets-*(기존 고아). App 이름 변경 전의 리소스가 삭제되지 않고 그대로 남아 있었던 것이다.

cert-controller 로그를 확인하니 결정적인 증거가 나왔다.

kubectl logs svc-a-external-secrets-cert-controller --tail=10

같은 초 내에 secretstore-validate를 여러 번 업데이트하는 로그가 찍히고 있었고, 주입되는 CA 값이 매번 달랐다. 두 controller가 경쟁하고 있다는 직접 증거였다.

결과: 채택. 두 cert-controller가 동일한 ValidatingWebhookConfiguration에 서로 다른 CA를 경쟁적으로 주입하면서 무한 루프가 발생한 것이다.

sequenceDiagram
    participant A as svc-a-cert-controller
    participant W as secretstore-validate
    participant B as cert-controller (고아)

    A->>W: CA-A 주입
    B->>W: CA 다름 감지 → CA-B 주입
    A->>W: CA 다름 감지 → CA-A 주입
    Note over A,B: 무한 반복 → ArgoCD가 변경 감지 → Synced/OutOfSync 깜박임

왜 svc-b에서는 문제가 없었는가?

같은 작업을 한 클러스터 B에서는 깜박임이 없었다. 확인해보니 클러스터 B에는 기존 external-secrets-* Deployment가 없었다. 클러스터 A에서만 기존 리소스가 49일째 운영 중이었고, 새 App 배포 시 이 리소스가 삭제되지 않아 두 세트가 공존하게 된 것이다.

이 비교를 통해 “App 이름 변경”이라는 동작이 기존 리소스를 자동으로 정리하지 않는다는 점이 확정됐다.

근본 원인

ArgoCD App 이름을 변경하면 기존 리소스가 자동 삭제되지 않는다. App 이름에 prefix를 추가하자 ArgoCD는 새 App과 새 리소스(svc-a-external-secrets-*)를 생성했지만, 기존 리소스(external-secrets-*)는 관리 주체를 잃고 고아로 남았다. 두 cert-controller가 공존하면서 동일한 WebhookConfiguration에 서로 다른 CA를 경쟁적으로 주입하는 무한 루프가 만들어졌다.

해결 방법

고아 리소스(기존 external-secrets-*)를 삭제하면 경쟁이 해소될 거라고 판단했다.

# 고아 Deployment 삭제
kubectl -n external-secrets delete deploy \
  external-secrets \
  external-secrets-cert-controller \
  external-secrets-webhook
 
# 연관 리소스 정리 (SA, Service, Secret, ClusterRole, ClusterRoleBinding)
kubectl -n external-secrets delete sa \
  external-secrets external-secrets-cert-controller external-secrets-webhook
kubectl -n external-secrets delete svc external-secrets-webhook
kubectl -n external-secrets delete secret external-secrets-webhook
kubectl delete clusterrole \
  external-secrets-cert-controller external-secrets-controller \
  external-secrets-edit external-secrets-servicebindings external-secrets-view
kubectl delete clusterrolebinding \
  external-secrets-cert-controller external-secrets-controller

삭제 후 App을 Sync하니 깜박임이 즉시 사라졌다.

NAME                                                    READY   STATUS
svc-a-external-secrets-86bd84957d-5frdx                   1/1     Running
svc-a-external-secrets-cert-controller-77b9bf84cf-wm6kd   1/1     Running
svc-a-external-secrets-webhook-6d887f5bfc-6f5kw           1/1     Running

Sync: Synced, Health: Healthy

교훈

  • ArgoCD App 이름 변경 시에는 기존 리소스를 먼저 정리해라. App 이름을 바꾸면 ArgoCD는 새 App을 만들 뿐, 기존 리소스는 관리 주체를 잃고 고아가 된다. 이름 변경 전에 기존 App을 Non-Cascading으로 삭제하고, 새 이름으로 재생성해야 한다.
  • 같은 리소스를 관리하는 Controller가 2개 이상이면 반드시 경쟁이 발생한다. 특히 cert-controller처럼 주기적으로 리소스를 업데이트하는 Controller는 고아로 남는 즉시 무한 루프를 만든다.
  • Synced/OutOfSync 깜박임은 외부에서 리소스가 지속적으로 변경되고 있다는 신호다. kubectl get application -o json으로 OutOfSync 리소스를 먼저 특정하고, 그 리소스를 누가 변경하고 있는지 추적해라.