ArgoCD Synced/OutOfSync 반복 - 고아 리소스 충돌
ArgoCD App 이름에 prefix를 추가하는 작업 직후, 해당 App이 0.5초 간격으로 Synced와 OutOfSync를 번갈아 표시하기 시작했다. 같은 방식으로 작업한 다른 클러스터에서는 아무 문제가 없었다. 클러스터 상태 차이가 있다는 뜻이었고, 파고 들어가니 이름 변경으로 관리 주체를 잃은 cert-controller가 49일째 조용히 살아있었다.
환경: ArgoCD 2.x, EKS, external-secrets Helm chart
0.5초마다 깜박이는 Sync 상태
external-secrets App을 svc-a-external-secrets로 이름을 바꾸고 Sync를 완료했는데, UI에서 상태가 멈추질 않았다. Synced가 됐다 싶으면 OutOfSync로 돌아오는 사이클이 0.5초 단위로 반복됐다.
같은 작업을 적용한 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가 지목됐다. cert-controller가 CA 인증서를 주입해 관리하는 webhook 설정이다. 이 값이 계속 바뀐다는 건 CA 인증서를 덮어쓰는 주체가 둘 이상이라는 뜻이었다.
두 cert-controller가 같은 webhook을 놓고 경쟁하고 있었다
CA 값이 계속 바뀌려면 누군가가 지속적으로 덮어쓰고 있어야 한다. cert-controller가 하나뿐이라면 CA를 한 번 설정하고 안정되지만, 두 개 이상이라면 서로 다른 CA를 번갈아 주입하는 무한 루프가 만들어진다.
kubectl -n external-secrets get deploy두 세트가 공존하고 있었다. svc-a-external-secrets-*(새 App이 배포한 것)와 external-secrets-*(App 이름 변경 이전의 고아). 이름 변경 전의 Deployment가 삭제되지 않고 그대로 남아 있었던 것이다.
cert-controller 로그를 보니 결정적인 증거가 나왔다.
kubectl logs svc-a-external-secrets-cert-controller --tail=10같은 초 내에 secretstore-validate를 여러 번 업데이트하는 로그가 찍히고 있었고, 주입되는 CA 값이 매번 달랐다. 두 controller가 서로의 CA를 감지하고 자신의 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 클러스터에서는 같은 작업 후 아무 문제가 없었는데, 확인해보니 기존 external-secrets-* Deployment 자체가 없었다. 클러스터 A에서만 기존 리소스가 49일째 운영 중이었고, 새 App 배포 시 삭제되지 않아 두 세트가 공존한 것이다.
ArgoCD App 이름 변경은 기존 리소스를 자동으로 삭제하지 않는다. App 이름이 바뀌면 ArgoCD는 새 App과 새 리소스를 생성할 뿐, 기존 리소스는 관리 주체를 잃고 고아가 된다. 고아가 된 cert-controller처럼 주기적으로 리소스를 업데이트하는 Controller는 삭제되기 전까지 계속 작동한다.
고아 리소스를 제거하면 경쟁이 해소된다
판단은 단순했다. 기존 external-secrets-* 리소스를 삭제하면 CA를 덮어쓰는 주체가 하나만 남고, 루프가 끊긴다.
아래 순서로 고아 Deployment와 연관 리소스를 정리했다. Deployment만 지우면 안 되고, ClusterRole/ClusterRoleBinding까지 함께 제거해야 권한 잔여물이 남지 않는다.
# 고아 Deployment 삭제
kubectl -n external-secrets delete deploy \
external-secrets \
external-secrets-cert-controller \
external-secrets-webhook
# 연관 리소스 정리
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
이 장애가 남긴 것
Synced/OutOfSync 깜박임은 ArgoCD 외부에서 리소스가 지속적으로 변경되고 있다는 신호다. ArgoCD가 불안정한 것이 아니라, 클러스터의 actual state 자체가 계속 바뀌고 있는 것이다. 이런 패턴을 보면 kubectl get application -o json으로 OutOfSync 리소스를 특정하고, 그 리소스를 누가 변경하는지부터 추적해야 한다.
같은 리소스를 관리하는 Controller가 2개 이상 존재하면 반드시 경쟁이 발생한다. 특히 cert-controller처럼 주기적으로 리소스를 조정하는 Controller는 고아 상태로 남은 순간부터 무한 루프의 씨앗이 된다.
ArgoCD App 이름 변경의 안전한 절차는 이렇다. 먼저 기존 App을 Non-Cascading으로 삭제해 리소스는 클러스터에 남기되 App 관리 주체만 제거한다. 그 다음 새 이름으로 App을 생성하고 Sync한다. 이 순서를 지키면 고아 리소스 없이 이름을 전환할 수 있다.
앞으로의 방향
현재 App 이름 변경은 수동 절차에 의존하고 있다. 같은 패턴의 사고가 재발할 수 있으므로, App 이름 변경 시 기존 리소스 존재 여부를 사전에 감지하는 방안을 검토 중이다. 클러스터별로 Deployment 목록을 비교하는 간단한 스크립트로도 이번 케이스는 사전에 잡을 수 있었다.
또한 클러스터 간 상태 차이(external-secrets-* 존재 유무)가 문제를 만들었으므로, 클러스터 간 리소스 상태를 주기적으로 비교하는 drift 감지 체계를 구축하는 것이 다음 과제다.
정보 공백 요약
| # | 필요한 정보 | 이유 | 위치 |
|---|---|---|---|
| 1 | Synced/OutOfSync 깜박임 전후 ArgoCD UI 스크린샷 | ”고아 삭제 직후 안정화” 시점의 시각적 증거 | 해결 방법 섹션 |
| 2 | secretstore-validate CA 변경 빈도 로그 (타임스탬프 포함) | 두 controller가 실제로 경쟁한다는 정량적 증거 | 원인 분석 섹션 |