GitOps는 자동화를 통해 배포가 빠르고 Git이라는 버전 컨트롤 툴로 안정적인 운영이 가능해 현재 많은 사랑을 받고 있는 DevOps 방법론 중 하나이다.

그리고 개발자가 Git 저장소에 코드를 Push하는 것만으로 배포에 참여할 수 있게 하고, 운영자는 Git을 통해 모든 배포 상태를 명확히 파악할 수 있도록 하여 두 팀 간의 협업을 촉진한다는 장점도 있다.

이런 GitOps를 구현할 때는 크게 두 가지 방식으로 나뉜다. 바로, Pull 방식과 Push 방식이다.

이번 아티클에서는 GitOps의 두 가지 방식에 대해 비교하고, 나에게 맞는 방식을 찾아갈 수 있는 가이드를 마련했다.

하지만 이 두 방식을 비교하려면, 먼저 GitOps에 대한 이해가 필요하다.

혹시 아직 GitOps가 무엇인지 잘 몰라도 괜찮다. 이번 아티클의 내용을 소화하는 데에 필요한 GitOps의 핵심 원칙 2가지를 짚고 넘어가면 감을 잡을 수 있을 것이다.

핵심 원칙 1: 시스템의 최종 상태를 코드로 선언

GitOps의 주요 원칙 중 하나는 시스템의 최종 상태를 코드로 선언적 정의하는 것이다.

이는 ‘어떻게’ 도달할지를 기술하는 절차적 방식과 달리, ‘무엇’이 되어야 하는지를 명시하는 방식이다.

절차적 방식의 대표적인 예는 Shell 스크립트이다. 스크립트 안에 정의된 명령어들을 순서대로 실행하게끔 만든 파일 말이다.

반면에 Kubernetes의 리소스를 정의한 YAML 파일이 대표적인 선언적 접근 방식이다.

GitOps는 이렇게 파일로 선언된 상태를 현재 시스템의 상태와 비교한 다음, 서로 일치하도록 현재 시스템을 변경하는 방식이다.

파일로 선언된 상태를 확인 후 현재 시스템을 수정하는 역할을 수행하는 툴을 GitOps 에이전트라고 한다.

대표적인 GitOps 에이전트로 ArgoCD가 있다. Kubernetes 클러스터를 위한 GitOps 에이전트로, 클러스터의 최종 상태를 현재 클러스터 상태와 비교 후 불일치가 있다면 현재 클러스터의 구성을 변경하는 역할을 수행한다.

여기서 클러스터의 최종 상태란, 배포되어야 할 Deployment나 Service, Statefulset 등의 리소스와 설정(Configuration) 정보를 의미한다.

핵심 원칙 2: 정의된 시스템의 최종 상태는 Git에 저장

GitOps에서 시스템 최종 상태를 선언한 파일들은 Git 저장소에 저장되고 관리된다. 이를 좀 더 전문적으로 표현하자면, Git을 단일 진실 공급원(Single Source of Truth, SSoT)으로써 활용한다고 한다.

시스템 상태 정보에 대한 모든 변경 사항은 Git 커밋을 통해 기록되고 추척된다. 그래서 누가, 언제, 무엇을 변경했는지 명확하게 알 수 있으며, Pull Request를 통한 동료 검토 과정도 자연스럽게 추적 및 관리가 가능하다.

(출처: Cloud Native Computing Foundation)

GitOps의 핵심 원칙 2가지를 살펴봤으니, Pull 방식에 대해 먼저 알아보자. Pull 방식은 대부분 GitOps 툴의 기본값이기 때문이다.


Pull 방식은 저장소를 주기적으로 확인하는 것

GitOps의 Pull 방식이란, 시스템 내부에 설치된 에이전트가 Git 저장소로부터 최신 상태 정보를 주기적으로 당겨오는 것을 말한다.

ArgoCD를 예로 들어보자.

Kubernetes 클러스터 내에 설치된 ArgoCD가 Git 레파지토리를 일정 주기로 확인해 클러스터의 최종 상태 정보를 가져온다.

그리고 최종 상태 정보와 현재 클러스터의 상태 정보를 비교한다.

만약 최종 상태 정보에 정의된 Deployment가 현재 클러스터에 배포되어 있지 않다면, ArgoCD는 이를 불일치(Drift)로 인지해 현재 클러스터에 해당 Deployment를 배포하는 식이다.

이 Pull 방식은 외부 시스템에 클러스터 접근 권한을 부여할 필요가 없어 보안적으로 매우 유리하다.

클러스터 내에 설치된 ArgoCD는 시스템의 최종 상태 정보가 저장되는 Git 레파지토리에 접근할 수만 있으면 되는 것이다.

그래서 클러스터의 경계를 더욱 명확하게 하고 공격 표면을 줄이는 효과가 있다.

Pull 방식의 단점은 리소스 낭비와 지연성(Latency)이다.

아까 Pull 방식은 일정 주기로 Git에 저장된 시스템의 최종 상태를 가져오는 것이라고 했다.

GitOps 에이전트가 주기적으로 해당 Git 레파지토리를 확인하려면 그만큼 에이전트가 주기적으로 리소스를 쓰게 된다. Git 레파지토리에 별다른 업데이트가 없어도, 에이전트는 계속 레파지토리와 현재 시스템의 불일치를 확인할 수밖에 없는 것이다.

그리고 일정 주기로 레파지토리를 확인한다는 것은, 그 주기가 다시 오기 전까지는 Git에 최종 상태가 업데이트되어도 바로 반영이 안 된다는 뜻이기도 하다.

이는 1분 1초가 급박한 긴급 업데이트를 해야 할 때 특히 치명적일 수 있다.

이렇게 Pull 방식에는 장점뿐만 아니라 단점도 명확하기 때문에, 사람들은 GitOps의 또 다른 방식을 함께 염두하는 것이다. 바로 Push 방식이다.


그렇다면 Push 방식은 어떨까?

기존의 Pull 기반 방식이 정해진 주기마다 변경 사항을 확인하는 반면, Push 방식은 특정 이벤트가 발생했을 때 동기화를 즉시 시작한다.

Push 방식에 대해서는 이미 익숙할 수도 있다. DevOps라고 하면 쉽게 떠오르는 Jenkins가 Push 방식으로 Webhook 트리거를 받아서 동작하는 대표적인 툴이기 때문이다.

물론 Jenkins는 CI/CD 툴이라서 GitOps와는 거리가 멀다고도 할 수 있다. 하지만 이 둘이 공유하는 Push 방식은 매우 유사하다.

GitOps의 Push 방식을 예를 들어 설명해보겠다.

시스템의 최종 상태가 저장되는 Git 저장소에 새로운 커밋이 푸시되었다고 해보자. 그럼 GitHub의 Webhook 등을 통해 GitOps 에이전트에 레파지토리 업데이트 알림이 전달되고, 이때 에이전트가 해당 Git 레파지토리를 가져와 현재 시스템 상태와 비교 후 불일치가 있다면 현재 시스템을 조정할 수 있는 것이다.

Push 방식의 장단점은 Pull 방식과 반대라고 보면된다.

Git 레파지토리가 수정되는 시점에 GitOps 에이전트가 바로 시스템의 상태 불일치를 확인하기 때문에 지연성이 줄어든다. 주기적인 체크가 없기 때문에 리소스 낭비도 적다.

반면에 Push 방식을 구현하기 위해서는 외부에서 시스템으로 접근할 수 있는 권한을 추가해야 한다. 방금 전 예로 든 GitHub Webhook이 시스템 내에 설치된 GitOps 에이전트로 전달될 수 있는 통로가 필요한 것이다. 그래서 시스템의 공격 표면이 넓어지고 운영 부담이 추가될 수 있다는 단점이 있다.


마무리

지금까지 GitOps의 Pull 방식과 Push 방식을 비교하고 각 장단점을 알아봤다.

이제 자연스레 아래와 같은 질문이 떠오를 것이다.

그럼 우리 팀은 어떤 방식을 취해야 할까?

위에서 정리한 Pull 방식과 Push 방식의 장단점이 바로 이런 질문에 대한 답을 찾아가는 데에 도움이 된다.

대부분 GitOps 툴의 기본값은 Pull 방식이다. 그래서 지금 방식을 유지할지, 아니면 Push 방식으로 넘어갈지가 핵심 문제일 것이다.

만약 현재 Pull 방식이라면 시스템의 최종 상태가 업데이트되고 현재 시스템의 상태가 동기화되기까지 걸리는 시간이 조직에서 허용되는 수준인지 체크해보자. 그리고 지금 수준이 큰 문제가 안 된다면 Pull 방식을 유지해도 무관하다.

만약 Push 방식으로 넘어가야 한다면, 또는 현재 Push 방식이라면 외부 접근 권한을 철저히 관리하는 것이 무엇보다 중요하다. GitOps 운영에 꼭 필요한 IP 대역이나 포트번호만 열어두는 화이트리스트 접근법이 필수다.

GitOps 방식으로 인프라를 관리하고 있다면 이번 아티클이 GitOps를 바라보는 시각을 조금 더 넓혀줬길 하는 마음이다.


함께 읽어보면 좋은 아티클