Published on

Argo CD Sync 실패 - OutOfSync·Health Degraded 9가지

Authors
Binance registration banner

서버 운영에서 Argo CD는 “원하는 상태(Desired)”와 “실제 상태(Live)”를 맞추는 도구입니다. 그런데 어느 날부터 OutOfSync가 계속 남거나, Sync는 끝났는데 Health Degraded가 떠서 배포가 멈춘 경험이 흔합니다. 더 난감한 점은 원인이 한 가지가 아니라는 겁니다.

이 글은 현장에서 반복적으로 만나는 Sync 실패/OutOfSync/Degraded 케이스를 9가지로 분류하고, 각 케이스별로 어떤 신호로 구분하는지, 어떤 커맨드로 확인하는지, 어떻게 고치는지를 빠르게 정리합니다.

참고: 이미지 풀/권한 문제로 Pod가 못 뜨는 케이스는 ImagePullBackOff로도 이어집니다. EKS라면 이 글도 같이 보세요: EKS Pod ImagePullBackOff 401 해결 가이드

먼저 공통 진단 루틴(5분 컷)

문제 유형을 가르기 전에 아래 3가지를 먼저 확보하면 대부분의 케이스가 빨리 풀립니다.

1) Argo CD 앱 상태/이벤트 확인

argocd app get my-app
argocd app diff my-app
argocd app history my-app
argocd app logs my-app

argocd app diff에서 어떤 리소스가 drift(차이) 나는지, argocd app get에서 어떤 리소스가 Degraded인지 먼저 좁힙니다.

2) Kubernetes 이벤트와 실패 지점 확인

kubectl -n my-ns get events --sort-by=.lastTimestamp | tail -n 50
kubectl -n my-ns get pods -o wide
kubectl -n my-ns describe pod my-pod
kubectl -n my-ns logs my-pod --all-containers --tail=200

Sync 자체가 실패한 건지(Apply 실패), Apply는 됐는데 워크로드가 정상화되지 않은 건지(Health Degraded)를 분리합니다.

3) Argo CD 컨트롤러 로그(권한/연결/리포지토리)

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
kubectl -n argocd logs deploy/argocd-server --tail=200

리포지토리 접근/Helm 렌더링/권한 부족은 대개 여기서 단서가 나옵니다.


1) Git에는 변경이 없는데 OutOfSync: 웹훅/폴링/Revision 불일치

증상

  • Git에는 최신인데 Argo CD UI는 예전 revision을 바라보거나 OutOfSync가 남음
  • argocd app get에서 Sync StatusOutOfSync, Target revision이 기대와 다름

진단

argocd app get my-app | sed -n '1,120p'
argocd app diff my-app

또는 repo-server에서 fetch 에러가 있는지 확인합니다.

해결

  • Webhook이 막혀서 갱신이 안 되는 경우: Git provider에서 Webhook delivery 로그 확인
  • 폴링 주기를 짧게: (운영 정책에 따라) Argo CD repo refresh 주기 조정
  • 강제 리프레시:
argocd app get my-app --refresh
argocd app sync my-app
  • 멀티 브랜치/태그를 쓰는 경우 targetRevision이 실제로 존재하는지 확인

2) 리소스 필드가 계속 되돌아가며 OutOfSync: Mutating Webhook/컨트롤러가 덮어씀

증상

  • Sync 직후 잠깐 Synced였다가 다시 OutOfSync
  • diff를 보면 특정 필드가 매번 바뀜(예: labels/annotations, env, securityContext)

진단

argocd app diff my-app

특히 admission(OPA/Gatekeeper, Kyverno), Service Mesh(sidecar injection), 보안 에이전트가 리소스를 변형하는 경우가 많습니다.

해결

  • 변형되는 필드를 Argo CD에서 무시(ignoreDifferences)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/template/metadata/annotations
  • 또는 변형을 유발하는 webhook의 정책을 해당 네임스페이스/리소스에 대해 예외 처리

“왜 특정 필드가 변경됐는지”를 추적할 때는, 결국 누가 권한을 갖고 바꿨는지(Admission/Controller/RBAC)로 귀결됩니다. 권한 추적 관점은 이 글이 사고방식에 도움이 됩니다: AWS IAM AccessDenied 403, 정책 시뮬레이터로 추적


3) Sync 실패(Apply 에러): CRD 미설치 또는 API 버전 불일치

증상

  • Sync 단계에서 바로 실패
  • 메시지 예: no matches for kind ... 또는 the server could not find the requested resource

진단

argocd app sync my-app
# 또는 UI의 Sync 실패 이벤트 메시지 확인

kubectl api-resources | grep -i mycrd
kubectl get crd | grep -i mycrd

해결

  • CRD를 먼저 배포(선행 앱으로 분리하거나 sync wave 사용)
  • Helm chart가 오래되어 apiVersion이 클러스터 버전과 안 맞는 경우 차트 업그레이드
  • Argo CD에서 SkipDryRunOnMissingResource를 임시로 쓰는 방법도 있지만, 근본은 CRD 선행 배포입니다.

예시(동일 앱에서 wave로 CRD 먼저):

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: widgets.example.com
  annotations:
    argocd.argoproj.io/sync-wave: "-1"

4) Sync 실패: RBAC/권한 부족으로 리소스 생성·패치 불가

증상

  • Sync 실패 메시지에 forbidden 포함
  • 특정 네임스페이스/클러스터 리소스에서만 실패

진단

# Argo CD가 사용하는 ServiceAccount 확인(설치 방식에 따라 다름)
kubectl -n argocd get sa

# 실제 에러는 application-controller 로그가 더 정확한 경우가 많음
kubectl -n argocd logs deploy/argocd-application-controller --tail=200 | grep -i forbidden

해결

  • Argo CD가 관리해야 하는 리소스(예: ClusterRole, IngressClass, CRD)가 있다면 적절한 권한 부여
  • 멀티테넌시라면 프로젝트(Project) 레벨의 허용 리소스/대상 네임스페이스 설정도 확인

권한을 무턱대고 넓히기보다, “어떤 리소스에 어떤 verb가 필요한지”를 에러 메시지 기반으로 최소화합니다.


5) OutOfSync가 계속: 서버 사이드 필드/기본값(defaulting) 차이

증상

  • Git에 없는 필드가 Live에 생기며 diff 발생
  • 주로 spec.replicas, strategy, resources, terminationMessagePolicy 같은 기본값

진단

argocd app diff my-app

해결

  • 기본값이 들어가도 상관없다면 ignoreDifferences로 무시
  • 반대로 기본값까지 고정하고 싶다면 매니페스트에 명시적으로 넣어 drift를 제거

예시(명시적으로 기본값을 선언해 drift 제거):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%

6) Health Degraded: Pod가 안 뜸(ImagePullBackOff, CrashLoopBackOff, Pending)

증상

  • Sync는 성공했는데 앱 Health가 Degraded
  • Pod 상태가 ImagePullBackOff, CrashLoopBackOff, Pending

진단

kubectl -n my-ns get pods
kubectl -n my-ns describe pod my-pod
kubectl -n my-ns logs my-pod --all-containers --tail=200

원인별 힌트:

  • ImagePullBackOff: 레지스트리 인증/이미지 태그 오타/권한
  • CrashLoopBackOff: 앱 설정값/시크릿 누락/마이그레이션 실패
  • Pending: 리소스 부족, 노드 셀렉터/테인트, PVC 바인딩 실패

해결

  • ImagePull 관련은 환경별로 처방이 다릅니다. EKS라면 아래 글이 바로 도움이 됩니다.
  • CrashLoop은 보통 ConfigMap/Secret 누락과 함께 옵니다. envFrom, volumeMounts를 우선 확인하세요.

7) Health Degraded: Ingress/ALB/외부 연동 이슈로 Readiness 실패

증상

  • Pod는 Running인데 서비스가 안 열리고 Degraded
  • Readiness probe가 실패하거나, Ingress 컨트롤러 이벤트에 에러가 찍힘

진단

kubectl -n my-ns describe deploy api
kubectl -n my-ns get ingress
kubectl -n my-ns describe ingress my-ing
kubectl -n my-ns get events --sort-by=.lastTimestamp | tail -n 50

ALB Ingress를 쓰면 타겟 그룹 헬스체크/보안그룹/경로 규칙 때문에 readiness와 비슷한 장애가 자주 납니다.

해결

  • readiness/liveness probe 경로, 포트, 초기 지연 시간 재점검
  • Ingress 컨트롤러(예: AWS Load Balancer Controller) 이벤트에서 “왜 타겟이 unhealthy인지”를 먼저 확인

관련 케이스를 더 깊게 보려면: EKS에서 ALB Ingress 408 Request Timeout 해결 가이드


8) Sync 실패 또는 OutOfSync: Kustomize/Helm 렌더링 실패(값/템플릿/플러그인)

증상

  • UI에서 ComparisonError 또는 Failed to load target state
  • repo-server 로그에 Helm/Kustomize 에러

진단

kubectl -n argocd logs deploy/argocd-repo-server --tail=200

로컬에서도 동일 렌더링을 재현해보면 빠릅니다.

Helm 예:

helm template myrel ./chart -n my-ns -f values.yaml

Kustomize 예:

kustomize build overlays/prod

해결

  • values 파일 누락/오타, required 템플릿 함수로 인한 실패 수정
  • Argo CD에서 허용되지 않는 플러그인/바이너리 의존성이 있다면 repo-server 커스텀 이미지 또는 ConfigManagementPlugin 설정 정비
  • Chart 의존성 다운로드 이슈면 helm dependency build가 필요한 구조인지 확인

9) OutOfSync인데 지워지지 않음: Finalizer/삭제 대기, 리소스가 Terminating에 걸림

증상

  • 리소스 삭제/교체 과정에서 Sync가 계속 꼬임
  • 네임스페이스 또는 특정 CR이 Terminating에서 멈춤

진단

kubectl -n my-ns get all
kubectl -n my-ns get <resource> <name> -o yaml | sed -n '1,200p'

여기서 metadata.finalizers가 남아 있고, 해당 컨트롤러가 이미 죽었거나 외부 시스템 정리가 안 되면 영원히 대기합니다.

해결

  • 컨트롤러가 살아있는지(예: ingress controller, external-dns, cert-manager) 확인 후 정상 경로로 삭제
  • 정말로 막혀있다면 finalizer를 제거(주의: 외부 리소스가 orphan 될 수 있음)
kubectl -n my-ns patch <kind> <name> -p '{"metadata":{"finalizers":[]}}' --type=merge

<kind><name>은 실제 리소스에 맞게 바꿔야 하며, 위 커맨드의 부등호 문자는 반드시 인라인 코드로 다뤄야 합니다.


운영에서 재발을 줄이는 설정 팁

Sync 옵션을 목적에 맞게

  • 자동 동기화가 필요한 앱: drift를 빠르게 줄이되, 변형 리소스는 ignoreDifferences로 통제
  • 삭제까지 Git이 권위가 되게: prune 사용(단, 실수 방지를 위해 보호 장치 필요)

예시:

spec:
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

“OutOfSync”와 “Degraded”를 분리해서 온콜 대응

  • OutOfSync: 선언 상태와 실제 상태의 차이(대개 Git/렌더링/변형/권한/순서)
  • Degraded: 리소스는 적용됐지만 워크로드가 정상 동작하지 않음(대개 Pod/네트워크/스토리지/외부 연동)

이 기준으로 먼저 분류하면, 같은 알람이라도 담당자가 바로 올바른 로그로 들어갈 수 있습니다.


마무리 체크리스트(요약)

  • argocd app diff로 drift 리소스를 먼저 특정
  • Apply 실패면 application-controller 로그에서 forbidden, no matches for kind, 렌더링 에러를 찾기
  • Apply 성공인데 Degradedkubectl describe pod와 이벤트로 ImagePullBackOff, CrashLoopBackOff, Pending, readiness 실패를 먼저 분리
  • 반복 drift면 webhook/컨트롤러 변형을 의심하고 ignoreDifferences 또는 정책 예외로 해결
  • 삭제/교체가 걸리면 finalizer와 Terminating 리소스를 점검

위 9가지는 대부분의 Argo CD Sync 장애를 커버합니다. 만약 argocd app diff 결과(민감정보 제거)와 repo-server 또는 application-controller 로그의 핵심 에러 라인을 공유할 수 있다면, 케이스를 더 정확히 좁혀서 재발 방지까지 포함한 처방으로 정리할 수 있습니다.