배포는 됐는데 클러스터 상태가 안 맞다 - Jenkins에서 GitOps로 넘어간 이유
Jenkins가 빌드와 배포를 한 번에 떠맡던 구조에서 벗어나, Argo Events·Workflows·ArgoCD·Pulp로 빌드와 배포를 분리하고 Git과 클러스터 상태를 항상 일치시키는 파이프라인으로 바꾼 과정.
문제: 파이프라인이 어디에 있는지 아무도 정확히 모른다
원래 파이프라인은 Jenkins가 빌드와 배포를 통째로 맡는 구조였다. 빌드 산출물(이미지·패키지)은 Nexus에 올라갔고, 배포까지 같은 Jenkins job 안에서 이어졌다.
서비스가 9개로 늘면서 이 구조의 문제가 드러났다.
- 파이프라인 정의가 Jenkins UI와 스크립트에 흩어져 있었다. 어떤 job이 어떤 스크립트를 참조하는지, 최근에 뭐가 바뀌었는지 이력을 추적하기가 번거로웠다.
- "배포했다"와 "클러스터에 실제로 반영됐다"가 같은 말이 아니었다. Jenkins job이 성공으로 끝나도, 그 이후 클러스터에서 누군가 수동으로 리소스를 건드리면 Git에 선언된 상태와 실제 클러스터 상태가 어긋난다. 이 drift를 감지할 방법이 없었다.
- 빌드와 배포가 한 프로세스로 묶여 있어 빌드만 다시 하고 싶어도, 배포만 다시 하고 싶어도 전체 job을 다시 돌려야 했다.
시도한 것: 빌드와 배포를 완전히 분리하기
핵심 방향은 하나다. 빌드는 이벤트가 트리거하고, 배포는 Git 상태를 클러스터가 계속 따라가는 방식으로 바꾼다.
Bitbucket (push)
│ webhook
▼
Argo Events (webhook 수신 → NATS → Sensor)
│ 트리거
▼
Argo Workflows (빌드 → 테스트 → 이미지 빌드)
│ OPA 정책 게이트 통과 확인
▼
Pulp (이미지/패키지 레지스트리)
│
▼
ArgoCD (Git 선언 상태 ↔ 클러스터 실제 상태 지속 동기화)
│
▼
클러스터 반영
Argo Events - Bitbucket 이벤트를 워크플로 트리거로
Bitbucket에서 push가 발생하면 webhook이 Argo Events로 들어온다. Argo Events는 이 이벤트를 NATS(메시지 브로커)에 넣고, Sensor가 이를 구독해 조건에 맞는 이벤트만 걸러 Argo Workflows를 트리거한다. Jenkins job이 직접 webhook을 받아 바로 실행하던 방식과 달리, 이벤트 수신과 실행 트리거가 분리되어 있어 어떤 이벤트가 어떤 워크플로를 실행시켰는지 추적이 명확해진다.
Argo Workflows - 빌드까지만 책임진다
트리거된 워크플로는 빌드·테스트·이미지 빌드까지만 수행한다. 여기서 중요한 게 OPA(Open Policy Agent) 정책 게이트다. 이미지가 만들어진 직후, 클러스터에 들어가기 전에 정책 위반 여부(예: 금지된 베이스 이미지 사용, 필수 라벨 누락 등)를 자동으로 검사한다. 정책을 위반한 이미지는 이 시점에 걸러지고, 통과한 이미지만 Pulp로 올라간다. 즉 "일단 배포하고 나중에 문제를 발견하는" 방식에서 "배포되기 전에 정책을 강제하는" 방식으로 바뀐 것이다.
Pulp - 이미지/패키지의 단일 저장소
빌드 산출물은 Pulp에 저장한다. Nexus 대신 Pulp를 쓴 이유는 여러 포맷(컨테이너 이미지, 패키지 등)을 하나의 저장소에서 다룰 수 있고, 미러링 기능이 폐쇄망 동기화가 필요한 다른 프로젝트(위성영상 플랫폼군 전반)에서도 재사용 가능한 구조이기 때문이다.
ArgoCD - 배포는 "따라가는" 것이다
여기가 Jenkins 방식과 근본적으로 다른 지점이다. ArgoCD는 "배포를 실행"하지 않는다. 대신 Git 저장소에 선언된 매니페스트 상태와 클러스터의 실제 상태를 지속적으로 비교하고, 차이가 있으면 클러스터를 Git 상태에 맞춰 동기화한다.
Before (Jenkins push 배포)
Jenkins job 성공 = "배포했다"
이후 클러스터가 Git과 달라져도 감지 불가
After (ArgoCD GitOps)
Git 상태가 곧 "원하는 상태(desired state)"
클러스터가 그 상태와 다르면 ArgoCD가 감지하고 동기화
"배포했다"가 아니라 "Git과 일치한다"가 기준이 된다
누군가 클러스터에서 리소스를 수동으로 고쳐도, ArgoCD가 이를 drift로 감지하고 Git 상태로 되돌리거나 최소한 알려준다. 배포 이력도 Git 커밋 이력 자체가 곧 배포 이력이 된다.
결과
배포 소요 시간이 4분에서 30초로 줄어든 것(이전 글 참고)은 서비스 분리 효과가 크지만, 배포 방식이 push에서 GitOps로 바뀐 것도 여기에 기여했다. 더 크게 체감한 변화는 속도보다 신뢰성이다.
- 클러스터 상태가 Git과 다르면 자동으로 드러난다 - 예전처럼 "왜 이 파드만 이전 버전이지"를 수동으로 찾아다닐 일이 없다.
- 빌드와 배포가 분리되어 있어, 배포 실패의 원인이 빌드 문제인지 클러스터 반영 문제인지 파이프라인 단계에서 바로 구분된다.
- OPA 게이트 덕분에 정책 위반 이미지가 클러스터에 도달하기 전에 걸러진다 - 배포 후 정책 위반을 발견하고 롤백하는 것보다 훨씬 싸다.
Jenkins가 나쁜 도구였던 건 아니다. 다만 서비스가 9개로 늘고 "지금 클러스터 상태가 Git과 같은가"라는 질문에 답할 수 있어야 하는 시점이 오자, 빌드 도구와 배포 방식을 분리하는 쪽이 맞았다.