- Published on
Argo CD sync 실패 - 비교/헬스체크 원인 9가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡해 보이는데 Argo CD 화면은 계속 OutOfSync 이거나 Degraded 로 남고, Sync failed 가 반복되면 팀 전체의 배포 신뢰도가 급격히 무너집니다. 특히 실패 원인이 "리소스 비교(diff)" 단계인지, "헬스체크(health)" 단계인지 분리해서 보지 않으면 같은 문제를 계속 삽질하게 됩니다.
이 글은 Argo CD에서 sync 실패를 일으키는 대표 원인을 비교(diff) 관점과 헬스체크(health) 관점으로 나눠 9가지로 정리하고, 각 케이스마다 확인 포인트와 해결책을 제시합니다.
참고로 클러스터 쪽에서 리소스가 비정상 상태로 오래 머무는 문제는 Argo CD 실패의 2차 증상일 때가 많습니다. 예를 들어 파드가 종료되지 못하고 계속 남아 있으면 health가 Progressing 이나 Degraded 로 떨어질 수 있습니다. 이런 케이스는 Azure AKS에서 Pod가 Terminating에 멈출 때 해결법도 함께 보시면 원인 분리가 빨라집니다.
먼저: 실패가 diff인지 health인지 구분하기
Argo CD UI에서 App 상세로 들어가면 대체로 다음 신호로 구분할 수 있습니다.
- diff 문제
OutOfSync가 유지되고, 리소스별로Diff에 차이가 계속 보임- Sync 시도 시
ComparisonError혹은 특정 리소스 patch/apply 실패가 함께 발생
- health 문제
Synced인데도Degraded또는Progressing- 리소스 이벤트에
Readiness probe failed,CrashLoopBackOff,ImagePullBackOff등이 보임
CLI로는 아래처럼 확인합니다.
argocd app get my-app
argocd app diff my-app
argocd app history my-app
argocd app logs my-app --tail 200
또한 실제 클러스터 상태는 다음처럼 교차 확인합니다.
kubectl -n my-ns get all
kubectl -n my-ns describe deploy my-deploy
kubectl -n my-ns get events --sort-by=.lastTimestamp | tail -n 50
비교(diff)에서 자주 터지는 원인 5가지
1) 서버 사이드 필드가 계속 바뀌는 리소스(드리프트)
대표적으로 Deployment 의 metadata.annotations 중 일부, 혹은 다른 컨트롤러가 자동으로 주입하는 값이 Git의 선언과 계속 달라져 OutOfSync 가 반복됩니다.
징후
- Sync 직후에는 맞았다가 곧바로 다시
OutOfSync - Diff에
managedFields,last-applied-configuration, 자동 주입 annotation 등이 보임
- Sync 직후에는 맞았다가 곧바로 다시
해결
- Argo CD
ignoreDifferences로 특정 필드 무시 - 가능하면 자동 주입을 유발하는 설정 자체를 Git 쪽으로 끌어올려 일관성 유지
- Argo CD
예시(Application 에서 특정 필드 무시):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/template/metadata/annotations
주의: 무시 범위를 너무 넓히면 실제 변경을 놓칠 수 있으니, 문제를 일으키는 키만 좁혀서 지정하는 편이 안전합니다.
2) CRD/CR 버전 불일치 또는 스키마 변경
CRD가 먼저 설치되지 않았거나, 클러스터의 CRD 버전과 Git이 생성하는 CR의 apiVersion 이 맞지 않으면 비교/적용 단계에서 실패합니다.
징후
no matches for kind또는cannot decode류 에러- 동일한 리소스가 환경별로만 실패(클러스터마다 CRD 버전이 다름)
해결
- CRD를 별도 App으로 분리해 먼저 sync
sync-wave를 사용해 CRD가 먼저 적용되도록 순서 제어
예시(리소스에 sync wave 부여):
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1"
3) SSA(Server-Side Apply)와 필드 소유권 충돌
Argo CD가 SSA로 apply할 때, 다른 도구(예: kubectl apply, Helm, 오퍼레이터)가 같은 필드를 소유하고 있으면 충돌이 나거나, 적용은 되지만 diff가 계속 생길 수 있습니다.
징후
- 에러 메시지에
field manager또는conflict가 포함 - 특정 필드만 계속 원복되거나 튕김
- 에러 메시지에
해결
- 한 리소스는 한 소스(Argo CD)에서만 관리하도록 운영 규칙 정리
- 불가피하면 해당 필드를 Argo CD가 건드리지 않도록 분리(차트 값 분리, Kustomize patch 분리)
4) Webhook/Policy가 apply를 거부(Admission 차단)
OPA Gatekeeper, Kyverno, 자체 ValidatingWebhook 등이 Git 선언을 정책 위반으로 판단해 거부하면 sync가 실패합니다.
징후
- Argo CD 이벤트/로그에
admission webhook거부 메시지 kubectl apply로도 동일하게 실패
- Argo CD 이벤트/로그에
해결
- 거부 사유를 그대로 정책 예외로 만들기보다는, 선언을 정책에 맞게 수정
- 네임스페이스 라벨/어노테이션 기반 예외 규칙을 운영 표준으로 문서화
진단 커맨드:
kubectl -n my-ns describe rs
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations
5) 생성 순서 문제(특히 Namespace, RBAC, Secret)
Namespace 나 ServiceAccount/RoleBinding/Secret 이 먼저 있어야 하는데 동시에 apply되거나 순서가 꼬이면 sync가 실패합니다.
징후
namespaces "x" not foundserviceaccount "x" not found- 이미지 풀 시크릿이 없어
ImagePullBackOff로 이어지기도 함
해결
- App을 분리하거나
sync-wave로 순서를 명시 - 의존 리소스는 한 번 더 재사용 가능한 플랫폼 레이어(App of Apps)로 올리기
- App을 분리하거나
헬스체크(health)에서 자주 터지는 원인 4가지
6) Deployment는 적용됐는데 파드가 Ready가 안 됨(Probe/Config)
가장 흔한 Degraded 원인입니다. Argo CD는 Deployment 의 Available 상태, ProgressDeadlineSeconds, 파드 readiness 등을 근거로 health를 판단합니다.
징후
Synced지만Degraded- 이벤트에
Readiness probe failed,Liveness probe failed - 컨테이너 로그에 환경 변수 누락, 설정 파일 누락
해결
- readiness/liveness probe를 애플리케이션 부팅 시간에 맞게 조정
startupProbe를 적극 사용해 초기 기동 구간을 분리
예시:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /startup
port: 8080
failureThreshold: 30
periodSeconds: 5
7) ImagePullBackOff: 레지스트리 인증/권한/태그 문제
Git 선언은 맞지만 이미지가 내려받히지 않으면 health는 계속 나빠집니다.
징후
- 파드 상태가
ImagePullBackOff,ErrImagePull - 이벤트에
pull access denied혹은401류
- 파드 상태가
해결
imagePullSecrets가 올바른 네임스페이스에 있는지 확인- 태그 존재 여부, 레지스트리 접근 네트워크(프라이빗 엔드포인트, 방화벽) 확인
진단:
kubectl -n my-ns describe pod my-pod
kubectl -n my-ns get secret | grep regcred
8) 잡(Job)이나 훅(Hook)이 실패해서 전체 Sync가 실패
Argo CD PreSync/PostSync 훅을 Job으로 걸어두면, 메인 리소스는 정상인데도 훅 실패로 Sync failed 가 됩니다.
징후
- UI에서 Hook 리소스가 빨갛게 실패
- Job 파드 로그에 마이그레이션 실패, 권한 부족, 외부 의존성 실패
해결
- 훅 Job은 반드시 재시도/타임아웃/실패 전략을 명확히
- 외부 의존성이 큰 작업은 배포 파이프라인 단계로 빼고, 클러스터 내 훅은 최소화
예시(훅 어노테이션):
metadata:
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
9) Argo CD 기본 health 계산이 워크로드 특성과 맞지 않음
일부 CR(예: 오퍼레이터가 만드는 커스텀 리소스)은 Argo CD가 health를 제대로 판단하지 못해 계속 Progressing 으로 남을 수 있습니다. 혹은 Ingress/Service 는 적용됐지만 실제 트래픽 가능 상태와 health가 괴리될 수 있습니다.
징후
- 리소스는 정상인데 Argo CD만
Progressing - 특정 Kind에서만 반복
- 리소스는 정상인데 Argo CD만
해결
argocd-cm의resource.customizations로 health 체크 Lua 스크립트 커스터마이징- 혹은 해당 리소스는 health 평가에서 제외하거나(조심), 상위 리소스(Deployment 등)로 상태를 대표하게 구성
예시(argocd-cm 에 커스텀 health):
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
resource.customizations.health.mygroup.io_MyKind: |
hs = {}
if obj.status ~= nil and obj.status.phase == "Ready" then
hs.status = "Healthy"
hs.message = "Ready"
else
hs.status = "Progressing"
hs.message = "Waiting for Ready"
end
return hs
운영에서 바로 쓰는 진단 체크리스트
아래 순서로 보면 원인 범위를 빠르게 줄일 수 있습니다.
- App 상태 확인:
OutOfSync인지Degraded인지 argocd app diff로 차이가 "계속" 발생하는지 확인- 실패한 리소스 1개를 잡고
kubectl describe와 이벤트 확인 - Webhook 거부 여부 확인(에러 메시지에
admission webhook포함 여부) - CRD/CR 순서 문제인지 확인(Kind 매칭 에러, sync wave)
- Job/Hook 실패 여부 확인
- 마지막으로 health 커스터마이징 필요 여부 판단
추가로, "원인은 하나" 라고 가정하면 놓치는 경우가 많습니다. 예를 들어 diff 단계에서 Secret이 먼저 없어서 실패했고, 이를 고친 뒤에는 파드가 ImagePullBackOff 로 health에서 다시 실패하는 식으로 연쇄가 발생합니다. 이런 연쇄 장애의 디버깅 패턴은 권한/정책/환경 차이를 빠르게 좁히는 접근이 중요한데, 비슷한 방식으로 403 권한 문제를 쪼개는 글로 GitHub Actions GITHUB_TOKEN 403 권한 오류 해결도 참고할 만합니다.
마무리: 재발 방지 설계 포인트
- 선언형 관리의 단일 진실 공급원(SoT)을 지키기
- 같은 리소스를 Helm과
kubectl apply로 동시에 만지지 않기
- 같은 리소스를 Helm과
- 플랫폼 레이어와 앱 레이어 분리
- Namespace/RBAC/CRD 같은 기반 리소스는 별도 App으로 관리
- diff 노이즈는 원인 제거가 우선, 불가피할 때만
ignoreDifferences - health는 "실서비스 가능" 과 연결되도록 probe와 커스텀 health를 정교화
Argo CD sync 실패는 결국 "비교가 안 맞는다" 또는 "적용은 됐지만 정상 상태가 아니다" 둘 중 하나로 귀결됩니다. 위 9가지를 기준으로 증상을 분류하고, 로그와 이벤트를 같은 축에서 보면 대부분의 케이스는 짧은 시간 안에 수습 가능합니다.