- Published on
Argo CD Sync 실패 - OutOfSync·Health Degraded 8가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에 Argo CD를 붙여두면 배포가 ‘자동화’된 것 같지만, 실제 운영에서 자주 마주치는 장면은 OutOfSync가 계속 남아 있고, 상태는 Health Degraded로 떨어지며, Sync 버튼을 눌러도 적용이 실패하거나 적용되었다가 다시 되돌아가는 상황입니다. 문제는 대부분 “Argo CD가 틀렸다”가 아니라, 클러스터가 원하는 상태(desired)와 실제 상태(live)를 일치시키는 과정에서 막히는 지점이 있다는 뜻입니다.
이 글은 Argo CD에서 Sync 실패/OutOfSync/Degraded를 만들기 쉬운 원인 8가지를, 현장에서 바로 써먹을 수 있는 진단 순서와 커맨드로 정리합니다.
먼저: 증상을 ‘Sync’와 ‘Health’로 분리해서 본다
Argo CD UI에서 흔히 보이는 조합은 다음과 같습니다.
- OutOfSync + Healthy: 리소스는 정상인데, Git과 live가 다름(드리프트/차이/무시 규칙 문제)
- Synced + Degraded: 원하는 스펙은 적용됐지만, 파드/서비스/인그레스가 정상 상태가 아님(런타임/의존성/권한/네트워크)
- OutOfSync + Degraded: 적용 자체가 안 됐거나, 일부만 적용되어 런타임도 깨짐
진단의 시작은 항상 이벤트/차이/헬스 원인을 빠르게 좁히는 것입니다.
# 1) 앱 상태 요약
argocd app get my-app
# 2) Sync 실패 이벤트/메시지
argocd app history my-app
argocd app logs my-app
# 3) 어떤 리소스가 문제인지
argocd app diff my-app
argocd app resources my-app
# 4) 쿠버네티스 이벤트(특히 Degraded면 필수)
kubectl -n <ns> get events --sort-by=.lastTimestamp | tail -n 50
원인 1) 필드 드리프트: 웹훅/컨트롤러가 live를 바꾼다(OutOfSync 지속)
전형적 증상
- Sync는 성공인데 곧바로 OutOfSync로 돌아감
- diff를 보면 특정 필드가 매번 바뀜(예:
metadata.annotations,spec.replicas,status유사 필드)
흔한 사례
- HPA가
spec.replicas를 바꾸는데 Git에는 고정 replicas가 있음 - Ingress Controller가 annotation을 자동 주입
- Service가
clusterIP등 immutable/자동 할당 필드 차이
진단
argocd app diff my-app --local ./manifests
# UI diff에서 반복적으로 바뀌는 필드 확인
해결
- 의도된 변형은 ignoreDifferences로 무시하거나, Git 스펙을 컨트롤러와 일치시키세요.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
- group: ""
kind: Service
jsonPointers:
- /spec/clusterIP
> 주의: 무시 규칙은 “문제를 숨기는” 도구가 될 수 있습니다. HPA/오토인젝션처럼 원천적으로 드리프트가 정상인 경우에만 적용하세요.
원인 2) CRD/리소스 적용 순서 문제: CRD가 없어서 Sync 실패
전형적 증상
- Sync 에러:
no matches for kind ... in version ... - Helm/Kustomize로 CRD와 CR을 같이 올릴 때 자주 발생
진단
argocd app logs my-app | grep -i "no matches for kind" -n
kubectl get crd | grep -i <crd-name>
해결
- CRD를 별도 Application으로 먼저 배포하거나, sync wave로 순서를 강제합니다.
# CRD 매니페스트(혹은 CRD를 포함한 kustomize/helm output)에 wave 부여
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0"
---
# CR(커스텀 리소스)에는 더 큰 wave
metadata:
annotations:
argocd.argoproj.io/sync-wave: "10"
원인 3) Immutable 필드 변경: Service/StatefulSet 등에서 ‘바꿀 수 없는 값’을 바꿈
전형적 증상
- Sync 실패 메시지에
field is immutable/Forbidden: updates to ... are forbidden - 대표적으로
Service.spec.clusterIP,StatefulSet.spec.serviceName,spec.selector등이 원인
진단
argocd app logs my-app | egrep -i "immutable|forbidden" -n
kubectl -n <ns> describe svc <name> | sed -n '1,120p'
해결
- 리소스를 삭제 후 재생성해야 합니다.
- Argo CD에서
Replace=true를 쓰거나, 수동으로 안전하게 교체합니다.
metadata:
annotations:
argocd.argoproj.io/sync-options: Replace=true
> Stateful 리소스는 데이터/다운타임 이슈가 있으니, 삭제 전 PVC/PodDisruptionBudget/롤링 전략을 점검하세요.
원인 4) Finalizer/삭제 대기: 리소스가 Terminating에 걸려 Sync가 멈춘다
전형적 증상
- Sync가 “진행 중”에서 오래 멈춤
- 어떤 리소스는
Terminating상태로 고착 - Degraded로 보이거나, 다음 wave가 진행되지 않음
진단
kubectl -n <ns> get <kind> <name> -o json | jq '.metadata.finalizers'
kubectl -n <ns> describe <kind> <name> | sed -n '1,200p'
해결
- 원인 컨트롤러(예: ingress/controller, external-dns, cert-manager, aws-load-balancer-controller)가 정상인지 확인
- 정말 막혔다면 최후 수단으로 finalizer 제거
kubectl -n <ns> patch <kind> <name> --type=merge -p '{"metadata":{"finalizers":[]}}'
원인 5) RBAC/권한 부족: Argo CD가 apply/patch를 못 한다(특히 클러스터 외부 리소스)
전형적 증상
- Sync 실패:
forbidden: User "system:serviceaccount:argocd:argocd-application-controller" cannot ... - 특정 네임스페이스/클러스터스코프 리소스에서만 실패
진단
argocd app logs my-app | grep -i forbidden -n
kubectl auth can-i create clusterrole --as system:serviceaccount:argocd:argocd-application-controller
kubectl auth can-i patch deploy -n <ns> --as system:serviceaccount:argocd:argocd-application-controller
해결
- 필요한 최소 권한으로 Role/ClusterRole을 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-extra
rules:
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","list","watch","create","update","patch","delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-extra
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-extra
subjects:
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
권한 이슈는 EKS에서 IAM/IRSA와 엮이면 더 복잡해집니다. 특히 KMS나 AWS API 접근이 섞이면 “쿠버네티스 RBAC은 맞는데 AWS가 403” 같은 형태로 보입니다. 이 경우는 EKS IRSA는 되는데 KMS Decrypt 403 해결법도 같이 점검하면 원인 분리가 빨라집니다.
원인 6) 이미지/시크릿/레지스트리 문제로 파드가 뜨지 않는다(Health Degraded)
전형적 증상
- Argo CD는 Synced인데 Health가 Degraded
- Kubernetes 이벤트에
ImagePullBackOff,ErrImagePull,CreateContainerConfigError
진단
kubectl -n <ns> get pods
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> get events --sort-by=.lastTimestamp | egrep -i "ImagePull|BackOff|CreateContainer"
해결
imagePullSecrets누락, 레지스트리 권한, 태그 오타 확인- Secret/ConfigMap 키 이름이 컨테이너 envFrom/volumeMount와 일치하는지 확인
spec:
imagePullSecrets:
- name: regcred
containers:
- name: api
image: ghcr.io/org/api:2026-02-23
envFrom:
- secretRef:
name: api-secret
원인 7) 리소스 부족/스케줄링 실패: Pending이 길어져 Degraded
전형적 증상
- Pod가
Pending에서 멈춤 - 이벤트에
0/.. nodes are available,Insufficient cpu/memory,taint관련 메시지
진단
kubectl -n <ns> get pod <pod> -o wide
kubectl -n <ns> describe pod <pod> | egrep -i "Insufficient|taint|node affinity|0/" -n
kubectl describe node <node> | egrep -i "Allocatable|Allocated" -n
해결
- requests/limits 재조정, 노드 오토스케일러 확인, taint/toleration 정합성 점검
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
원인 8) Admission/Webhook/OPA/Gatekeeper 정책으로 거부된다(Sync 실패 또는 부분 적용)
전형적 증상
- Sync 실패 로그에
admission webhook ... denied the request - 특정 라벨/보안컨텍스트/이미지 정책 때문에 생성 자체가 거부
진단
argocd app logs my-app | grep -i "admission webhook" -n
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations
해결
- 정책이 요구하는 필드를 매니페스트에 추가(예:
runAsNonRoot,readOnlyRootFilesystem,labels) - 웹훅 엔드포인트 장애(타임아웃)인 경우, 해당 컨트롤러의 상태/네트워크를 우선 복구
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
클러스터 내부 네트워크나 DNS 이슈로 웹훅 호출이 타임아웃 나면, “정책 거부”처럼 보이지만 실은 통신 장애입니다. EKS 환경에서 이런 류의 간헐 타임아웃은 EKS TLS handshake timeout 해결 - IRSA·VPC·CoreDNS에서 소개한 점검 루틴이 그대로 도움이 됩니다.
실전 트러블슈팅 체크리스트(순서대로 보면 빠르다)
아래 순서대로 보면 대부분 10~15분 내로 원인을 좁힐 수 있습니다.
- Argo CD diff 확인: OutOfSync면 무엇이 다른지부터 고정
argocd app diff my-app
- Argo CD logs에서 첫 에러 찾기: Sync 실패면 에러 1줄이 거의 정답
argocd app logs my-app | tail -n 200
- Kubernetes events 확인: Degraded면 이벤트에 답이 있음
kubectl -n <ns> get events --sort-by=.lastTimestamp | tail -n 50
- 권한(forbidden)/정책(admission)/immutable 키워드로 분기
- CRD 존재/순서(wave) 점검
- ImagePull/Secret/ConfigMap 점검
- 스케줄링(Pending) 점검
- Finalizer/Terminating 점검
마무리: OutOfSync는 ‘차이’, Degraded는 ‘실행 실패’다
Argo CD에서 Sync가 실패하거나 OutOfSync/Degraded가 뜰 때, 감으로 만지기 시작하면 시간이 오래 걸립니다. 대신 다음 원칙만 지키면 속도가 확 달라집니다.
- OutOfSync: diff가 말해주는 “차이의 종류(드리프트/순서/immutable)”를 먼저 해결
- Degraded: 쿠버네티스 이벤트가 말해주는 “런타임 실패(이미지/시크릿/스케줄링/웹훅)”를 해결
운영에서 자주 반복되는 장애 패턴(권한, 네트워크 타임아웃, 정책 거부 등)은 별도 런북으로 만들어두면, Argo CD는 다시 ‘자동화 도구’로 돌아옵니다.
추가로, 배포 후 파드에 들어가 로그를 보거나 exec가 안 되는 상황까지 겹치면 진단이 더 어려워지는데, 이 경우 EKS에서 kubectl exec·logs가 안 될 때 진단법의 체크리스트를 함께 적용하면 원인 분리가 빨라집니다.