Published on

Argo CD Sync 실패? AppProject RBAC로 해결하기

Authors

Argo CD를 운영하다 보면 애플리케이션 자체 YAML은 멀쩡한데도 Sync가 실패하는 순간이 있습니다. 특히 멀티 테넌시로 프로젝트를 나눠 쓰거나, 팀별로 AppProject를 분리한 환경에서는 AppProject RBAC 설정이 원인인 경우가 매우 많습니다.

이 글은 다음 상황을 빠르게 해결하는 데 초점을 둡니다.

  • Argo CD UI에서 Sync 버튼을 눌렀는데 permission denied 류 메시지로 실패
  • 특정 네임스페이스로 배포만 막히거나, 특정 리소스(예: ClusterRole, IngressClass, CRD)만 생성 실패
  • 같은 리포지토리인데 프로젝트만 바꾸면 성공/실패가 갈림

운영 트러블슈팅 관점은 다른 글에서도 비슷한 패턴이 있습니다. 예를 들어 “겉으로는 정상인데 특정 경로만 503” 같은 현상은 원인 분리가 핵심입니다. 네트워크 계열이지만 접근 방식은 유사하니 참고로 함께 보면 좋습니다: EKS Ingress 503인데 Pod 정상일 때 점검 가이드

Sync 실패를 만드는 AppProject의 3가지 관문

Argo CD에서 AppProject는 단순한 “폴더”가 아니라, 애플리케이션이 할 수 있는 행동을 제한하는 정책 집합입니다. Sync 시에는 크게 아래 3개의 관문을 통과해야 합니다.

  1. sourceRepos: 어떤 Git 리포지토리에서 manifest를 가져올 수 있는가

  2. destinations: 어느 클러스터, 어느 네임스페이스로 배포할 수 있는가

  3. resource whitelist/blacklist: 어떤 Kubernetes 리소스를 만들거나 수정할 수 있는가

여기에 “누가 Sync 버튼을 누를 수 있는가”를 결정하는 Project RBAC 까지 합쳐지면, 겉보기에는 비슷한 에러로 보이지만 실제 원인은 완전히 달라집니다.

증상별로 원인 좁히기: 에러 메시지 패턴

Sync 실패 화면에서 자주 보이는 패턴은 다음과 같습니다.

1) 리포지토리 접근 거부

  • 메시지 예시(유사): repository not permitted in project

대부분 AppProject.spec.sourceRepos에 해당 리포지토리가 허용되지 않았습니다.

2) 대상 클러스터 또는 네임스페이스 거부

  • 메시지 예시(유사): application destination is not permitted

대부분 AppProject.spec.destinations에 클러스터 서버 주소 또는 네임스페이스가 매칭되지 않습니다.

3) 특정 리소스만 생성/패치 실패

  • 메시지 예시(유사): resource ... is not permitted in project

대부분 clusterResourceWhitelist 또는 namespaceResourceWhitelist가 막고 있습니다.

4) UI에서 Sync 자체가 막힘

  • 메시지 예시(유사): permission denied 혹은 버튼이 비활성화

대부분 Project RBAC 또는 Argo CD 전역 RBAC이 원인입니다. “리소스는 허용되는데 사용자가 Sync를 못 누르는” 케이스가 여기에 해당합니다.

필수 점검 1: Application이 어떤 Project를 쓰는지 확인

가장 먼저 Application.spec.project가 의도한 프로젝트인지 확인합니다.

kubectl -n argocd get app my-app -o jsonpath='{.spec.project}'

프로젝트가 다르면, 동일한 manifest라도 허용 정책이 달라져 Sync 성공/실패가 갈립니다.

필수 점검 2: AppProject의 sourceRepos, destinations

아래는 “팀 A는 특정 Git 리포지토리와 dev 네임스페이스만 배포 가능” 같은 전형적인 AppProject 예시입니다.

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-a
  namespace: argocd
spec:
  description: Team A project

  sourceRepos:
    - 'https://github.com/my-org/team-a-manifests.git'

  destinations:
    - server: 'https://kubernetes.default.svc'
      namespace: 'team-a-dev'

  clusterResourceWhitelist:
    - group: ''
      kind: Namespace

  namespaceResourceWhitelist:
    - group: 'apps'
      kind: Deployment
    - group: ''
      kind: Service
    - group: 'networking.k8s.io'
      kind: Ingress

여기서 자주 터지는 실수는 다음과 같습니다.

  • destinations.server 값이 실제 Application의 destination server와 불일치
  • 네임스페이스를 team-a-*처럼 패턴으로 쓰고 싶은데 명시적으로만 적어둠
  • Helm/Kustomize가 내부적으로 만드는 리소스(예: ConfigMap, Secret)를 whitelist에 누락

필수 점검 3: 리소스 whitelist가 막는 케이스(가장 흔함)

멀티 테넌시 환경에서 특히 자주 막히는 리소스는 아래입니다.

  • CustomResourceDefinition (CRD)
  • ClusterRole, ClusterRoleBinding
  • IngressClass
  • MutatingWebhookConfiguration, ValidatingWebhookConfiguration
  • Namespace (네임스페이스를 Argo CD로 만들게 할지 정책 결정 필요)

예를 들어, 어떤 차트가 CRD를 포함하고 있는데 프로젝트가 CRD를 허용하지 않으면 Sync 중간에 실패합니다.

다음은 “네임스페이스 리소스는 제한하지만, 일부 클러스터 리소스는 허용”하는 예시입니다.

spec:
  clusterResourceWhitelist:
    - group: 'apiextensions.k8s.io'
      kind: CustomResourceDefinition
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRole
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRoleBinding

  namespaceResourceWhitelist:
    - group: ''
      kind: ConfigMap
    - group: ''
      kind: Secret
    - group: ''
      kind: Service
    - group: 'apps'
      kind: Deployment
    - group: 'networking.k8s.io'
      kind: Ingress

운영 팁:

  • “차트가 무엇을 만들지”는 설치 전에 helm template 결과로 미리 확인하는 습관이 좋습니다.
  • 팀에 클러스터 권한을 주기 싫다면, CRD는 플랫폼 팀 프로젝트에서만 관리하고 앱 팀 프로젝트에서는 CRD를 금지하는 식으로 분리합니다.

Project RBAC: Sync 버튼이 안 눌리는 진짜 이유

AppProject는 “리소스 허용 정책”만 있는 게 아니라, 프로젝트 레벨 RBAC도 가질 수 있습니다. 이때 핵심은 rolespolicies 입니다.

아래는 team-a 프로젝트에서 team-a-devs 그룹에게 애플리케이션 Sync 권한을 주는 예시입니다.

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-a
  namespace: argocd
spec:
  roles:
    - name: devs
      description: Team A developers
      groups:
        - team-a-devs
      policies:
        - 'p, proj:team-a:devs, applications, get, team-a/*, allow'
        - 'p, proj:team-a:devs, applications, sync, team-a/*, allow'
        - 'p, proj:team-a:devs, applications, action/apps/Deployment/restart, team-a/*, allow'

여기서 실수 포인트:

  • 정책의 대상이 team-a/* 형태로 되어 있는데, 실제 Application 이름 또는 네임스페이스 스코프가 다르면 매칭이 안 됩니다.
  • SSO 그룹 이름이 Argo CD에 들어오는 값과 다릅니다. 예를 들어 IdP에서는 team-a-devs인데 Argo CD에서는 접두사가 붙거나 클레임 경로가 달라질 수 있습니다.

“리소스 권한”과 “클러스터 권한”을 혼동하지 말기

Argo CD Sync 실패는 AppProject 정책만의 문제가 아닐 수 있습니다.

  • AppProject가 허용했는데도 실패한다면, 다음 레이어는 “Argo CD가 클러스터에서 실제로 권한이 있는가” 입니다.
  • 즉, argocd-application-controller가 사용하는 ServiceAccount의 Kubernetes RBAC(ClusterRole/RoleBinding)가 부족할 수 있습니다.

구분 방법은 간단합니다.

  • AppProject에 막히면 “not permitted in project” 류가 많이 뜹니다.
  • Kubernetes RBAC가 막히면 “forbidden: User system:serviceaccount:...” 류가 많이 뜹니다.

이처럼 레이어를 분리해서 보는 습관은 IaC에서도 동일하게 중요합니다. 예를 들어 apply가 성공한 것처럼 보이는데 실제 반영이 안 되는 케이스는 drift나 정책 레이어를 의심해야 합니다: Terraform apply 후 Azure NSG 규칙이 안 바뀌는 이유

실전 트러블슈팅 체크리스트(순서대로)

1) Application이 가리키는 Project 확인

kubectl -n argocd get app my-app -o yaml | sed -n '1,120p'

spec.project, spec.destination(server/namespace), spec.source.repoURL을 함께 봅니다.

2) AppProject의 허용 범위 확인

kubectl -n argocd get appproject team-a -o yaml
  • sourceRepos에 repoURL이 포함되는가
  • destinations에 server/namespace가 포함되는가
  • whitelist에 필요한 리소스가 들어있는가

3) 실패한 리소스가 “클러스터 스코프”인지 확인

예: CRD, ClusterRole 같은 리소스는 네임스페이스 whitelist가 아니라 clusterResourceWhitelist에 있어야 합니다.

4) Project RBAC로 Sync 권한 확인

  • SSO 그룹이 제대로 매핑되는지
  • 정책에 applications, sync가 있는지
  • 대상 패턴이 실제 앱 이름과 매칭되는지

5) 마지막으로 Kubernetes RBAC 확인

에러에 system:serviceaccount:가 보이면 AppProject가 아니라 클러스터 RBAC 문제일 가능성이 큽니다.

재발 방지: 프로젝트 템플릿과 최소 권한 설계

운영에서 가장 좋은 해결은 “한 번 고치고 끝”이 아니라, 같은 유형의 Sync 실패를 구조적으로 줄이는 것입니다.

  • 팀 프로젝트 템플릿을 만들어 sourceRepos, destinations, whitelist 기본값을 표준화
  • 클러스터 스코프 리소스는 플랫폼 프로젝트에서만 관리하도록 분리
  • Project RBAC 정책은 최소 권한으로 시작해서, 필요한 action만 점진적으로 추가

GitOps 환경에서는 권한 설계가 곧 배포 안정성입니다. 권한이 과하면 사고 반경이 커지고, 권한이 부족하면 배포가 멈춥니다. AppProject RBAC는 그 균형을 잡는 핵심 도구이니, “Sync 실패를 고치는 설정”이 아니라 “팀 운영 모델을 코드로 고정하는 장치”로 보는 것이 좋습니다.

부록: 자주 쓰는 정책 패턴 모음

특정 네임스페이스만 허용

spec:
  destinations:
    - server: 'https://kubernetes.default.svc'
      namespace: 'team-a-dev'
    - server: 'https://kubernetes.default.svc'
      namespace: 'team-a-prod'

모든 네임스페이스 허용(권장하지 않음)

spec:
  destinations:
    - server: 'https://kubernetes.default.svc'
      namespace: '*'

프로젝트 내 모든 앱에 Sync 허용

spec:
  roles:
    - name: devs
      groups:
        - team-a-devs
      policies:
        - 'p, proj:team-a:devs, applications, get, team-a/*, allow'
        - 'p, proj:team-a:devs, applications, sync, team-a/*, allow'

필요한 리소스와 권한을 “어떤 팀이 어떤 범위까지 배포할 수 있는가” 관점에서 다시 정리하면, 대부분의 Argo CD Sync 실패는 AppProject 단계에서 깔끔하게 해결됩니다.