1. 개요

Argo CD 는 Git 에 적힌 쿠버네티스 객체(YAML)를 읽어 클러스터의 실제 상태와 비교하고, 다르면 Git 쪽 내용으로 맞추는 도구다. 사람이 kubectl apply 하던 일을 클러스터 안의 프로그램이 대신 하고, 무엇이 배포돼야 하는지의 기준을 Git 에 둔다. 이 방식을 GitOps 라고 부른다.

kubectl -n argocd get applications     # Argo CD 가 관리하는 연결 목록
argocd app diff kukie-dev-apps         # Git 과 클러스터의 차이
argocd app sync kukie-dev-apps         # Git 쪽으로 맞추기
deployment.yamlservice.yamlArgo CDDeploymentService① 읽기③ 다르면 적용② 실제 상태 조회 · 비교Git — 원하는 상태클러스터 — 실제 상태① 정해진 주기(기본 최대 3분)마다 Git 을 읽어 YAML 을 렌더링한다② 같은 객체를 클러스터에서 읽어 두 쪽을 비교한다 — 같으면 Synced, 다르면 OutOfSync③ 동기화하면 Git 쪽 내용으로 클러스터에 적용한다 (수동 또는 자동)Argo CD 는 kubectl apply 를 대신 하는 쪽이다 — 기준은 언제나 Git 이다

2. 구성 요소

Argo CD 자체도 클러스터 안의 Pod 몇 개다. 보통 argocd 네임스페이스에 설치한다.

argocd 네임스페이스 — 보라 상자 하나하나가 Pod (Argo CD 도 같은 클러스터 안에서 돈다)사람웹 UI · argocd CLIGit레포 · Helm 저장소클러스터API 서버argocd-serverUI · API 창구argocd-repo-server가져와 렌더링application-controller비교 · 적용 (주체)argocd-redis캐시보기 · SynccloneSync 요청렌더링한 YAML검은 화살표 적용 · 파랑 화살표 읽기·비교렌더링 결과 · 클러스터 상태선택 설치 — 켜면 같은 네임스페이스에 Pod 로 더 뜬다applicationset-controllerApplicationSet → Application 생성dexSSO 로그인 (argocd-server 앞)notifications상태 변화 → Slack · 메일 알림클러스터에 객체를 넣는 건 application-controller 하나다나머지는 보여 주고(server), 준비하고(repo-server), 기억한다(redis)

application-controller 는 넣을 수 있는 권한이 넓다(보통 클러스터 관리자급). 그래서 “무엇을 넣어도 되는가” 를 Argo CD 안에서 다시 좁히는 장치가 5절의 AppProject 다.

3. 추가하는 리소스 종류

원래 쿠버네티스에는 Application 도 프로젝트도 없다. Argo CD 를 설치하면 CRD 로 아래 세 종류가 API 에 추가되고, Argo CD 를 지우면 함께 사라진다.

Applicationkind: Applicationmetadata:name: kukie-dev-appsspec:project: kukie-apps← 이 울타리를 따른다source: …어디서destination: …어디로AppProject (별개의 객체)kind: AppProjectmetadata:name: kukie-apps← 같은 이름spec:sourceRepos: […]울타리 내용destinations: […]…Whitelist: […]이름으로ApplicationSetApplication 을 찍어 내는 틀만든다 (선택)셋 다 apiVersion argoproj.io/v1alpha1 — 쿠버네티스에는 원래 없고,Argo CD 를 지우면 같이 사라진다Application 이 AppProject 안에 들어 있는 게 아니다 — 같은 이름 한 줄로 이어진다
kubectl api-resources --api-group=argoproj.io
# applications     app,apps     argoproj.io/v1alpha1   true   Application
# applicationsets  appset       argoproj.io/v1alpha1   true   ApplicationSet
# appprojects      appproj      argoproj.io/v1alpha1   true   AppProject

세 종류의 객체는 Argo CD 가 설치된 네임스페이스(argocd)에 둔다. 다른 네임스페이스에 두려면 따로 켜야 한다.

4. Application

Application 은 Argo CD 가 관리하는 단위다. “이 레포의 이 폴더를 저 클러스터의 이 네임스페이스에” 를 적는다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: kukie-dev-apps
  namespace: argocd
spec:
  project: kukie-apps              # 속한 AppProject 이름
  source:
    repoURL: https://github.com/durokukie/kukie-manifest.git
    targetRevision: main
    path: envs/dev
  destination:
    server: https://kubernetes.default.svc   # Argo CD 가 설치된 그 클러스터
    namespace: kukie-dev
envs/dev/ApplicationAppProjectsourcedestination객체들네임스페이스source — repoURL(레포) · path(폴더) · targetRevision(브랜치·태그·커밋)destination — server(클러스터 주소) · namespaceproject — 이 앱이 속한 AppProject 이름 한 줄 (점선: 이름으로만 이어진다)Application 은 '어디서 → 어디로' 를 적은 연결선 하나다

4.1. project 칸

  • Application 하나는 AppProject 하나에만 속한다. 한 AppProject 에는 Application 이 여러 개 속할 수 있다
  • 둘은 포함 관계가 아니라 나란히 있는 별개의 객체다. Application 이 project: 에 이름을 적어 가리킬 뿐이다
  • 비워 두면 default 프로젝트에 속한다 (5.2. 참고)

4.2. 상태 표시

Sync — Git 렌더링 결과와 클러스터가 같은가Synced같다OutOfSync다르다 → 동기화 대상Health — 넣은 객체가 정상으로 떠 있는가Healthy정상Progressing뜨는 중 (롤링 등)Degraded실패 · 반복 재시작Missing클러스터에 아직 없음어디에 보이나 — 원본은 Application 객체의 status 칸 하나, 보는 창구가 셋status:sync:status: OutOfSynchealth:status: Missing① Application 객체 (controller 가 적음)$ kubectl -n argocd get appNAME SYNC STATUS HEALTHkukie-dev-apps OutOfSync Missingreloader Synced Healthy② kubectl · argocd app getkukie-dev-appsMissingOutOfSyncproject: kukie-apps③ Argo CD 웹 UI 앱 카드두 칸은 따로 움직인다 — 처음 연결한 수동 동기화 앱은 OutOfSync · Missing (읽고 비교만 하고 아직 넣지 않음)

두 값은 application-controller 가 Application 객체의 status 칸에 적고, 웹 UI · kubectl get applications · argocd app get 은 모두 그 칸을 읽어 보여 준다. 수동 동기화인 Application 은 Git 을 읽고 차이를 계산만 한다. 처음 연결했을 때 OutOfSync · Missing 으로 보이는 것은 아직 아무것도 넣지 않았다는 뜻이다.

5. AppProject

AppProject 는 그 프로젝트에 속한 Application 이 어디서 가져와 어디에 무엇을 넣을 수 있는지의 한계선이다. Application 이 Git 에 뭐라고 적든 이 선 밖이면 Argo CD 가 동기화를 거부한다.

DeploymentServiceIngressExternalSecretkukie-dev 네임스페이스 (destinations)울타리 = AppProject kukie-appskukie-manifestsourceReposClusterRole✕kube-system 행✕다른 레포✕초록 벽 — AppProject kukie-apps. 주황 레포에서 읽어 파랑 네임스페이스 안에, 허용한 종류만 넣는다빨강 점선 — 울타리 밖. 클러스터 전체 단위 종류 · 다른 네임스페이스 · 다른 레포울타리 밖은 Git 에 적혀 있어도 들어가지 않는다 — Argo CD 가 동기화를 거부한다

5.1. 제한 항목

AppProject — kukie-infra argocd/dev/platform/templates/kukie-apps.yamlkind: AppProjectmetadata:name: kukie-apps← Application 의 project: 가 가리키는 이름namespace: argocdspec:sourceRepos:어디서 — 읽어도 되는 레포- https://github.com/durokukie/kukie-manifest.gitdestinations:어디로 — kukie-dev 네임스페이스만- server: https://kubernetes.default.svcnamespace: kukie-devclusterResourceWhitelist: []클러스터 단위 종류는 하나도 안 됨namespaceResourceWhitelist:무엇을 — 이 여섯 종류만- {group: apps, kind: Deployment}- {group: "", kind: Service}- {group: "", kind: ConfigMap}- {group: networking.k8s.io, kind: Ingress}- {group: external-secrets.io, kind: ExternalSecret}- {group: external-secrets.io, kind: SecretStore}Secret · Role · Application · AppProject 는 목록에 없다Secret 은 ESO 가 ExternalSecret 을 보고 만들고, 권한과 Argo CD 객체는 앱 레포에서 넣지 못한다kukie-dev-apps 가 이 이름을 고르므로, 네 칸(어디서 · 어디로 · 금지 · 허용)이 그 앱의 한계선이다

동기화할 때는 이 항목들을 아래 순서로 검사한다.

Application동기화 요청sourceRepos읽을 레포destinations클러스터 · 네임스페이스clusterResource클러스터 단위 종류namespaceResource네임스페이스 안 종류적용✕ 거부✕ 거부✕ 거부✕ 거부어디서 → 어디로 → 무엇을 (하나라도 걸리면 동기화 실패) · 뒤의 둘은 …Whitelist / …Blacklistroles사람 쪽 — 누가 이 프로젝트의 앱을 보고 Sync 하나← 객체 검사와 별개로, Argo CD 사용자 권한을 나눈다앞의 넷은 '무엇이 들어가나' 를, roles 는 '누가 누르나' 를 막는다

5.2. default 프로젝트

Argo CD 를 설치하면 default 프로젝트가 자동으로 생긴다. 모든 레포 · 모든 클러스터 · 모든 종류를 허용하는(*) 사실상 울타리가 없는 프로젝트다. 고칠 수는 있지만 지울 수는 없다. 그래서 운영에서는 용도별 프로젝트를 따로 만들고 Application 이 default 를 쓰지 않게 한다.

5.3. 쿠버네티스 기본 장치와의 차이

주문서project: kukie-apps주문서project: kukie-platform앱 팀 · kukie-manifest인프라 · kukie-infra규칙표 kukie-apps레포 kukie-manifest 만방 kukie-dev 만가구 Deployment · Service …규칙표 kukie-platform레포 kukie-infra · 정해진 차트방 kube-system · kukie-dev …가구 컨트롤러 설치용 전부AppProject — 소속별로 먼저 확인Argo CD마스터키 계정 하나RBAC같은 계정이라 다 통과kukie-dev 방member · agent · webkube-system 방LBC 같은 클러스터 공용① 앱 → kukie-dev② 컨트롤러 → kube-system✕③ kukie-apps 주문이 kube-system 에 넣으려 하면규칙표에서 거부 — 마스터키까지 가지 않는다RBAC 은 '누가 왔나' 만 본다 — Argo CD 가 넣는 건 전부 같은 계정이라 앱마다 구분하지 못한다AppProject 는 같은 배달원이라도 주문서 소속마다 다른 규칙표로 먼저 거른다

Argo CD 의 application-controller 는 권한이 넓은 하나의 서비스 계정으로 움직인다. RBAC 만으로는 Application 마다 다르게 막을 수 없어서 AppProject 가 그 한 겹을 더한다.

5.4. Application 을 쓸 수 있는 곳

AppProject 는 Application 의 project: 칸으로 정해진다. Application 객체를 새로 쓸 수 있는 사람은 더 넓은 프로젝트를 골라 적을 수도 있다는 뜻이다. 그래서 Application · AppProject 객체는 앱 매니페스트와 다른 레포(관리자만 push 하는 곳)에 두고, 앱 레포 쪽 프로젝트의 허용 종류에는 Application · AppProject 를 넣지 않는다.

6. ApplicationSet

ApplicationSet 은 목록(generator)과 틀(template)을 곱해 Application 을 여러 개 만든다. 환경이나 클러스터가 늘 때마다 Application 을 손으로 복사하지 않기 위해 쓴다.

generator- env: dev- env: stg- env: prodtemplatepath: envs/{{env}}ApplicationSetapp-devapp-stgapp-prodgenerator — 목록을 만든다 (직접 적은 목록 · 등록된 클러스터 · Git 폴더 등)template — Application 틀. {{env}} 같은 자리에 목록 값이 들어간다목록이 늘면 Application 이 늘고, 줄면 그 Application 이 지워진다환경·클러스터가 많을 때 쓴다 — Application 을 손으로 여러 개 쓰는 대신 규칙 하나로
List직접 적은 값env: dev, stg, prodClusterArgo CD 에 등록된 클러스터들seoul, tokyo …GitGit 의 폴더 · 파일 목록apps/a, apps/b …Matrix · Merge위 둘 이상의 조합클러스터 × 앱 …목록 → 항목마다 Application 하나generator 는 '몇 개를 무슨 값으로' 를 정하고, template 이 그 값으로 Application 을 채운다

Application 과 이름이 비슷하지만 별개의 종류이고, argocd-applicationset-controller 가 있어야 동작한다.

7. App of Apps

App of Apps 는 kind: Application 만 들어 있는 Git 폴더를 읽는 Application 하나를 두고, 그것이 나머지 Application 을 만들게 하는 구성이다. 맨 위의 것을 흔히 루트(root) Application 이라고 부른다.

Terraform(처음 한 번)루트 Applicationlbc.yamleso.yamlapps.yamlGit 폴더 — YAML 파일 (내용은 kind: Application)LBC 앱ESO 앱kukie-dev-appsDeployment …Deployment …member · agent …자식 Application — 클러스터에 생긴 객체실제 객체회색 — Git 밖. 루트를 Git 안에 두면 그것을 처음 읽어 줄 주체가 없다주황 문서 = Git 의 파일 · 보라 큐브 = 그 파일대로 클러스터(argocd 네임스페이스)에 생긴 Application 객체보라는 모두 같은 종류다. '루트' 는 맨 위에 있다는 뜻의 별명루트에 finalizer(resources-finalizer.argocd.argoproj.io)가 있으면 지울 때 자식과 그 객체까지 함께 지운다루트가 읽는 폴더에 쓸 수 있으면 Application 을 마음대로 만들 수 있다 — 관리자만 push 하게 둔다루트는 대장이 아니라 부모다 — 자식을 만들 뿐, 자식보다 큰 권한은 없다

7.1. 루트 Application

  • 별도 종류가 아니다. 다른 Application 과 똑같이 kind: Application 이고, 자기 프로젝트의 제한을 그대로 받는다
  • 읽는 폴더에 Application 객체가 들어 있어서 결과적으로 다른 Application 을 만들 뿐이다
  • 루트 자체는 그 폴더 밖에서 넣는다(Terraform · Helm · kubectl apply 한 번). 폴더 안에 두면 처음에 그것을 읽어 줄 주체가 없다
  • 이후 폴더에 Application 을 더하거나 빼는 것은 Git 머지만으로 된다

7.2. 삭제 전파

Application 에 resources-finalizer.argocd.argoproj.io finalizer 가 있으면, 그 Application 을 지울 때 그것이 넣은 객체도 함께 지운다. 루트에 붙어 있으면 루트를 지울 때 자식 Application 과 그 객체까지 전부 지워진다. finalizer 가 없으면 Application 만 지워지고 넣었던 객체는 클러스터에 남는다.

8. 관리 범위

Argo CD 가 하는 일은 객체를 클러스터(API 서버)에 넣는 데까지다. 넣은 객체를 읽고 클러스터 밖 자원을 만들거나 Pod 를 고치는 것은 그 종류를 맡은 컨트롤러다.

Argo CDAWS클러스터 (API 서버에 저장된 객체)IngressLBCAWS 로드밸런서ExternalSecretESOSecretSecret 변경ReloaderPod 재시작Argo CD 가 하는 일은 여기까지컨트롤러결과파랑 문서 — Argo CD 가 넣은 객체. 그 자체는 '이렇게 되어 있어야 한다' 는 기록일 뿐초록 — 컨트롤러. 객체를 지켜보다가(점선) 실제 일을 한다. 컨트롤러도 Argo CD 가 Deployment 로 띄운 Pod 다ESO 가 만든 Secret 은 Argo CD 가 넣은 게 아니라서 Git 과 비교 대상이 아니다컨트롤러가 없으면 객체는 저장만 되고 아무 일도 일어나지 않는다
  • Ingress 를 넣어도 로드밸런서 컨트롤러가 없으면 로드밸런서는 생기지 않는다
  • 컨트롤러 자신도 Deployment 같은 객체로 띄워지므로, 컨트롤러 설치 역시 Argo CD 로 할 수 있다
  • 컨트롤러가 만든 객체(ESO 가 만든 Secret 등)는 Git 에 없으므로 Argo CD 의 비교 대상이 아니다

9. 동기화

Git 이 바뀜Git 에서 지움 — prune손으로 바꿈 — selfHealv2v1v2a.yamlb 삭제ab✕replicas: 11 ← 3kubectl editGit클러스터보라 화살표 — Argo CD 의 동기화 (실선: 기본 동작 · 점선: 켰을 때만)흐린 점선 — 지워졌거나 덮어써질 것 · 회색 — 사람이 직접 바꾼 것Git 이 기준이다 — prune 과 selfHeal 은 그 기준을 얼마나 엄격히 지킬지 정한다

9.1. 수동과 자동

수동 (기본)Git 변경차이 계산OutOfSync사람이 SyncUI · CLI기다림클러스터 적용Synced자동 — syncPolicy.automatedGit 변경차이 계산OutOfSync바로클러스터 적용SyncedGit 확인 주기는 기본 최대 3분 (웹훅을 걸면 바로) — 두 방식 모두 차이 계산까지는 똑같이 한다처음 연결 · 선행 준비가 덜 된 앱은 수동, 평소 운영은 자동

9.2. prune

자동 동기화는 기본적으로 지우지 않는다. Git 에서 파일을 지워도 클러스터의 객체는 남는다. prune: true 를 켜면 Git 에 더 이상 없는 객체를 동기화 때 함께 지운다.

9.3. selfHeal

기본적으로 클러스터에서 손으로 바꾼 값은 다음 Git 변경 전까지 그대로 남는다. selfHeal: true 를 켜면 클러스터 쪽 변경도 차이로 보고 Git 내용으로 되돌린다. 되돌리기는 기본 5초 간격으로 다시 시도한다.

9.4. ignoreDifferences

일부러 Git 과 달라지는 칸은 비교에서 뺀다. 예를 들어 Reloader 는 Secret 이 바뀌면 Deployment 의 Pod 템플릿에 재시작 표시 주석을 붙이는데, 이 칸을 빼지 않으면 selfHeal 이 그 표시를 계속 지운다.

ignoreDifferences:
  - group: apps
    kind: Deployment
    jsonPointers:
      - /spec/template/metadata/annotations/reloader.stakater.com~1last-reloaded-from
syncPolicy:
  syncOptions: [RespectIgnoreDifferences=true]   # 동기화 때도 이 칸을 덮어쓰지 않게

10. 관련 문서

  • CRD — Application · AppProject · ApplicationSet 이 추가되는 방식
  • kubelet — 객체를 읽고 실제 일을 하는 조정 루프의 예
  • Admission Webhook — API 서버 단계에서 객체를 검증 · 변형하는 다른 장치
  • Controller — 객체를 지켜보다 실제 일을 하는 프로그램 (예정)
  • Helm · Kustomize — repo-server 가 렌더링하는 형식 (예정)

11. 출처

“Declaratively specify one Argo CD app that consists only of other apps.”

“Only admins should have push access to the parent Application’s source repository.”

“automated sync will not delete resources when Argo CD detects the resource is no longer defined in Git.”