ArgoCd는 GitOps 도구로 항상 Git 저장소의 상태(Desired State, 원하는 상태)와 실제 클러스터 상태(Live State, 현재상태)를 똑같이 일치시키려고 감시한다.
OutOfSync는 내가 깃헙에 올려둔 코드랑 실제 쿠버네티스 클러스터에 배포되어 있는 상태가 서로 다를 때를 말한다.
이때 Sync(동기화) 버튼을 누르거나(CLI로 Sync하거나) 자동 동기화(Auto-Sync)가 작동하면 Synced로 바뀐다.
이번 케이스는 Synced가 안되는 상황 중 하나를 트러블슈팅한 것이다.
배경 및 문제상황
발생 일자: 2026-05-14
재현 일자: 2026-05-29
shoong-delivery의 GitOps 파이프라인을 처음 셋업하던 중이었다. 4개 MSA 서비스(order/kitchen/delivery/notification)와 batch CronJob을 공통 Helm 차트(charts/shoong-app)로 배포하려고 dev 환경 ArgoCD Application 5개를 등록했다.
ArgoCD UI에서 dev-shoong-* 앱 4개가 OutOfSync 상태로 표시됐다.
Sync 버튼을 아무리 눌러도 아주잠깐 Synced되었다가 OutOfSync로 돌아왔다.


CLI로 OutOfSync 앱만 필터링한 화면.
원인분석
원인을 찾기 위해 각 OutOfSync 앱으로 들어가 DIFF 탭을 살폈다.
어느 라인에서 계속 다른 상태로 남아있는지 확인하기 위함이었다.
Namespace 리소스의 tracking-id 어노테이션 충돌
4개 앱(batch/delivery/kitchen/order)의 DIFF 탭을 열어보니 공통적으로 /Namespace/shoong 리소스 하나에서 diff가 잡혔다. 차이 나는 필드는 argocd.argoproj.io/tracking-id 어노테이션이었고, 라이브 클러스터에 박혀있는 값이 해당 앱의 이름이 아닌 다른 앱 이름이었다.
dev-shoong-batch DIFF — 라이브:
dev-shoong-order, 원하는 값:dev-shoong-batch
dev-shoong-delivery DIFF — 라이브:
dev-shoong-order, 원하는 값:dev-shoong-delivery

dev-shoong-kitchen DIFF — 라이브:
dev-shoong-order, 원하는 값:dev-shoong-kitchen
dev-shoong-order DIFF — 라이브:
dev-shoong-notification, 원하는 값:dev-shoong-order
tracking-id는 ArgoCD가 sync할 때 클러스터에 직접 기록하는 어노테이션으로 사용자가 git에 직접 써넣는 값이 아니다.
ArgoCD가 클러스터에 배포된 수많은 리소스(Deployment, Service, Pod 등)들이 '어떤 ArgoCD 애플리케이션'에 의해 관리되고 있는지 추적하기 위해 사용하는 식별 태그이다.
예를 들어 shoong-batch 앱의 diff에 있는 것을 봤을 때
apiVersion: v1
kind: Namespace
metadata:
annotations:
argocd.argoproj.io/tracking-id: dev-shoong-delivery:/Namespace:shoong/shoong # 기존 것.
argocd.argoproj.io/tracking-id: dev-shoong-batch:/Namespace:shoong/shoong # 바꾸려고 하는 것.
batch 앱이 말하는 것은 "클러스터의 Namespace/shoong에는 dev-shoong-order가 소유자라고 적혀있는데 나(dev-shoong-batch)는 내가 소유자여야 한다고 생각한다. 그러니 내 이름으로 바꾸겠다."
같은 논리로 delivery, kitchen, order도 각자 자기 이름으로 바꾸려고 시도했던거다.
왜 notification은 문제 없는거지?, 다른 앱들은 자기 Namespace 리소스의 소유권을 주장하는 하는거지 궁금했다.
좀 더 문제의 원인을 찾아보기 위해 앱을 하나씩 수동 sync 해봤다.
shoong-batch를 sync하면 batch가 Synced 상태가 된다.

이어서 shoong-delivery를 sync하면,

batch는 OutOfSync로 돌아가고 delivery가 Synced가 된다. 이 시점에서 batch의 DIFF를 보면 dev-shoong-delivery → dev-shoong-batch로 바뀌어 있다.
한 앱을 sync할 때마다 해당 앱이 /Namespace/shoong의 tracking-id를 자기 이름으로 덮어쓰고, 그 순간 다른 앱들은 라이브 값과 자신이 원하는 값이 달라져 OutOfSync로 떨어지는 구조였다. 어떤 순서로 sync해도 한 번에 하나만 Synced가 될 수 있었고, 나머지는 항상 OutOfSync인 무한 순환이었다.
tracking-id를 서로 덮어쓰는 메커니즘은 파악했지만, 왜 5개 앱이 모두 같은 Namespace/shoong 리소스를 관리하려 하는지 처음에 이해하지 못했다.
5개 앱의 파드와 서비스가 shoong 네임스페이스 안에서 동작하는 건 맞지만 그게 Namespace 리소스 자체를 앱 각자가 관리해야 한다는 의미는 아닌데 말이다.
이 문제의 발단은 헬름 차트에 namespace.yaml 파일을 추가하면서 발생했다.
istio 서비스 메시 구축 중에 shoong 네임스페이스 안의 모든 파드에 Istio 사이드카를 주입 시키려고 했었다. 파드마다 개별 설정하지 않고 Namespace에 istio-injection: enabled 라벨을 붙이면 그 안의 모든 리소스에 일괄 적용되기 때문에, 이를 자동화하려고 공통 차트에 namespace.yaml을 추가했던 것이 문제의 발단이었다.
# charts/shoong-app/templates/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: shoong
labels:
istio-injection: enabled
문제해결
shoong-app 차트를 기반으로 5개 앱이 각각 독립적인 Helm 릴리스로 배포된다. 차트 안에 namespace.yaml이 있으니 5개 릴리스 모두가 Namespace/shoong를 자기 소속 리소스로 포함하게 된다. ArgoCD 입장에서는 5개 앱이 동시에 같은 Namespace를 내꺼라고 선언한 상황이 된 것이었다.
namespace.yaml 삭제
먼저, 공통 차트에서 namespace.yaml을 제거해 5개 앱이 Namespace 리소스를 각자 관리하려는 충돌 자체를 없앴다.
istio-injection: enabled 라벨은 어디에 붙여야하나?
그럼 결국에는 각각의 리소스에 sidecar.istio.io/inject: "true" 라벨을 붙여야하나?
특정 파드만 Istio 사이드카 주입하고 싶다면 그럴 수 있겠지만 이 프로젝트에서는 shoong 네임스페이스에 속하는 모든 파드들 Istio 서비스 메쉬에 포함되어야했다.
몇 가지 대안을 찾아봤다.
Terraform으로 Namespace 사전 생성
ArgoCD가 배포되기 전 Terraform 단계에서 istio-injection: enabled 라벨을 포함해 Namespace를 미리 만들어두는 방법init.sh 에서 Namespace 사전 생성
위 Terraform 방법과 비슷하게 init.sh 에서 네임스페이스를 미리 생성하는 것이다.
위 두 방법으로 문제를 간단히 해결할 수는 있지만,
Git을 단일 공급원으로 사용하면서 모든 상태를 선언적으로 기술되어야한다라는 GitOps 운영 철학과 맞지않다.
Namespace 전용 ArgoCD Application 별도 생성
Namespace만 관리하는 별도 ArgoCD 앱(infra-namespace)을 만들어서 단독으로 소유하는 방법.
-> 앱 수가 늘어나고 네임스페이스 하나를 위해 Application을 추가하는 게 과하다고 생각했다.managedNamespaceMetadata 추가
ArgoCD 애플리케이션 설정(Application Manifest) 자체에는 해당 앱이 배포될 네임스페이스가 없을 경우 자동으로 생성해 주는 내장 기능이 있다.syncOptions에CreateNamespace=true를 추가하면 된다. 이미 이 방식으로 네임스페이스가 없을 경우 추가가 되고 있었는데, 이때 생성되는 네임스페이스에 라벨까지 함께 주입하도록 설정하는 방법이 managedNamespaceMetadata이다.managedNamespaceMetadata는 ArgoCD가 Namespace를 생성할 때 라벨과 어노테이션을 직접 관리하는 옵션이다. Helm 릴리스가 Namespace 리소스를 만드는 게 아니라 ArgoCD 자체가 처리하기 때문에 tracking-id 충돌 대상이 되지 않는다.# argocd/dev/shoong-batch.yaml (나머지 4개도 동일) syncPolicy: automated: prune: true selfHeal: true managedNamespaceMetadata: labels: istio-injection: enabled syncOptions: - CreateNamespace=true-> GitOps의 선언적 관리 원칙을 지키면서도 추가적인 App 분리없이 깔끔하게 해결이 된다.
결과
namespace.yaml 삭제 + managedNamespaceMetadata 추가 후 sync하면 5개 앱 모두 Synced 상태로 유지된다.

재발방지대책
공유 Helm 차트에 클러스터 범위 리소스를 넣지 않는다
Namespace, ClusterRole, ClusterRoleBinding처럼 클러스터 전체 범위의 리소스는 여러 ArgoCD Application이 같은 차트를 쓸 때 소유권 충돌이 생긴다. 공유 차트에는 Deployment, Service, ConfigMap처럼 해당 앱 고유의 네임스페이스 범위 리소스만 포함해야 한다.
Namespace 라벨/어노테이션 관리는 managedNamespaceMetadata를 우선 사용한다
namespace.yaml을 차트에 넣는 대신 ArgoCD Application의 managedNamespaceMetadata로 관리하면 tracking-id 충돌 없이 선언적으로 유지할 수 있다. 여러 앱이 같은 네임스페이스를 대상으로 해도 충돌이 없다.
Sync가 안 풀릴 때 DIFF 탭을 먼저 확인한다
Sync 버튼을 반복해서 누르기 전에 DIFF 탭에서 어떤 리소스, 어떤 필드에서 차이가 발생하는지 먼저 파악한다. 어떤 값이 충돌하는지를 보면 ArgoCD 내부 동작 문제인지(tracking-id), 외부 컴포넌트가 런타임에 값을 바꾸는 문제인지(Istio failurePolicy 등) 빠르게 분류할 수 있다.
'TroubleShooting' 카테고리의 다른 글
| EBS CSI 드라이버 미설치로 인한 PVC Pending 이슈 (1) | 2026.06.01 |
|---|---|
| [AWS] 프리티어 노드 용량 부족 이슈 (0) | 2026.05.18 |
| [AWS] ESO(External Secrets Operator) 설정 (0) | 2026.05.18 |
| [AWS] EKS kubectl 접근 트러블슈팅 기록 (0) | 2026.05.18 |
| [AWS] Terraform으로 EKS Cluster SG 포트 범위 최소화하기 (0) | 2026.05.18 |