명령형 배포가 만드는 문제

많은 팀이 kubectl apply나 스크립트로 클러스터를 직접 변경한다. 문제는 클러스터의 실제 상태가 코드 저장소 어디에도 남지 않는다는 점이다. 누가 언제 무엇을 바꿨는지 추적이 어렵고, 장애가 나면 "이전 상태"로 되돌릴 근거가 사라진다. 여러 명이 동시에 손대면 클러스터마다 설정이 미묘하게 달라지는 구성 드리프트(drift)가 쌓인다. 스테이징에선 되던 게 운영에서 안 되는 전형적인 원인이다.

GitOps의 핵심 원칙

GitOps는 배포를 명령이 아니라 선언으로 바꾼다. 원하는 상태(desired state)를 Git에 YAML로 정의하고, 클러스터 안의 컨트롤러가 그 상태를 지속적으로 클러스터에 맞춰 조정(reconcile)한다. 사람은 클러스터를 직접 만지지 않고 Git에 커밋할 뿐이다. 이 방식은 네 가지를 보장한다. 선언적 정의, 버전 관리와 감사 이력, 자동 조정, 그리고 드리프트 감지 및 자가 치유다.

Push 방식과 Pull 방식

기존 CI 파이프라인이 클러스터에 접속해 배포하는 방식(Push)과, 클러스터 내부 에이전트가 Git을 감시하다 스스로 당겨오는 방식(Pull)은 성격이 다르다.

구분Push (CI가 배포)Pull (Argo CD/Flux)
클러스터 자격증명CI에 노출클러스터 밖으로 안 나감
드리프트 감지없음지속 조정
배포 이력CI 로그 의존Git 커밋 = 이력

Pull 방식이 보안과 일관성 면에서 유리해 GitOps의 표준으로 자리 잡았다.

Argo CD로 애플리케이션 선언하기

Argo CD는 Application 리소스로 "어떤 Git 경로를 어떤 네임스페이스에 동기화할지"를 선언한다. 아래 설정은 매니페스트가 바뀌면 자동 동기화하고, 삭제된 리소스를 정리(prune)하며, 수동 변경을 되돌린다(selfHeal).

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payment-api
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://git.internal/team/payment-config.git
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: payment
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

환경별 구성은 Kustomize로 분리

같은 매니페스트를 환경마다 복사하면 관리가 무너진다. Kustomize의 base/overlay 구조로 공통 정의를 한 곳에 두고 환경 차이만 패치한다.

# overlays/production/kustomization.yaml
resources:
  - ../../base
replicas:
  - name: payment-api
    count: 4
images:
  - name: payment-api
    newTag: v1.8.2
patches:
  - path: resources-patch.yaml
    target:
      kind: Deployment
      name: payment-api

이미지 태그를 Git에 커밋하는 순간이 곧 배포다. 롤백은 이전 커밋으로 되돌리는 것과 동일하다.

이미지 태그 커밋과 배포 흐름

CI는 이미지를 빌드·푸시한 뒤, 애플리케이션 코드 저장소가 아니라 구성 저장소의 태그만 갱신한다. 코드와 배포 상태를 분리하는 것이 핵심이다.

#!/usr/bin/env bash
set -euo pipefail
TAG="v$(git rev-parse --short HEAD)"
docker build -t registry.internal/payment-api:"$TAG" .
docker push registry.internal/payment-api:"$TAG"

# 구성 저장소에서 태그만 교체 후 커밋
cd payment-config/overlays/production
kustomize edit set image payment-api="registry.internal/payment-api:$TAG"
git commit -am "deploy payment-api $TAG" && git push

실무에서 주의할 점

먼저 시크릿 관리다. 평문 Secret을 Git에 넣으면 안 된다. Sealed Secrets나 External Secrets Operator로 암호화하거나 외부 저장소를 참조한다. 둘째, selfHeal의 양날이다. 자가 치유는 긴급 수동 조치(핫픽스 kubectl edit)를 즉시 되돌려버리므로, 장애 대응 시 동작을 미리 이해해야 한다. 셋째, 단일 저장소 병목이다. 모든 팀이 하나의 구성 저장소를 공유하면 리뷰가 정체된다. 애플리케이션 단위로 저장소나 디렉터리를 나누고 CODEOWNERS로 권한을 분리하라. 마지막으로 조정 주기와 동기화 실패 알림을 반드시 모니터링에 연결해, "커밋했는데 반영 안 됨"을 조용히 넘기지 않도록 한다.