GitOps로 인프라를 선언적으로 자동화하기 — Argo CD로 배포부터 자동 복구까지
인프라 변경을 서버에 직접 SSH로 접속해서 적용하던 시절을 기억하시나요? 저도 한동안 그렇게 했는데, 어느 날 스테이징과 프로덕션 환경 설정이 언제부터인지 조금씩 달라져 있다는 걸 발견하고 꽤 당혹스러웠던 기억이 납니다. 누가 바꿨는지, 왜 바꿨는지 추적할 방법이 없었거든요. 나중에 알고 보니 팀원 한 명이 급한 김에 직접 접속해서 설정 하나를 바꿨고, 그게 수개월 동안 묻혀 있었던 거였습니다. GitOps는 바로 그 문제를 풀기 위해 등장한 방식입니다.
"이건 DevOps나 인프라 담당자 얘기 아닌가요?"라고 느끼시는 분도 계실 텐데, 프론트엔드·백엔드 구분 없이 배포 파이프라인이 어떻게 돌아가는지 이해하는 순간 개발 속도가 달라집니다. 내 PR이 왜 아직 반영이 안 됐는지, 스테이징이랑 프로덕션 동작이 왜 다른지 — 이런 질문에 스스로 답할 수 있게 되거든요.
이 글에서는 GitOps의 4가지 핵심 원칙과 실제 워크플로를 이해하고, Argo CD 기반의 선언적 배포 파이프라인을 직접 구성할 수 있는 데까지 안내해 드릴게요. Kubernetes를 처음 접하는 분이라면 중간중간 짧은 설명을 참고하시면 더 빠르게 따라오실 수 있습니다.
핵심 개념
GitOps의 4가지 원칙
GitOps를 처음 접하면 "그냥 Git으로 배포하는 거 아닌가?"라고 생각하기 쉬운데, 조금 더 구체적인 원칙이 있습니다. 2017년 Weaveworks의 Alexis Richardson이 정리한 이 원칙들은 지금도 OpenGitOps 사양의 기반이 됩니다.
| 원칙 | 설명 | 핵심 포인트 |
|---|---|---|
| 선언적(Declarative) | "어떻게"가 아닌 "무엇을" 원하는지 정의 | YAML로 원하는 상태만 기술 |
| 버전 관리(Versioned) | 모든 변경 이력이 Git에 보존됨 | 언제든 git log로 감사 가능 |
| 자동 Pull | 에이전트가 Git 변화를 감지해 자동 적용 | 외부 Push가 아닌 클러스터가 스스로 Pull |
| 지속 조정(Reconciliation) | 실제 상태가 선언 상태와 다르면 자동 복구 | 드리프트를 사람이 아닌 시스템이 감지 |
저도 처음에 표를 보고 "원칙이야 당연한 소리 아닌가"라고 생각했는데, Pull 방식으로 운영해보고 나서야 이게 얼마나 실질적인 차이를 만드는지 체감했습니다.
선언적(Declarative) vs 명령형(Imperative): 명령형은 "서버에 Nginx를 설치하고, 포트 80을 열고, 서비스를 시작해라"처럼 절차를 기술합니다. 선언적은 "Nginx가 포트 80에서 실행 중인 상태"를 기술하고, 그 상태로 만드는 방법은 도구에게 맡깁니다.
드리프트(Drift): 실제 시스템의 상태가 Git에 선언된 상태와 달라지는 현상입니다. 누군가 긴급 패치를 위해 직접 kubectl로 변경하거나, 외부 요인으로 설정이 바뀔 때 발생합니다. GitOps 오퍼레이터는 이 드리프트를 지속적으로 감지하고 자동으로 복구합니다.
Pull 방식이 왜 중요한가
기존 CI/CD는 파이프라인이 클러스터에 직접 Push하는 구조였습니다. 이렇게 되면 파이프라인 서버가 클러스터 접근 자격증명을 들고 있어야 하고, 그 자격증명이 유출되면 클러스터 전체가 위험해집니다.
처음에 Pull 방식을 봤을 때 "왜 클러스터가 직접 당겨오는 거지?"라고 의아했는데, 자격증명 관련 사고를 한 번 목격하고 나서야 이게 얼마나 중요한지 체감했습니다.
GitOps의 Pull 방식은 반대로 동작합니다. Argo CD나 Flux 같은 오퍼레이터가 클러스터 안에서 Git을 지켜보다가 변경이 감지되면 클러스터 자신이 스스로 동기화합니다. 외부에 자격증명을 노출할 필요가 없죠.
[기존 Push 방식]
CI 서버 ---(클러스터 자격증명 필요)---> Kubernetes 클러스터
[GitOps Pull 방식]
Git 저장소 <---(감시)--- Argo CD (클러스터 내부)
|
자동 동기화
↓
Kubernetes 클러스터Kubernetes는 컨테이너를 클러스터 단위로 운영하는 플랫폼입니다. 여러 서버를 묶어 애플리케이션을 배포·스케일링·복구하는 역할을 합니다. 처음 접하신다면 "서버들을 하나처럼 다루는 운영체제" 정도로 이해하시면 됩니다.
전체 워크플로 한눈에 보기
개발자 PR 작성
↓
코드 리뷰 + 머지
↓
CI 파이프라인 (GitHub Actions 등)
- 테스트 실행
- 컨테이너 이미지 빌드
- 이미지 레지스트리에 Push
- 매니페스트 저장소의 이미지 태그 업데이트
↓
GitOps 오퍼레이터 (Argo CD / Flux)가 변경 감지
↓
클러스터에 자동 배포
↓
드리프트 발생 시 → 자동 복구실무에서 가장 많이 쓰이는 패턴은 GitHub Actions(CI) + Argo CD(CD) 조합입니다. CI와 CD의 역할을 명확히 분리해서, CI는 빌드와 테스트에만 집중하고 배포는 Argo CD가 전담하는 구조입니다. Flux도 비슷한 역할을 하는 경쟁 도구인데, Argo CD를 더 많이 선택하는 이유는 직관적인 Web UI, 멀티클러스터 지원, CNCF 졸업 프로젝트라는 안정성 때문입니다.
실전 적용
예시 1: Argo CD Application 선언
가장 기본적인 Argo CD 설정입니다. Application이라는 CRD(Custom Resource Definition — Kubernetes 기본 리소스 외에 추가로 정의한 타입)를 통해 "어떤 Git 저장소의 어떤 경로를 어느 클러스터에 배포할지"를 선언합니다.
# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/my-org/my-app-manifests
targetRevision: v1.2.0 # 실제 운영에서는 특정 태그 또는 커밋 SHA를 지정하는 것을 권장합니다 (HEAD는 안티패턴)
path: overlays/production # Kustomize 오버레이 경로
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Git에서 삭제된 리소스는 클러스터에서도 삭제
selfHeal: true # 수동 변경이 생기면 Git 상태로 자동 복구
syncOptions:
- CreateNamespace=true작성한 파일은 아래 명령으로 클러스터에 적용할 수 있습니다.
kubectl apply -f argocd-app.yaml| 필드 | 역할 |
|---|---|
repoURL |
매니페스트가 저장된 Git 저장소 |
targetRevision |
동기화할 태그, 브랜치, 또는 커밋 SHA |
path |
저장소 내 매니페스트 경로 |
automated.selfHeal |
드리프트 자동 복구 여부 |
automated.prune |
Git에서 삭제된 리소스 자동 정리 여부 |
예시 2: GitHub Actions로 이미지 태그 자동 업데이트
CI 파이프라인에서 새 이미지를 빌드한 뒤, 매니페스트 저장소의 이미지 태그를 자동으로 업데이트하는 패턴입니다. 이 커밋이 Argo CD의 트리거가 됩니다.
# .github/workflows/deploy.yaml
name: Build and Update Manifest
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to container registry
run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login my-registry -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin
- name: Build and push image
run: |
IMAGE_TAG=${{ github.sha }}
docker build -t my-registry/my-app:$IMAGE_TAG .
docker push my-registry/my-app:$IMAGE_TAG
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
- name: Update manifest repository
run: |
git clone https://github.com/my-org/my-app-manifests.git
cd my-app-manifests/overlays/production
# kustomize로 이미지 태그 업데이트 (base/kustomization.yaml에 images 블록이 있어야 작동합니다)
kustomize edit set image my-app=my-registry/my-app:$IMAGE_TAG
git config user.email "ci@my-org.com"
git config user.name "CI Bot"
git commit -am "chore: update image tag to $IMAGE_TAG"
git push
env:
GITHUB_TOKEN: ${{ secrets.MANIFEST_REPO_TOKEN }}
# MANIFEST_REPO_TOKEN: GitHub → Settings → Developer settings → Personal access tokens에서
# 매니페스트 저장소에 대한 'repo' 권한으로 발급 후, 앱 코드 저장소의 Secrets에 등록하세요.
# 기본 GITHUB_TOKEN은 다른 저장소에 push할 수 없어 403 오류가 납니다.저장소 직접 클론 대신 검증된 Action 활용:
git clone+git push방식은 토큰 관리와git config를 수동으로 처리해야 해서 실수가 잦습니다.peter-evans/create-pull-request나stefanzweifel/git-auto-commit-action같은 Action을 쓰면 훨씬 간결하게 처리할 수 있으니, 실무에 도입할 때는 이런 대안도 검토해보시면 좋습니다.
앱 코드 저장소와 매니페스트 저장소를 분리하는 이유: 두 저장소를 분리하면 배포 이력이 명확해지고, 인프라 변경 권한과 코드 변경 권한을 독립적으로 관리할 수 있습니다. 처음엔 번거롭게 느껴질 수 있지만, 팀이 커질수록 이 분리가 빛을 발합니다.
예시 3: Kustomize로 환경별 설정 분기
Kustomize는 Kubernetes 매니페스트를 오버레이 방식으로 환경별로 분기하는 도구입니다. kubectl에 기본 내장되어 있어 별도 설치 없이 사용할 수 있습니다.
manifests/
├── base/
│ ├── deployment.yaml # 공통 설정
│ ├── service.yaml
│ └── kustomization.yaml
└── overlays/
├── staging/
│ ├── kustomization.yaml # 스테이징 오버레이
│ └── replica-patch.yaml # replicas: 1
└── production/
├── kustomization.yaml # 프로덕션 오버레이
└── replica-patch.yaml # replicas: 3kustomize edit set image 명령이 제대로 동작하려면 base/kustomization.yaml에 images 블록이 미리 있어야 합니다. 처음 설정할 때 이걸 빠뜨리면 "왜 태그가 안 바뀌지?"라는 혼란이 생기기 쉽습니다.
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
images:
- name: my-app
newTag: latest # CI가 overlays에서 이 값을 덮어씁니다# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- path: replica-patch.yaml
images:
- name: my-app
newTag: "abc1234" # CI가 이 값을 자동으로 업데이트장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 완전한 감사 추적 | 모든 인프라 변경이 Git 커밋 이력으로 남아 언제, 누가, 왜 바꿨는지 추적 가능 |
| 즉각적 롤백 | git revert 한 줄로 이전 상태로 복구됩니다 |
| 드리프트 방지 | 누군가 kubectl로 직접 수동 변경해도 에이전트가 자동으로 Git 상태로 복원 |
| 보안 강화 | PR 리뷰·승인 프로세스를 통해 모든 인프라 변경을 통제할 수 있습니다 |
| Pull 방식 보안 | 클러스터가 자격증명을 외부에 노출하지 않아 공격 표면이 줄어듦 |
단점 및 주의사항
솔직히 학습 곡선 부분이 가장 큰 허들입니다. 저희 팀도 처음 3주는 설정만 붙잡고 있었거든요. 그래도 한 번 궤도에 오르면 그 편리함이 압도적이라, 포기하지 않고 붙잡길 잘했다는 생각이 듭니다.
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 시크릿 관리 | Git에 민감 정보를 그대로 저장할 수 없음 | Sealed Secrets, External Secrets Operator, HashiCorp Vault 중 하나를 선택 |
| 학습 곡선 | Kubernetes, Helm, Kustomize 등 선행 지식이 필요 | 작은 서비스부터 단계적으로 도입하는 것을 권장합니다 |
| 멀티클러스터 복잡도 | 클러스터 수가 늘어날수록 시크릿 관리 등 유지보수 부담 증가 | External Secrets Operator처럼 중앙화된 방식 도입 고려 |
| 문화적 전환 | 운영팀이 직접 서버에 접근하는 방식에서 벗어나야 함 | 점진적 도입과 내부 교육이 병행되면 좋습니다 |
실무에서 가장 흔한 실수
- 앱 코드 저장소에 시크릿을 그냥 커밋하는 경우 — 솔직히 저도 처음에 "어차피 프라이빗 저장소니까 괜찮겠지"라고 생각한 적 있는데, 2025년 기준 공개 저장소에 시크릿을 노출한 경험이 있는 조직이 61%에 달한다는 조사가 있습니다. Sealed Secrets나 External Secrets Operator를 처음부터 도입하는 것을 권장합니다.
- 하나의 저장소에 앱 코드와 매니페스트를 모두 관리하는 경우 — 초반엔 편하지만, CI 트리거와 GitOps 트리거가 섞이면서 파이프라인이 복잡해집니다. 초기부터 분리하는 편이 나중에 훨씬 편합니다.
selfHeal: true를 켜두고 수동 핫픽스를 시도하는 경우 — 긴급 상황에서 직접 kubectl로 패치했는데 몇 초 뒤에 Argo CD가 원래 상태로 되돌려버리는 상황을 경험하면 당황스럽습니다. 긴급 수정이 필요할 때는 먼저 자동 동기화를 일시 정지하거나, Git에 직접 커밋하는 흐름을 팀 내에 미리 공유해 두는 것이 좋습니다.
마치며
글 초반에 "누가 바꿨는지, 왜 바꿨는지 추적할 방법이 없었다"는 이야기를 드렸는데, GitOps를 도입하고 나면 그 질문의 답이 항상 Git 히스토리에 있습니다. git log 한 줄이면 충분합니다.
GitOps는 단순히 배포를 자동화하는 기술을 넘어, 팀 전체가 변경에 대한 신뢰와 가시성을 갖게 해주는 운영 문화입니다.
처음부터 전체 인프라에 적용하려 하면 부담이 클 수 있습니다. 아래 순서로 작은 것부터 시작해보시면 GitOps의 감각을 익히는 데 도움이 됩니다.
- Argo CD를 로컬 minikube에 설치해보기 — Argo CD 공식 설치 가이드를 참고해 설치한 뒤,
kubectl port-forward svc/argocd-server -n argocd 8080:443설정 후localhost:8080에서 Web UI를 통해 배포 상태를 직접 눈으로 확인해보시면 GitOps의 감각을 빠르게 익힐 수 있습니다. - 간단한 앱의 Kubernetes 매니페스트를 별도 Git 저장소로 분리하기 —
deployment.yaml,service.yaml을my-app-manifests저장소로 옮기고 Kustomize 구조(base/,overlays/)를 적용해보시면 환경별 설정 분기의 편리함을 체감할 수 있습니다. - GitHub Actions에서 이미지 빌드 후 매니페스트 저장소의 이미지 태그를 자동 커밋하도록 연결하기 — 위 예시 2의 워크플로를 참고해 CI가 Argo CD의 트리거 역할을 하도록 연결하면 기본적인 GitOps 파이프라인이 완성됩니다.
다음 글: Argo CD에서 멀티클러스터 환경을 관리하고 시크릿을 안전하게 다루는 실전 패턴(Sealed Secrets, External Secrets Operator)을 다룰 예정입니다.