LabHub

CGOA — GitOps 인증 어소시에이트 · GitOps 원칙과 용어 · 이론

OpenGitOps 네 원칙 — 왜 push 가 아니라 pull 인가

LabHub 에서 이어서 보기

한 줄 요약

GitOps 는 배포 도구의 이름이 아니라 운영 모델의 이름 입니다. OpenGitOps 가 정의한 네 가지 원칙 — 선언적(Declarative), 버전 관리되고 불변(Versioned and Immutable), 자동으로 당겨짐(Pulled Automatically), 지속적으로 조정됨(Continuously Reconciled) — 을 모두 만족해야 GitOps 입니다. 넷 중 하나라도 빠지면 그냥 "Git 에 YAML 을 두는 CD 파이프라인"입니다.

왜 이게 필요했나

10 년 전 배포는 대개 이런 모양이었습니다. CI 서버가 빌드를 끝내면 그 CI 서버가 kubectl apply 를 쐈습니다. 이걸 push 모델이라고 부릅니다. 겉보기에는 단순한데 세 가지가 무너집니다.

첫째, 클러스터의 열쇠가 CI 안에 있습니다. Jenkins 나 GitHub Actions 러너가 kubeconfig 또는 ServiceAccount 토큰을 들고 있어야 합니다. 러너는 외부에서 온 코드를 실행하는 기계입니다. 포크에서 올라온 PR 워크플로 하나가 프로덕션 배포 권한을 얻는 경로가 여기서 생깁니다.

둘째, "지금 클러스터에 뭐가 떠 있나"의 답이 없습니다. 파이프라인 로그를 뒤져야 하고, 그마저도 누군가 손으로 고친 것은 기록에 없습니다.

셋째, 파이프라인이 멈추면 상태도 멈춥니다. push 는 이벤트가 있을 때만 동작합니다. 아무 이벤트도 없는 새벽 3시에 누가 replicas 를 손으로 바꿔 놓으면 아무도 모릅니다.

pull 모델은 이 셋을 한꺼번에 뒤집습니다. 클러스터 안에서 도는 에이전트(Argo CD, Flux)가 Git 을 읽어 옵니다. CI 에는 클러스터 자격증명을 한 개도 주지 않아도 됩니다. CI 가 하는 일은 "이미지를 빌드해 레지스트리에 올리고, 설정 저장소에 태그 한 줄을 커밋"까지입니다.

어떻게 동작하나

네 원칙을 하나씩 뜯어보면 이렇습니다.

| 원칙 | 뜻 | 어겼을 때 벌어지는 일 |
| --- | --- | --- |
| 선언적 | 원하는 최종 상태 를 기술한다. 도달하는 절차가 아니라 | 같은 스크립트를 두 번 돌리면 결과가 달라진다 |
| 버전 관리되고 불변 | 모든 상태가 커밋으로 남고, 이미 만들어진 리비전은 고치지 않는다 | "언제부터 이랬죠?"에 답할 수 없다 |
| 자동으로 당겨짐 | 승인된 변경을 에이전트가 스스로 가져간다 | 사람이 배포를 잊으면 Git 과 현실이 갈라진다 |
| 지속적으로 조정됨 | 이벤트와 무관하게 계속 비교하고 수렴시킨다 | 손으로 고친 변경이 영원히 살아남는다 |

세 번째 원칙의 원문이 "Pushed Automatically" 가 아니라 Pulled Automatically 라는 점이 시험에서 자주 나옵니다. 그리고 네 번째의 "Continuously" 는 "커밋할 때마다"가 아니라 커밋이 없어도 계속 이라는 뜻입니다. Argo CD 는 기본 180 초마다 이 루프를 돕니다.

"Git 이 SSOT(Single Source of Truth)" 라는 말도 자주 오해받습니다. 이건 "Git 에 모든 파일이 있다"가 아니라 "클러스터와 Git 이 다르면 Git 이 옳다" 는 선언입니다. 방향이 정해져 있다는 게 핵심입니다. 드리프트를 발견했을 때 Git 을 클러스터에 맞춰 고치는 것은 GitOps 가 아니라 그냥 사후 문서화입니다.

선언형이 항상 이기는 것도 아닙니다. 순서가 본질인 작업 — 데이터베이스 스키마 마이그레이션, 일회성 데이터 보정, 인증서 초기 발급 — 은 "최종 상태"로 표현이 안 됩니다. 그래서 Argo CD 는 훅(PreSync/PostSync)이라는 명령형 탈출구를 따로 둡니다. 선언형은 기본값이고, 명령형은 예외적으로 명시하는 것이라고 이해하면 정확합니다.

현장에서 만나는 모습

저자의 7 노드 홈랩은 이 모델을 그대로 씁니다. Gitea 가 10.0.0.200, Argo CD 가 10.0.0.201, Harbor 가 10.0.0.202, Grafana 가 10.0.0.203 — MetalLB L2 풀 10.0.0.200-215 에서 받은 주소입니다. 네트워크는 kube-proxy 없이 Cilium 1.20 eBPF 가 처리하고, 그 위에 kube-prometheus-stack 과 CloudNativePG 가 올라가 있습니다. 이 스택 전체가 클러스터 밖에서 밀어 넣은 게 아니라 안에서 당겨 온 결과입니다.

여기서 선언형의 위력과 한계를 동시에 보여주는 사건이 하나 있었습니다. 이 클러스터의 controlPlaneEndpoint 는 VIP 나 DNS 가 아니라 첫 컨트롤 플레인의 물리 IP 10.0.0.120 으로 박혀 있습니다. 나중에 컨트롤 플레인을 3 대로 늘려 etcd 멤버 3 개를 갖췄는데도, 그 첫 노드가 죽으면 kubectl 도 7 대의 kubelet 도 전부 접속 불가가 됩니다. apiserver 인증서 SAN 에 다른 노드 IP 가 없어 TLS 검증부터 실패하기 때문입니다.

이게 GitOps 와 무슨 상관이냐 하면 — 선언 파일에 적히지 않은 값은 관리되지 않는다 는 것을 보여주기 때문입니다. controlPlaneEndpoint 는 클러스터를 만든 순간 한 번 정해지고 그 뒤에는 Git 에도 없고 조정 대상도 아닙니다. 그래서 바꾸는 게 지옥입니다. GitOps 로 관리할 수 있는 영역과 클러스터 부트스트랩처럼 그 바깥에 있는 영역을 구분하는 감각이 실무에서 훨씬 중요합니다.

이어서 읽을 것

바로 다음 리딩에서 desired state, drift, reconciliation, convergence 같은 시험 단어를 정확한 정의로 맞춥니다. 그다음 모듈에서는 /root/cgoa-repo/ 아래에 실제 GitOps 저장소 뼈대를 만들고, kubectl kustomize 로 환경별 렌더 결과가 어떻게 갈리는지 눈으로 확인합니다.