대규모 쿠버네티스 플랫폼에서 GitOps 다시 생각하기
GitOps의 달콤한 거짓말
최근 회사에서 클러스터가 50개를 넘어서면서 GitOps 운영에서 예상치 못한 문제들이 나타나기 시작했습니다. "Git은 유일한 진실의 원천(Single Source of Truth)"이라는 말을 믿고 설계한 시스템이 규모가 커지면서 삐걱거리기 시작한 거죠.
클라우드 네이티브 생태계에서 수년간 반복되어온 이 간단한 명제가 실제로는 얼마나 복잡한 현실을 감추고 있는지, 그리고 대규모 환경에서는 왜 다른 접근이 필요한지 정리해보려고 합니다.

상태, 의도, 현실 - 용어부터 정리하자
쿠버네티스 관련 논의에서 '상태'라는 용어가 도처에 사용되지만, 사실 세 가지 완전히 다른 개념이 뒤섞여 있습니다.
실제 상태 - 지금 벌어지는 현실
첫 번째는 클러스터의 실제 런타임 상태입니다. 지금 바로 이 순간 어떤 Pod가 실행 중이고, 어떤 컨테이너가 healthy한지, IP 주소는 뭔지, 어떤 노드에 과부하가 걸렸는지 같은 것들이죠. 이 상태는 끊임없이 변화합니다.
마지막으로 알려진 상태 - 운영 메모리
두 번째는 클러스터의 운영 메모리입니다. 쿠버네티스에서는 etcd에 저장되는 정보죠. 컨트롤러들이 지속적으로 읽고 쓰는, 클러스터가 현재 세상을 어떻게 이해하고 있는지를 담은 객체들과 메타데이터입니다.
실질적으로 말하면 etcd가 클러스터의 진짜 상태 저장소입니다.
원하는 상태 - 우리의 의도
세 번째는 원하는 상태, 즉 우리가 바라는 시스템의 모습입니다. YAML 매니페스트, Helm 차트, 커스텀 설정 등으로 표현되는 것들이죠. 이 파일들은 현재 일어나고 있는 일이 아니라 우리가 바라는 일을 설명합니다.
바로 여기서 Git이 등장합니다. Git은 이런 매니페스트를 저장하고 의도를 버전 관리하는 역할을 합니다.
Git이 진정한 상태 저장소가 아닌 이유
진짜 상태 저장소라면 상태가 지속적으로 기록되고 업데이트되는 시스템이어야 합니다. 빈번한 읽기/쓰기 작업을 처리하고, 런타임 정보를 저장하며, 시스템의 현재 상태를 알고 있어야 하죠. 데이터베이스나 분산 키-값 저장소, 쿠버네티스의 etcd 같은 것들 말입니다.
Git이 정말 상태 저장소라면 컨트롤러들이 런타임 정보를 지속적으로 저장소에 기록해야 할 겁니다. 양방향 GitOps처럼요.
예를 들어:
- 복제본 수가 변경되었습니다
- Pod가 생성되었습니다
- Pod에 IP 주소 10.1.2.3이 할당되었습니다
- Pod가 CrashLoopBackOff 상태에 진입했습니다
하지만 이런 일은 전혀 일어나지 않습니다. Git은 런타임 변경 사항을 기록하지 않아요. 배포가 실패했는지, Pod가 비정상 상태인지 알 수도 없고요.
Git은 사람이나 파이프라인이 작성한 정적 선언을 저장하는 시스템입니다. 운영 메모리로는 기능할 수 없어요.
Git의 실제 역할과 한계
Git이 실제로 하는 역할은 훨씬 간단합니다. 의도를 기록하는 시스템이죠. 누가 언제 설정을 변경했는지, 그 이유는 무엇인지를 기록합니다. 이것만으로도 충분히 가치 있지만, 운영 상태를 저장하는 것과는 근본적으로 다릅니다.
게다가 널리 인용되는 "Git이 진실의 원천이다"라는 말도 자세히 살펴보면 완전히 들어맞지 않아요. 일반적인 패턴에서 Git은 의도를 기록하는 시스템으로 사용되고, 대부분의 종속성은 간접적으로 참조됩니다.
Helm 차트는 다른 차트를 참조하고, 차트는 컨테이너 이미지를 참조하죠. 매우 간단한 매니페스트를 생각해보세요:
image: my-app:latest
# 또는
image: my-app:3.0.1
매니페스트는 최신 버전의 애플리케이션을 배포하겠다는 의도를 명시하지만, 실제 아티팩트는 다른 곳, 보통 컨테이너 레지스트리에 있습니다. 해당 태그 뒤의 이미지가 다시 빌드되어 푸시되면 Git에서는 아무것도 변경되지 않았어도 클러스터가 다른 이미지를 가져올 수 있어요.
실제 아티팩트, 즉 정말로 실행되는 부분은 Git 외부에 존재합니다.
대규모에서 Git이 한계를 드러내는 지점
소규모 환경에서는 전통적인 GitOps가 매우 효과적입니다. 하지만 플랫폼 규모가 커지면 이야기가 달라져요.
일반적인 플랫폼 환경을 상상해보세요:
- 50개 클러스터
- 클러스터당 5개의 플랫폼 애플리케이션
- 총 250개의 GitOps로 제어되는 배포
각 GitOps 컨트롤러는 주기적으로(기본값은 3분마다) 소스의 업데이트를 확인합니다. 소스가 Git인 경우, 이는 각 컨트롤러가 저장소와 반복적으로 상호작용한다는 뜻이죠.
모든 폴링 과정에는 여러 단계가 포함됩니다:
- 인증
- 연결 설정
- 저장소 동기화
- 커밋 그래프 비교
아무것도 변하지 않았어도 이런 작업들은 여전히 진행됩니다.
최신 GitOps 도구들은 shallow clone, sparse checkout, 저장소 파티셔닝 같은 기술로 이런 오버헤드를 줄이려고 합니다. 일부 팀은 웹훅을 사용해 폴링을 완전히 피하려는 실험도 하죠. 이런 최적화들은 분명 도움이 되지만, Git 프로토콜의 근본적인 "수다스러움"은 바꾸지 못합니다.
수백 개의 컨트롤러가 동시에 실행되면 Git 서버에 불필요한 부하가 발생하고, 시간이 지나면서 성능 문제가 나타나기 시작합니다.
OCI 아티팩트를 배포 계층으로 활용하기
OCI 레지스트리는 대규모 아티팩트 배포라는 전혀 다른 문제를 해결합니다. 컨테이너 레지스트리는 수천 개의 노드를 동시에 서비스하도록 구축되었어요. 높은 동시성과 글로벌 분산을 정상적인 운영 조건으로 처리합니다.
구성이 OCI 아티팩트로 패키징되면 상호작용이 훨씬 간단해집니다:
- CI 파이프라인이 매니페스트와 메타데이터를 포함하는 아티팩트를 빌드
- 해당 아티팩트를 OCI 레지스트리로 푸시
- GitOps 컨트롤러는 아티팩트 다이제스트가 변경되었는지만 확인
- 변경사항이 있으면 해당 아티팩트를 가져와 적용
중요한 차이점은 컨트롤러가 더 이상 저장소 히스토리를 협상하지 않는다는 겁니다. 특정 아티팩트가 변경되었는지만 확인하면 되죠.
이로써 OCI는 배포 메커니즘으로서 훨씬 효율적이 됩니다.
하지만 OCI에도 분명한 한계가 있습니다. OCI 아티팩트는 불변 패키지입니다. 배포에는 적합하지만, 구성이 생성되거나 관리되는 시스템은 아니에요. 배포 계층으로 이해하는 것이 가장 적절합니다.

구성 데이터 계층의 필요성
Git이 의도를 저장하고 OCI가 아티팩트를 배포한다면, 중요한 질문이 남습니다. 실제 구성 값은 어디에 있을까요?
바로 이 지점에서 ConfigHub 같은 구성 관리 시스템이 등장합니다. ConfigHub는 구성을 여러 저장소에 흩어져 있는 파일이 아닌 구조화된 데이터로 처리합니다.
이 접근법은 의도적으로 WET(Write Everything Twice) 원칙을 따릅니다. 사람의 작업을 중복하는 게 아니라, 기계를 위해 명확한 구성을 생성하는 거죠.
명시적 구성의 장점
- 배포 전에 최종 결과를 검증할 수 있습니다
- 구성 방식을 이해하기가 더 쉬워집니다
- 디버깅이 훨씬 간단해집니다
이를 통해 진정한 WYSIWYG 모델을 구현할 수 있어요. 화면에 보이는 것이 실제로 배포되는 내용이죠.
ConfigHub는 여러 시스템의 구성을 통합합니다:
- Git 저장소
- Vault 같은 시크릿 스토어
- 데이터베이스
- 배포 메타데이터
그런 다음 이런 입력값들을 검증된 구성 표현으로 변환합니다.
규제 압력도 고려해야 할 요소
확장성 문제만이 플랫폼들이 아키텍처를 재고하는 유일한 이유는 아닙니다. 규제도 주요 동인으로 떠오르고 있어요.
**EU 사이버 복원력법(CRA)**은 디지털 구성요소를 포함하는 제품에 대한 엄격한 보안 요구사항을 도입했습니다. 이런 요구사항의 대부분은 2027년까지 의무화될 예정이죠.
EU에서 소프트웨어나 SaaS를 제공하는 조직은 다음을 제공해야 합니다:
- 소프트웨어 공급망 투명성
- 취약점 관리
- 소프트웨어 자재명세서(SBOM)
CRA는 시장 원칙을 따르기 때문에, EU에서 소프트웨어를 파는 기업은 본사 위치와 관계없이 관련 규정을 준수해야 합니다.
Git만으로는 공급망 추적성을 완벽하게 보장하기 어렵습니다. 커밋 병합이나 히스토리 재작성 같은 작업이 감사 추적의 일부를 삭제할 수 있거든요.
반면 OCI 아티팩트는 조직이 여러 요소를 단일의 불변 아티팩트로 묶을 수 있게 해줍니다:
- 배포 매니페스트
- 컨테이너 이미지
- SBOM
- 서명
각 아티팩트는 배포의 검증 가능한 스냅샷이 됩니다.
현실적인 GitOps 아키텍처
실제로 목표는 아키텍처에서 Git을 제거하는 게 아닙니다. Git은 협업과 버전 관리 측면에서 여전히 매우 유용한 도구예요. 하지만 그 역할은 올바르게 이해되어야 합니다.
현대적인 대규모 GitOps 아키텍처는 일반적으로 책임을 분리합니다:
| 구성요소 | 역할 | 특징 |
|---|---|---|
| Git | 의도 기록 | 개발자와 플랫폼 팀의 변경사항 추적 |
| OCI | 아티팩트 배포 | 불변 배포 아티팩트, 대규모 확장 가능 |
| ConfigHub | 구성 집계 | 구성 구조에 대한 쿼리 가능한 진실의 원천 |
| Kubernetes/etcd | 운영 상태 | 클러스터의 실제 운영 상태 저장 |
각 구성요소는 자신이 가장 잘할 수 있는 역할을 수행합니다.
이런 아키텍처를 통해 플랫폼 팀은 파일 기반 저장소로는 답변하기 어려운 운영 관련 질문을 할 수 있게 됩니다:
- 유럽 전역의 모든 클러스터에서 스테이징 환경에 실행되는 애플리케이션은 뭔가요?
- securityContext가 누락된 워크로드는 어떤 건가요?
- 어떤 배포에서 취약한 이미지를 참조하고 있나요?
특히 사고나 보안 이벤트 발생 시 시스템의 정확한 상태를 신속하게 파악하는 건 매우 중요합니다.

마치며
"Git은 유일한 진실의 원천"이라는 말은 GitOps 초창기에는 유용한 사고 모델이었습니다. 하지만 플랫폼이 대규모로 성장하면서 이 단순한 명제의 한계가 드러나고 있어요.
Git은 강력한 도구이지만, 인프라의 운영 메모리 역할을 하도록 설계된 건 아닙니다. 이를 상태 저장소로 취급하면 혼란과 확장성 문제가 발생하죠.
규모가 커지면 게임의 룰이 바뀝니다. 향후 10년을 위한 플랫폼을 구축한다면, Git을 데이터베이스처럼 취급하는 걸 멈추고 구성 파일을 정말 중요한 핵심 데이터로 다루기 시작해야 할 때인 것 같습니다.