GitOps 와 Argo CD 의 개념
이번 포스팅부터는 Argo CD 를 정리해보려 합니다. 쿠버네티스에 무언가를 배포하는 방법은 여러 가지인데 그중 GitOps 라는 방식이 자리를 잡아가고 있습니다. Git 저장소를 유일한 진실의 원천으로 두고 클러스터 안의 에이전트가 실제 상태를 계속 그쪽으로 맞추는 방식입니다. Argo CD 는 그 방식을 구현한 도구입니다.
첫 편에서는 왜 이런 도구가 필요해졌는지와 Argo CD 를 이루는 개념들을 다루겠습니다.
1. 배포는 어떻게 해왔는가
1.1. 스크립트로 밀어 넣던 시절
처음에는 CI 파이프라인 마지막에 배포 명령을 붙이는 방식이었습니다.
빌드 → 테스트 → 이미지 푸시 → kubectl apply
간단하고 직관적입니다. 그런데 규모가 커지면 몇 가지가 무너집니다.
- CI 가 클러스터 자격 증명을 갖고 있어야 합니다. 외부에 있는 CI 서버에 운영 클러스터의 관리자 권한을 쥐어주는 셈입니다. CI 가 뚫리면 클러스터가 함께 뚫립니다.
- 클러스터가 늘어나면 자격 증명도 늘어납니다. 개발, 스테이징, 운영, 지역별 클러스터까지 가면 CI 가 관리해야 할 접속 정보가 계속 불어납니다.
- 지금 무엇이 떠 있는지 알 수 없습니다.
누군가
kubectl edit으로 직접 고치면 저장소의 매니페스트와 실제가 어긋납니다. 그런데 그것을 알아챌 방법이 없습니다. - 되돌리기가 어렵습니다. 이전 버전으로 돌아가려면 그때의 매니페스트가 무엇이었는지부터 찾아야 합니다.
1.2. 세 번째 문제가 특히 고약하다
드리프트(Drift) 라고 부르는 현상입니다. 저장소에는 복제본이 3개로 적혀 있는데 실제로는 5개가 떠 있는 식입니다. 장애 대응 중에 급하게 손으로 늘려두고 저장소에 반영하지 않은 경우가 대부분입니다. 그 자체로는 문제가 없지만 다음 배포 때 조용히 3개로 돌아가면서 장애가 재발합니다. 그리고 왜 그런지 아무도 모릅니다.
시간이 지날수록 저장소의 YAML 은 "한때 이랬던 것" 이 되고 진짜 상태는 클러스터에만 있게 됩니다. 그러면 클러스터를 새로 만들어야 할 때 재현할 방법이 없습니다.
2. GitOps
2.1. 네 가지 원칙
GitOps 는 이 문제들을 다음 원칙으로 풉니다.
- 선언형 : 시스템 전체가 선언적으로 기술되어야 합니다. 쿠버네티스가 이미 그렇습니다.
- 버전 관리되고 변경 불가능한 저장소 : 그 선언은 Git 에 있고 모든 변경에 이력이 남습니다.
- 자동으로 끌어옴 : 승인된 변경은 에이전트가 스스로 가져가 적용합니다.
- 지속적인 조정 : 에이전트가 실제 상태를 계속 관찰하며 선언된 상태로 수렴시킵니다.
네 번째가 핵심입니다. 배포가 "한 번 일어나는 사건" 이 아니라 "계속 도는 루프" 가 됩니다. 쿠버네티스 컨트롤러가 하는 일과 정확히 같은 구조이고 그것을 클러스터 바깥까지 한 겹 더 확장한 것입니다.
2.2. 밀어 넣기와 끌어오기
이 차이가 앞서 본 보안 문제를 해결합니다.
- Push 방식 (기존 CI) : 바깥의 CI 가 클러스터를 향해 명령을 보냅니다. CI 가 클러스터 자격 증명을 가져야 합니다.
- Pull 방식 (GitOps) : 클러스터 안에 있는 에이전트가 Git 을 읽어 스스로 적용합니다. 클러스터 자격 증명이 바깥으로 나가지 않고 에이전트에게는 Git 읽기 권한만 있으면 됩니다.
방향이 뒤집히면서 자격 증명이 흐르는 방향도 뒤집힙니다. 클러스터가 여러 개여도 각 클러스터의 에이전트가 알아서 자기 것을 가져가므로, 중앙에서 접속 정보를 모아둘 필요가 없습니다.
2.3. 저장소를 어떻게 나눌 것인가
실무에서 가장 먼저 부딪히는 결정입니다. 애플리케이션 코드와 매니페스트를 한 저장소에 두면 관리는 편하지만 문제가 생깁니다. 배포를 위한 이미지 태그 변경이 애플리케이션 커밋 이력에 섞이고 CI 가 자기 저장소에 커밋을 밀어 넣으면서 다시 CI 가 돌아 무한 루프에 빠지기도 합니다.
그래서 대체로 매니페스트를 별도 저장소로 분리합니다.
- 앱 저장소 : 소스 코드. CI 가 빌드해 이미지를 푸시합니다.
- 설정 저장소 : 매니페스트. 이미지 태그가 여기서 바뀌고 Argo CD 가 이것만 봅니다.
권한을 가르기도 좋습니다. 개발자는 앱 저장소에, 운영 변경은 설정 저장소의 리뷰를 거치게 할 수 있습니다.
3. Argo CD
3.1. 무엇인가
쿠버네티스를 위한 선언형 GitOps 지속 배포 도구입니다. Intuit 에서 시작해 CNCF 로 넘어갔고 2022년에 정식 졸업(Graduated) 프로젝트가 되었습니다. 하는 일을 한 줄로 줄이면 이렇습니다. Git 에 있는 매니페스트와 클러스터의 실제 상태를 계속 비교하고 다르면 알려주거나 맞춥니다.
3.2. Application
Argo CD 의 중심에 있는 CRD 입니다. "어느 저장소의 어느 경로를, 어느 클러스터의 어느 네임스페이스에 배포할 것인가" 를 담습니다.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/manifests
targetRevision: main
path: apps/web/overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: web
syncPolicy:
automated:
prune: true
selfHeal: true
여기서 source 와 destination 이 하나씩 묶여 있다는 점을 봐두면 좋습니다. 같은 매니페스트를 여러 환경에 배포하려면 Application 을 환경마다 만듭니다. 쿠버네티스 시리즈에서 본 대로, 이것도 spec 과 status 를 가진 평범한 오브젝트입니다. 사용자가 spec 을 적고 Argo CD 의 컨트롤러가 status 에 현재 동기화 상태와 건강 상태를 채웁니다.
3.3. 두 가지 상태
Argo CD 는 Application 마다 성격이 다른 두 상태를 따로 보여줍니다. 이 둘을 구분하는 것이 중요합니다. Sync Status 는 Git 과 클러스터가 같은가를 봅니다.
- Synced : 일치합니다.
- OutOfSync : 다릅니다. 누가 손으로 고쳤거나, Git 이 앞서 있거나.
- Unknown : 비교할 수 없습니다. 저장소를 못 읽는 경우 등.
Health Status 는 그 리소스가 정상 동작하는가를 봅니다.
- Healthy : 정상입니다.
- Progressing : 아직 진행 중입니다. 롤아웃 중인 Deployment 등.
- Degraded : 문제가 있습니다. 복제본이 뜨지 못하는 경우 등.
- Missing : 있어야 하는데 없습니다.
- Suspended : 일시 정지된 상태입니다.
두 상태가 왜 따로 있는지는 예를 보면 분명합니다. Synced 인데 Degraded 일 수 있습니다. Git 에 적힌 대로 정확히 적용했는데 그 이미지가 애초에 잘못되어 Pod 가 계속 죽는 경우입니다. 반대로 OutOfSync 인데 Healthy 일 수도 있습니다. 누가 복제본을 늘려두었고 서비스는 잘 돌고 있는 경우입니다.
"배포가 끝났다" 와 "잘 돌고 있다" 는 다른 질문이라는 것이 여기에 담겨 있습니다.
3.4. 건강 상태는 어떻게 판단하는가
Argo CD 는 기본 리소스에 대한 판단 기준을 내장하고 있습니다. Deployment 라면 status.readyReplicas 를, Service 라면 LoadBalancer 의 주소 할당 여부를 봅니다. 직접 만든 CRD 에 대해서는 Lua 스크립트로 판단 기준을 정의할 수 있습니다. 쿠버네티스 시리즈 8편에서 본 오퍼레이터가 관리하는 커스텀 리소스도, 이 방식으로 Argo CD 가 상태를 읽을 수 있게 됩니다.
4. AppProject
Application 이 하나둘일 때는 문제가 없지만 팀이 여러 개가 되면 통제가 필요해집니다.
AppProject 는 Application 들을 묶고 경계를 긋는 오브젝트입니다.
- 허용된 소스 저장소 : 이 프로젝트의 Application 은 지정된 저장소에서만 매니페스트를 가져올 수 있습니다.
- 허용된 대상 : 어느 클러스터의 어느 네임스페이스에 배포할 수 있는지 제한합니다.
- 허용/금지 리소스 종류 : 클러스터 범위 리소스를 만들지 못하게 막는 식입니다. 팀이 ClusterRole 을 마음대로 만드는 것을 차단할 수 있습니다.
- 동기화 창(sync window) : 특정 시간대에만 배포를 허용하거나 금지합니다.
AppProject 를 쓰지 않으면 모든 Application 이 default 프로젝트에 들어가고 default 는 기본적으로 아무 제한이 없습니다. 팀이 나뉘어 있다면 프로젝트부터 가르는 것이 순서입니다.
5. 매니페스트를 만드는 방법
Argo CD 는 저장소에서 가져온 것을 그대로 적용하지 않습니다. 먼저 최종 YAML 로 만들어내는 단계를 거칩니다. 이것을 매니페스트 생성이라고 합니다.
지원하는 방식들입니다.
- plain YAML / JSON : 디렉토리 안의 매니페스트를 그대로 씁니다.
- Kustomize :
기본(base)에 환경별 덮어쓰기(overlay)를 얹는 방식입니다. Argo CD 가
kustomization.yaml을 발견하면 자동으로 인식합니다. - Helm :
차트를 템플릿으로 쓰고 값을 채웁니다. 여기서 중요한 점이 있습니다. Argo CD 는
helm install을 하지 않고helm template으로 YAML 만 뽑아냅니다. 그래서 Helm 이 릴리스 상태를 따로 들고 있지 않고 상태 관리는 전적으로 Argo CD 가 합니다. 8편에서 본 "Helm 이 아는 상태와 실제가 어긋나는" 문제가 이 방식에서는 생기지 않습니다. - 설정 관리 플러그인 : Jsonnet 이나 사내 도구 등을 끼워 넣을 수 있습니다.
여러 소스를 하나의 Application 에 묶는 것도 가능합니다. 공개 Helm 차트를 가져오되 값 파일은 사내 저장소에서 읽는 구성이 대표적입니다.
6. 동기화
6.1. 수동과 자동
기본값은 수동입니다. Git 이 바뀌면 Argo CD 가 OutOfSync 로 표시만 하고 사람이 Sync 버튼을 눌러야 적용됩니다. 운영 환경에서는 이 방식을 유지하는 곳도 많습니다. syncPolicy.automated 를 켜면 자동으로 적용합니다. 여기에 붙는 두 옵션이 중요합니다.
- prune : Git 에서 지워진 리소스를 클러스터에서도 지웁니다. 이것을 켜지 않으면 삭제가 반영되지 않아 쓰지 않는 리소스가 계속 남습니다. 다만 실수로 파일을 지웠을 때 그대로 삭제되므로 조심해야 합니다.
- selfHeal : 누가 클러스터를 직접 고치면 되돌립니다. 앞서 본 드리프트를 원천적으로 막는 장치입니다.
selfHeal 을 켜면 kubectl edit 으로 한 수정이 몇 초 뒤 사라집니다. 처음에는 당황스럽지만 그것이 이 도구의 목적입니다. 바꾸고 싶으면 Git 을 바꾸라는 것입니다.
6.2. 언제 알아채는가
Argo CD 는 기본적으로 3분마다 저장소를 다시 확인합니다. 그래서 커밋 후 반영까지 최대 3분이 걸릴 수 있습니다. 즉시 반영하고 싶으면 Git 저장소의 웹훅을 Argo CD 로 걸어둡니다. 그러면 푸시 즉시 확인이 일어납니다. 폴링은 그대로 두어 웹훅이 유실되어도 결국 맞춰지도록 합니다.
쿠버네티스 시리즈 1편에서 본 레벨 트리거가 여기서도 그대로 나타납니다. 웹훅은 빨리 알아채기 위한 힌트일 뿐이고 판단은 항상 현재 상태를 다시 보고 합니다.
7. 조금 더 들어가보기 : 왜 Git 이 진실의 원천이 될 수 있는가
GitOps 는 Git 을 진실의 원천으로 삼습니다. 그런데 왜 하필 Git 인가, 그리고 Git 이 그 역할을 감당할 수 있는 근거가 무엇인지는 짚고 넘어갈 만합니다.
7.1. Git 은 파일 이력이 아니라 내용 주소 저장소다
흔히 Git 을 "변경 이력을 저장하는 도구" 로 이해하는데 내부는 조금 다릅니다. Git 은 내용을 해시로 주소 삼아 저장하는 키-값 저장소입니다.
- 파일의 내용을 해시하면 blob 이 됩니다.
- 디렉토리 구조는 blob 과 다른 tree 를 가리키는 tree 가 됩니다.
- 커밋은 tree 하나와 부모 커밋을 가리키는 commit 이 됩니다.
여기서 중요한 성질이 나옵니다. 커밋 해시는 그 시점의 전체 내용에서 계산됩니다. 파일 하나라도 다르면 tree 해시가 달라지고 그러면 커밋 해시도 달라집니다. 부모 커밋 해시까지 포함되므로 과거를 고치면 그 이후의 모든 해시가 바뀝니다.
즉 커밋 해시 하나가 그 시점 저장소 전체 상태의 지문입니다.
7.2. 그래서 배포 이력이 증명 가능해진다
이 성질이 GitOps 의 근거가 됩니다. "지금 운영에 떠 있는 것은 커밋 a1b2c3d 입니다" 라고 말할 때, 그 해시는 그 시점의 모든 매니페스트를 합친 값입니다. 누군가 몰래 한 줄을 고쳤다면 해시가 달라지므로 들통납니다.
앞서 정리한 Argo CD 의 동기화 상태가 의미를 갖는 것도 이 때문입니다. "클러스터가 이 커밋과 일치한다" 는 말이 검증 가능한 진술이 됩니다. 배포 도구가 자체 데이터베이스에 이력을 남기는 방식과 비교하면 차이가 분명합니다. 그쪽은 그 데이터베이스를 믿어야 하는데 Git 은 내용 자체에서 계산된 값이라 별도의 신뢰가 필요 없습니다.
7.3. 되돌리기가 왜 안전한가
이력이 남는 것과 되돌릴 수 있는 것은 다른 이야기입니다. Git 에서 되돌리기가 안전한 이유는 옛 커밋이 사라지지 않기 때문입니다. 새 커밋을 쌓아도 옛 트리는 그대로 저장소에 남아 있습니다. 그래서 특정 커밋으로 되돌린다는 것은 그 시점의 tree 를 다시 가리키는 것이지, 과거를 복원하는 복잡한 작업이 아닙니다.
앞서 본 Deployment 의 롤백이 이전 ReplicaSet 의 개수를 다시 올리는 것으로 끝나는 것과 같은 구조입니다. 버릴 것을 지우지 않고 남겨두었기 때문에 되돌아갈 수 있습니다. Iceberg 의 스냅샷도, 쿠버네티스의 리비전도 같은 발상입니다. 여러 시스템에서 반복해 나오는 방식입니다.
7.4. 그래도 Git 이 만능은 아니다
몇 가지 한계가 분명합니다.
- 큰 파일에 약합니다. 커밋마다 내용 전체가 새 객체로 저장되므로, 자주 바뀌는 큰 파일이 있으면 저장소가 빠르게 부풉니다. 매니페스트는 텍스트라 대체로 괜찮습니다.
- 비밀을 담을 수 없습니다. 그리고 한 번 커밋하면 이력에서 지우기가 매우 어렵습니다. 앞서 정리한 대로 별도의 비밀 관리가 필요한 이유입니다.
- 순서를 보장하지 않습니다. 여러 팀이 같은 저장소에 커밋하면 그 순서가 배포 순서가 되는데 서로 의존이 있으면 그것까지 Git 이 관리해주지는 않습니다.
- Git 이 죽으면 배포가 멈춥니다. 진실의 원천이 하나라는 것은 그것이 단일 장애점이라는 뜻이기도 합니다. 다만 이미 떠 있는 워크로드는 계속 돕니다.
7.5. 대규모에서 생각할 것들
- 저장소 하나에 모든 것을 담을 것인가. 하나로 두면 전체를 한눈에 보기 좋지만 커밋이 잦아지고 권한을 가르기 어렵습니다. 팀별로 나누면 반대가 됩니다.
- 커밋 하나가 얼마나 많은 것을 바꾸는가. 공통 설정을 한곳에 두면 그것을 고칠 때 수백 개가 동시에 움직입니다. 편의와 폭발 반경을 맞바꾸는 결정입니다.
- 읽기 부하. 에이전트가 주기적으로 저장소를 확인하므로, Application 이 수백 개면 Git 서버로 가는 요청도 그만큼입니다. 캐시와 웹훅을 함께 쓰는 이유입니다.
결론
Argo CD 를 공부하면서 가장 크게 와닿은 것은, 배포를 사건이 아니라 상태로 보는 관점의 전환이었습니다. 그전까지 저는 배포를 "파이프라인이 끝까지 돌면 완료되는 절차" 로 생각했습니다. 그런데 GitOps 에서는 배포가 끝나는 지점이 없습니다. 계속 비교하고 계속 맞춥니다. 그래서 "배포가 성공했는가" 대신 "지금 Git 과 클러스터가 같은가" 를 묻게 됩니다.
두 번째로 인상 깊었던 것은 Sync Status 와 Health Status 를 갈라놓은 것이었습니다. 적용은 정확히 되었는데 동작은 망가진 상태가 실제로 존재하는데 그 둘을 한 화면에서 구분해 보여주는 것만으로도 문제를 어디서 찾아야 할지가 분명해졌습니다. "내가 시킨 대로 되었는가" 와 "그게 잘 도는가" 는 다른 질문이라는 것을 도구가 구조로 못박아 둔 셈입니다.
세 번째는 자격 증명이 흐르는 방향이 뒤집힌다는 점입니다. Push 에서 Pull 로 바꾼 것이 단순한 구현 차이가 아니라, 클러스터 접근 권한을 바깥으로 내보내지 않아도 되는 보안상의 이득으로 이어졌습니다. 방향 하나를 바꿔 문제를 없애는 방식이 인상적이었습니다.
공부 참고 자료
📑 2. Argo CD - Declarative Setup
📑 3. Argo CD - Application Specification
📑 5. Argo CD - Automated Sync Policy