LabHub

CAPA — Argo 프로젝트 인증 어소시에이트 · Argo CD 핵심 · 이론

왜 비교 대상이 세 개나 필요한가

LabHub 에서 이어서 보기

한 줄 요약

Argo CD 의 Application 컨트롤러는 기본 180초마다 깨어나 Desired(Git), Live(클러스터), Last-Applied(어노테이션) 세 상태를 비교한다. 두 개가 아니라 세 개인 이유와, 비교 전에 정규화를 거치지 않으면 왜 영원히 OutOfSync 로 남는지가 이 레슨의 전부다.

왜 이게 필요했나

Git 과 클러스터를 그냥 비교하면 안 되는가. 안 된다. 쿠버네티스는 오브젝트를 저장할 때 수많은 기본값을 채워 넣기 때문이다. Service 를 clusterIP 없이 올리면 API 서버가 하나 배정하고, 이미지 태그가 latest 면 imagePullPolicy 에 Always 를 넣고, 모든 오브젝트에 resourceVersion·uid·generation·creationTimestamp·managedFields 를 붙이고 status 를 채운다. Git 매니페스트에는 이 중 아무것도 없다. 그래서 "다르다" 는 결론이 항상 나오고, 자동 동기화를 켜 두면 컨트롤러가 3분마다 무의미한 apply 를 영원히 반복한다.

세 번째 비교 대상이 필요한 이유는 다른 종류의 문제다. Git 과 클러스터만 알면 "Git 에 없는데 클러스터에 있는 필드" 가 내가 예전에 넣었다가 지운 것인지, 다른 컨트롤러(HPA, 사이드카 인젝터, 디폴터)가 넣은 것인지 구별할 수 없다. Last-Applied 는 "직전에 내가 선언했던 것" 을 기억한다. 예전에 내가 선언했다가 지금 Git 에서 사라진 필드는 지워야 할 것이고, 내가 한 번도 선언한 적 없는 필드는 남의 것이므로 건드리면 안 된다. 이 판단을 3-way diff 가 한다. Argo CD 는 이 계산에 쿠버네티스 Server-Side Apply 가 쓰는 것과 같은 Structured Merge Diff 라이브러리를 쓴다.

어떻게 동작하나

정규화 단계에서 제거되는 대표 필드는 metadata.resourceVersion, metadata.uid, metadata.generation, metadata.creationTimestamp, metadata.managedFields, 그리고 대부분 리소스의 status 다. 그래도 남는 차이는 ignoreDifferences 로 직접 빼야 한다. 가장 흔한 사례가 HPA 다. Git 에는 replicas: 2 라고 적혀 있는데 HPA 가 8로 올려 두면 영원히 OutOfSync 이고, selfHeal 까지 켜져 있으면 Argo CD 가 2로 내리고 HPA 가 8로 올리는 싸움이 시작된다. 답은 /spec/replicas 를 diff 에서 빼는 것이다.

동기화는 세 단계로 나뉜다. PreSync → Sync → PostSync 이고, Sync 가 실패하면 SyncFail 단계가 따로 돈다. 각 단계에서 도는 것이 훅이며, 훅 리소스의 삭제 정책 기본값은 BeforeHookCreation 이다. 다음 동기화 때 이전 훅 오브젝트를 먼저 지우고 새로 만든다는 뜻이라, 실패한 마이그레이션 Job 의 흔적이 다음 배포까지 남아 있게 된다.

같은 단계 안에서의 순서는 웨이브가 정한다. 낮은 번호부터 돌고, 한 웨이브의 모든 리소스가 Healthy 가 될 때까지 기다린 다음 다음 웨이브로 넘어간다. 어느 웨이브에서든 실패하면 전체 동기화가 중단된다. 이 "기다린다" 가 중요하다. 웨이브는 apply 순서를 바꾸는 장치가 아니라 준비 완료를 기다리는 장치다.

재시도는 지수 백오프다. limit 5, duration 5s, factor 2, maxDuration 3m 로 두면 5초, 10초, 20초, 40초, 80초 간격으로 다섯 번 시도한다. 그리고 Argo CD 가 자기 것을 알아보는 방법은 추적 어노테이션이며 형식은 APP_NAME:GROUP/KIND:NAMESPACE/NAME 이다. 코어 그룹은 그룹 이름이 비어 있어서 my-app:/Service:default/nginx-svc 처럼 콜론 뒤에 바로 슬래시가 온다. 이 형식을 손으로 써 보면 왜 라벨 방식보다 어노테이션 방식이 권장되는지 감이 온다. 라벨은 63자 제한이 있고 네임스페이스가 들어가지 않는다.

현장에서 만나는 모습

저자 홈랩의 ArgoCD 는 MetalLB 풀에서 받은 10.0.0.201 로 떠 있고, 같은 대역의 10.0.0.200 에 Gitea 가 있다. 클러스터 등록에서 반드시 짚고 넘어가야 하는 함정이 하나 있는데, argocd cluster add 는 대상 클러스터에 argocd-manager 서비스어카운트를 만들고 그것을 기본으로 cluster-admin 에 묶는다는 점이다. 홈랩에서는 넘어갈 수 있지만 프로덕션에서 이대로 두면 GitOps 컨트롤러 하나가 모든 클러스터의 루트가 된다. 필요한 apiGroup 과 동사만 담은 ClusterRole 을 따로 만들어 바인딩을 갈아 끼우는 것이 정석이다.

또 하나, 이 클러스터에는 Cilium Gateway API 가 올라가 있는데 Gateway API CRD 를 v1.2 로 두었더니 tlsroutes 와 referencegrants 가 v1 이 아니라며 컨트롤러가 기동을 거부했다. v1.6.1 로 올려야 떴다. Argo CD 로 CRD 를 배포할 때 이런 일이 자주 생기는데, CRD 와 그 CRD 를 쓰는 커스텀 리소스가 같은 동기화에 들어 있으면 CRD 가 아직 등록되기 전에 CR 이 apply 되면서 실패한다. 웨이브를 나눠 CRD 를 먼저 보내는 것이 표준 해법이고, 이것이 웨이브가 존재하는 이유를 가장 잘 보여주는 사례다.

다음 실습에서 할 것

/root/capa-app/ 에 Application 매니페스트를 한 필드씩 쌓아 올린다. source·destination·syncPolicy·retry·ignoreDifferences 를 차례로 채우고, 마지막에는 그 앱이 배포할 네임스페이스와 디플로이먼트를 실제로 클러스터에 올려 추적 어노테이션까지 손으로 붙여 본다.