- Published on
Argo CD Sync 실패? RBAC·CRD·Drift 진단법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Argo CD를 운영하다 보면 Sync 버튼을 눌렀는데도 리소스가 적용되지 않거나, OutOfSync가 계속 남거나, 특정 리소스만 반복적으로 실패하는 상황을 자주 만납니다. 이때 감으로 접근하면 원인 규명이 길어집니다.
이 글은 Argo CD Sync 실패를 RBAC(권한), CRD(스키마/리소스 타입), Drift(클러스터와 Git 상태 불일치) 세 범주로 빠르게 분류하고, 각 범주별로 관측 포인트 → 재현/검증 커맨드 → 해결 패턴 순서로 정리합니다.
운영 환경이 EKS라면 권한 문제(특히 AWS 연동)가 함께 얽히는 경우가 많습니다. Pod IAM 권한 이슈는 별도로 아래 글도 참고하면 좋습니다.
1) 먼저: 실패를 3분 안에 분류하는 체크리스트
Sync 실패를 보면 가장 먼저 다음 3가지를 확인합니다.
1-1. Argo CD 이벤트/로그에서 에러 키워드 뽑기
permission/forbidden/cannot이 보이면 RBAC 가능성이 큼no matches for kind/could not find the requested resource는 CRD/ApiVersion 문제diff/OutOfSync반복, 혹은 Sync는 성공인데 다시OutOfSync로 돌아오면 Drift 가능성
CLI를 쓴다면 애플리케이션 이벤트와 상태를 함께 봅니다.
argocd app get myapp
argocd app history myapp
argocd app diff myapp
쿠버네티스 이벤트도 같이 보면 힌트가 더 빨리 나옵니다.
kubectl -n argocd get pods
kubectl -n argocd logs deploy/argocd-application-controller --tail=200
kubectl -n argocd logs deploy/argocd-repo-server --tail=200
1-2. 실패가 "어떤 리소스"에서 나는지 확인
Argo CD UI에서 실패 리소스를 클릭하면 Message에 거의 답이 있습니다. 특히 다음 패턴은 바로 분류됩니다.
namespaces "X" is forbidden: User "system:serviceaccount:argocd:argocd-application-controller" cannot ...RBACresource mapping not found for name: ... no matches for kind "Foo" in version "example.com/v1"CRDfailed to apply: ... field is immutable또는cannot patch ...Drift또는 선언 방식 문제(특히Job,PVC,Service일부 필드)
1-3. Sync 옵션이 문제를 숨기고 있지 않은지 확인
다음 옵션은 증상을 바꾸거나(=원인을 가리거나) Drift를 악화시킬 수 있습니다.
Prune=false인데 Git에서 리소스를 지웠다면, 클러스터에 남아 Drift처럼 보일 수 있음ApplyOutOfSyncOnly=true는 일부 리소스 적용이 건너뛰어져 “왜 안 바뀌지?”로 이어질 수 있음ServerSideApply=true는 필드 소유권 충돌이 Drift로 나타날 수 있음
애플리케이션 spec을 확인합니다.
kubectl -n argocd get app myapp -o yaml | sed -n '1,200p'
2) RBAC 진단: "Argo CD가 적용할 권한이 없다"
RBAC 문제는 보통 2가지 축으로 나뉩니다.
- 쿠버네티스 RBAC: Argo CD 컨트롤러 ServiceAccount가 대상 리소스를
get/list/watch/create/patch/delete할 권한이 없음 - Argo CD 내부 RBAC: UI/CLI 사용자가 Sync를 누를 권한이 없음(이 글은 주로 1번)
2-1. 실패 메시지에서 ServiceAccount 주체를 확인
대부분 에러에 주체가 그대로 찍힙니다.
system:serviceaccount:argocd:argocd-application-controller- 또는
argocd:argocd-server(일부 작업)
2-2. kubectl auth can-i로 권한을 “증명”하기
예를 들어 my-namespace에 Deployment를 패치 못한다면 아래처럼 검증합니다.
kubectl auth can-i patch deployments.apps \
--namespace my-namespace \
--as system:serviceaccount:argocd:argocd-application-controller
CRD 리소스(예: VirtualService)도 똑같이 확인합니다.
kubectl auth can-i create virtualservices.networking.istio.io \
-n my-namespace \
--as system:serviceaccount:argocd:argocd-application-controller
여기서 no가 나오면 Argo CD Sync 실패는 “정상”입니다. 권한부터 해결해야 합니다.
2-3. 해결 패턴 1: 네임스페이스 단위 Role/RoleBinding
가장 안전한 방식은 앱 네임스페이스별로 Role을 주는 것입니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argocd-sync
namespace: my-namespace
rules:
- apiGroups: ["", "apps", "batch", "networking.k8s.io"]
resources: ["configmaps", "secrets", "services", "deployments", "statefulsets", "jobs", "ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-sync
namespace: my-namespace
subjects:
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: argocd-sync
2-4. 해결 패턴 2: ClusterRole이 필요한 케이스
다음 리소스는 클러스터 범위이므로 ClusterRole이 필요합니다.
CustomResourceDefinitionClusterRole,ClusterRoleBindingNamespaceMutatingWebhookConfiguration/ValidatingWebhookConfiguration
예를 들어 CRD를 Argo CD가 설치해야 한다면(운영 정책상 허용할 때만) 다음 권한이 필요합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-crd-admin
rules:
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-crd-admin
subjects:
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-crd-admin
운영 팁: “모든 걸 다 할 수 있는 ClusterRole”로 땜빵하면 당장은 편하지만, 이후 사고나 감사에서 크게 문제가 됩니다. 최소 권한으로 좁히고, 앱 팀별 네임스페이스 경계를 분명히 하는 편이 좋습니다. (조직 경계/책임 분리 관점에서는 DDD의 경계 개념도 참고할 만합니다: DDD 애그리게이트 경계 깨짐 - 해결 7가지)
3) CRD 진단: "리소스 Kind를 모른다" 또는 "API 버전이 없다"
CRD 문제는 대부분 아래 둘 중 하나입니다.
- CRD 자체가 아직 설치되지 않음
- CRD는 있는데
apiVersion이 다르거나, 컨트롤 플레인/애드온 버전이 바뀌어 리소스가 더 이상 유효하지 않음
3-1. 대표 에러 패턴
no matches for kind "X" in version "group/v1"the server could not find the requested resource
이건 Argo CD가 틀린 게 아니라, 쿠버네티스 API 서버가 그 타입을 모른다는 뜻입니다.
3-2. CRD 존재 여부 확인
예를 들어 VirtualService가 문제라면:
kubectl get crd | grep -i virtualservice
kubectl get crd virtualservices.networking.istio.io -o yaml | sed -n '1,120p'
또는 해당 리소스가 어떤 그룹/버전을 지원하는지 확인합니다.
kubectl api-resources | grep -i virtualservice
kubectl explain virtualservice --api-version=networking.istio.io/v1beta1 | head
3-3. 해결 패턴 1: CRD를 먼저 설치하고 그 다음 CR 적용
GitOps에서 흔한 실수는 “CRD와 CR을 같은 Sync wave로 적용”하는 것입니다. 그러면 CR이 먼저 적용되면서 실패합니다.
Argo CD에서는 sync-wave로 순서를 강제할 수 있습니다.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: foos.example.com
annotations:
argocd.argoproj.io/sync-wave: "-10"
---
apiVersion: example.com/v1
kind: Foo
metadata:
name: sample
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
size: 3
또는 앱을 2개로 쪼갭니다.
platform-crds앱: CRD/클러스터 범위 리소스workloads앱: 실제 서비스 리소스
이 분리는 RBAC 설계에도 유리합니다(플랫폼 팀만 CRD 권한 보유).
3-4. 해결 패턴 2: Helm 차트/매니페스트의 CRD 처리 방식 점검
Helm 기반이라면 CRD가 crds/ 디렉터리에 들어가 있기도 하고, 템플릿으로 렌더링되기도 합니다. Argo CD가 Helm을 렌더링할 때 CRD 적용 방식이 기대와 다를 수 있으니 다음을 확인합니다.
- 차트가 CRD를
crds/에 두는지 - Argo CD 앱 설정에서 Helm 파라미터/버전이 맞는지
- CRD 업데이트가 “삭제 후 재생성”을 요구하는지(운영 중엔 위험)
실제로 Argo CD가 렌더링한 결과를 보고 싶으면:
argocd app manifest myapp | sed -n '1,200p'
4) Drift 진단: "Sync는 됐는데 또 OutOfSync" / "적용이 계속 되돌아감"
Drift는 Git에 선언한 상태와 클러스터 실제 상태가 달라서 발생합니다. 원인은 크게 4가지가 흔합니다.
- 다른 컨트롤러(HPA, Operator, Admission Webhook)가 필드를 계속 변경
- 사람이 수동으로
kubectl edit또는 핫픽스 적용 immutable field변경처럼 “원래 patch로는 못 바꾸는” 변경- Server-Side Apply 필드 소유권 충돌
4-1. argocd app diff로 Drift의 필드 단위 확인
argocd app diff myapp
여기서 특정 annotation/label/replicas 같은 값이 계속 바뀐다면 “누가 바꾸는지”를 찾아야 합니다.
4-2. kubectl로 마지막 적용 주체 추적
managedFields를 보면 누가 언제 어떤 필드를 소유했는지 단서가 나옵니다.
kubectl -n my-namespace get deploy my-deploy -o yaml | sed -n '1,220p'
manager:에 kube-controller-manager, argocd-controller, helm, terraform, 특정 operator 이름 등이 보일 수 있습니다.
4-3. 해결 패턴 1: HPA/컨트롤러가 바꾸는 필드는 IgnoreDifferences
예: HPA가 spec.replicas를 바꾸면 Argo CD는 계속 되돌리려 하고, HPA는 다시 올리고… 싸움이 납니다.
Argo CD Application에 ignore 설정을 둡니다.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
ignoreDifferences:
- group: apps
kind: Deployment
name: my-deploy
namespace: my-namespace
jsonPointers:
- /spec/replicas
4-4. 해결 패턴 2: 웹훅이 주입하는 필드(예: sidecar, CA bundle) 무시
서비스 메시/보안 제품이 annotation이나 volume을 주입하면 diff가 커집니다. 이때도 ignore로 관리합니다.
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jqPathExpressions:
- ".spec.template.metadata.annotations"
주의: 너무 넓게 무시하면 “진짜 변경”도 놓칩니다. 가능한 한 좁게(리소스/필드 단위) 제한하세요.
4-5. 해결 패턴 3: immutable field 변경은 재생성이 필요
대표적으로 다음은 변경이 제한됩니다.
Service의spec.clusterIPJob의 일부 템플릿 필드PVC의 일부 스펙
이 경우 Argo CD가 patch하다 실패합니다. 해결은 보통 둘 중 하나입니다.
- 리소스 이름을 바꾸거나(새 리소스로 생성)
Replace전략을 사용해 삭제 후 재생성(다운타임/데이터 손실 위험 평가 필요)
Argo CD에서는 리소스에 replace 동작을 유도하는 애노테이션을 쓰기도 합니다(조직 표준에 맞게 제한적으로 사용 권장).
metadata:
annotations:
argocd.argoproj.io/sync-options: Replace=true
4-6. 해결 패턴 4: Prune/Finalize로 “남아 있는 리소스” 정리
Git에서 삭제했는데 클러스터에 남아 있으면 Drift처럼 보입니다. Prune이 꺼져 있거나, finalizer 때문에 삭제가 막힌 경우가 많습니다.
- 앱 설정에서
automated.prune확인 - 삭제가 안 되는 리소스는 finalizer를 확인
kubectl -n my-namespace get <리소스타입> <이름> -o yaml | sed -n '1,120p'
부득이하게 finalizer를 제거해야 한다면 영향도를 먼저 평가한 뒤 진행하세요.
5) 실전 트러블슈팅 플로우(요약)
운영 중에는 아래 순서로 보면 시간이 가장 덜 듭니다.
argocd app get/ UI에서 실패 리소스와 메시지 확인- 메시지가
forbidden이면kubectl auth can-i로 RBAC 증명 - 메시지가
no matches for kind면kubectl get crd와kubectl api-resources로 CRD/버전 확인 - 반복
OutOfSync면argocd app diff로 필드 확인 후managedFields로 변경 주체 추적 - HPA/웹훅/오퍼레이터가 바꾸는 필드는
ignoreDifferences로 최소 범위 무시 - immutable 변경은 replace/재생성 전략으로 설계 변경
6) 부록: 자주 쓰는 커맨드 모음
# 앱 상태/이벤트
argocd app get myapp
argocd app diff myapp
argocd app history myapp
# 컨트롤러 로그
kubectl -n argocd logs deploy/argocd-application-controller --tail=200
kubectl -n argocd logs deploy/argocd-repo-server --tail=200
# 권한 확인
kubectl auth can-i patch deployments.apps -n my-namespace \
--as system:serviceaccount:argocd:argocd-application-controller
# CRD/리소스 확인
kubectl get crd | head
kubectl api-resources | head
# 리소스 변경 주체(ManagedFields) 확인
kubectl -n my-namespace get deploy my-deploy -o yaml | sed -n '1,220p'
Argo CD Sync 실패는 “도구가 불안정해서”라기보다, 대부분 권한/스키마/상태 불일치 중 하나로 귀결됩니다. 위의 분류-검증 루틴을 팀 런북으로 고정해두면, 장애 대응 시 원인 규명 시간을 크게 줄일 수 있습니다.