ArgoCD + Kubernetes로 GitOps 무중단 배포 실전 적용기 — Rolling Update부터 Canary까지, Git 커밋 하나로 프로덕션을 안전하게 바꾸는 법
배포가 두려웠던 적 있으신가요? 저도 처음 Kubernetes를 운영할 때는 배포 날마다 Slack에 "배포합니다 🙏" 메시지를 올리고, 모니터링 대시보드를 뚫어지게 바라보며 식은땀을 흘렸습니다. 그런데 ArgoCD를 도입한 이후로 배포가 그냥 "평범한 Git PR 머지"가 됐습니다. PR 머지 후 5분 만에 프로덕션에 반영되는 게 일상이 됐고, 새벽 배포에도 알람 없이 자동 롤백되는 경험을 하고 나서는 야근이 자연스럽게 줄었습니다.
이 글은 GitOps의 개념부터 ArgoCD를 활용한 Rolling Update, Blue-Green, Canary 배포 전략까지 실전 코드와 함께 다룹니다. 단순히 "이런 게 있다"는 소개가 아니라, 실무에서 바로 적용할 수 있는 설정과 함께 흔히 빠지기 쉬운 함정도 솔직하게 공유할 예정입니다. 이 글을 다 읽고 나면 ArgoCD로 서비스 무중단 배포 파이프라인을 직접 설계할 수 있는 구체적인 그림이 생깁니다.
글의 흐름은 이렇습니다. 먼저 GitOps가 기존 CI/CD와 어떻게 다른지 개념을 잡고, ArgoCD의 핵심 구성요소를 이해합니다. 그다음 Rolling Update, Blue-Green, Canary 세 가지 배포 전략을 실제 YAML 코드와 함께 살펴보고, 각 전략에서 저희 팀이 실제로 맞닥뜨렸던 함정들을 공유합니다. 마지막에는 지금 바로 시작할 수 있는 단계별 가이드로 마무리합니다.
GitOps란 무엇인가 — "Git이 곧 진실"
GitOps는 인프라와 애플리케이션 배포의 모든 상태를 Git 저장소에 선언적으로 기록하고, 이를 실제 시스템과 지속적으로 동기화하는 운영 패러다임입니다. 전통적인 CI/CD 파이프라인이 "명령을 실행해서 상태를 바꾸는" 방식이라면, GitOps는 "원하는 상태를 선언하면 시스템이 알아서 맞춰주는" 방식입니다.
GitOps의 핵심 원칙: Git 저장소가 Single Source of Truth(단일 진실 공급원)입니다. 클러스터에 직접
kubectl apply를 치는 순간 이 원칙이 깨집니다.
ArgoCD는 이 원칙을 Kubernetes 위에서 구현해주는 도구입니다. CNCF 졸업 프로젝트(클라우드 네이티브 생태계의 공식 성숙도 인증 단계)로, Kubernetes 생태계에서 가장 널리 채택된 CD 도구 중 하나입니다. 2025년 4월에는 2021년 이후 첫 메이저 버전인 ArgoCD v3.0이 출시됐는데, 리소스 단위 RBAC과 API 서버 부하 경감 등 엔터프라이즈 수준의 기능들이 대거 추가됐습니다.
ArgoCD 아키텍처 — 핵심 구성요소 이해하기
ArgoCD를 처음 접하면 용어가 좀 낯선데, 실제로 몇 가지만 이해하면 전체 그림이 잡힙니다.
| 구성요소 | 역할 |
|---|---|
| Application CRD | "어떤 Git 저장소의 어떤 경로"를 "어떤 클러스터의 어떤 네임스페이스"에 배포할지 정의하는 핵심 리소스 |
| App of Apps 패턴 | Application들을 관리하는 상위 Application — 소수의 서비스를 계층적으로 관리할 때 적합 |
| ApplicationSet | 멀티 클러스터·멀티 환경 배포를 자동화 — 수십 개 환경도 템플릿 하나로 처리 |
| Sync Waves & Hooks | 배포 순서 제어 — DB 마이그레이션 먼저, 앱 배포 나중처럼 순서를 보장 |
App of Apps vs ApplicationSet: 두 패턴이 비슷해 보이는데, 선택 기준이 있습니다. App of Apps는 서비스 수가 적고 구조가 고정적일 때 적합합니다. 수동으로 Application YAML을 작성해서 계층 구조를 만드는 방식이라 직관적입니다. 반면 ApplicationSet은 환경(dev/staging/prod)이나 클러스터가 늘어날수록 빛을 발합니다. 새 클러스터를 추가할 때 YAML 한 줄만 추가하면 되기 때문입니다. 저희 팀은 처음엔 App of Apps로 시작했다가, 클러스터가 세 개를 넘어가는 시점에 ApplicationSet으로 마이그레이션했습니다.
가장 기본이 되는 Application 리소스는 이렇게 생겼습니다:
# k8s/argocd/apps/my-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-config
targetRevision: HEAD
path: k8s/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Git에서 삭제된 리소스는 클러스터에서도 제거
selfHeal: true # 클러스터 상태가 Git과 달라지면 자동 복원
selfHeal: true의 의미: 누군가 실수로kubectl edit으로 직접 수정해도, ArgoCD가 감지하고 Git 상태로 자동 복구합니다. 이게 GitOps의 Self-Healing 특성입니다. 운영 중에 "왜 내가 수정한 게 자꾸 원래대로 돌아가지?"라고 당황하는 분들이 가끔 있는데, 이 설정 때문입니다.
배포 전략 개요 — Rolling, Blue-Green, Canary
솔직히 처음엔 세 전략의 차이가 헷갈렸습니다. 한 줄씩 정리해보면:
| 전략 | 핵심 아이디어 | 언제 쓰면 좋은가 |
|---|---|---|
| Rolling Update | 파드를 하나씩 순차 교체 | 대부분의 상황, 리소스가 제한적일 때 |
| Blue-Green | 새 환경을 준비해두고 트래픽을 한 번에 전환 | 즉각 롤백이 필요하거나 DB 스키마 변경이 포함될 때 |
| Canary | 트래픽의 일부만 새 버전에 먼저 흘려보기 | 리스크가 클 때, 실제 트래픽으로 검증하고 싶을 때 |
Rolling Update는 Kubernetes 기본 제공이지만, Blue-Green과 Canary는 Argo Rollouts라는 별도 도구가 필요합니다. 이 점을 모르고 ArgoCD만 설치했다가 "왜 카나리 배포가 안 되지?" 하셨던 분들이 꽤 많습니다.
실전 적용
Rolling Update 실전 설정 — 프로덕션 체크리스트와 함께
Rolling Update는 단순해 보이지만, 설정 하나 빠지면 배포 중에 트래픽이 끊기는 상황이 생깁니다. 저도 처음에 readinessProbe 없이 배포했다가 새 파드가 뜨자마자 요청이 몰려서 500 에러를 냈던 기억이 있습니다. 안전한 Rolling Update를 위한 Deployment 설정을 보면 이렇습니다:
# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "2" # DB 마이그레이션(wave 1) 이후 실행
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 배포 중 항상 3개 파드 유지
maxSurge: 1 # 최대 4개까지 동시 실행 허용
template:
spec:
terminationGracePeriodSeconds: 60 # in-flight 요청 처리 완료 대기
containers:
- name: my-app
image: my-app:v2.0
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe: # 이게 없으면 unhealthy 파드에 트래픽 유입
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
---
# k8s/base/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 2 # replicas: 3 기준, 최대 1개만 동시에 종료 허용
selector:
matchLabels:
app: my-appminAvailable: 2와 replicas: 3의 관계를 짚어두면, "파드 3개 중 최소 2개는 항상 살아있어야 한다"는 의미입니다. 결국 한 번에 1개만 종료할 수 있으니, 노드 드레인이나 롤링 업데이트 중 서비스 가용성이 보장됩니다.
| 설정 | 미설정 시 문제 |
|---|---|
readinessProbe |
앱이 아직 준비 안 됐는데 트래픽 유입 → 에러 발생 |
terminationGracePeriodSeconds |
요청 처리 중 파드 강제 종료 → 커넥션 끊김 |
resources.requests |
스케줄러가 노드 리소스 부족으로 파드 배치 실패 |
PodDisruptionBudget |
노드 드레인 시 모든 파드 동시 종료 가능 |
DB 마이그레이션처럼 순서가 중요한 경우엔 Sync Waves를 활용하면 됩니다:
# k8s/jobs/db-migration.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
argocd.argoproj.io/sync-wave: "1" # Deployment(wave 2)보다 먼저 실행
argocd.argoproj.io/hook: PreSync
spec:
template:
spec:
restartPolicy: Never # Job은 반드시 명시 필요
containers:
- name: migrate
image: my-app:v2.0
command: ["python", "manage.py", "migrate"]Blue-Green with Argo Rollouts — 즉각 롤백이 필요할 때
Blue-Green은 "현재 운영 중인 파란 환경"과 "새 버전의 초록 환경"을 동시에 띄워두고, 검증 후 트래픽을 한 번에 전환하는 방식입니다. 즉각 롤백이 가능하다는 게 가장 큰 장점인데, DB 스키마 변경이 포함된 배포에서 저희 팀도 이 전략을 선택했습니다. 리소스가 일시적으로 두 배 필요하지만, scaleDownDelaySeconds 이후엔 구 버전이 자동으로 내려가므로 영구적으로 두 배를 쓰는 건 아닙니다.
먼저 트래픽을 받을 Service가 두 개 필요합니다:
# k8s/services/my-app-services.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-active # 실제 사용자 트래픽이 여기로
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-app-preview # 새 버전 검증용
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080그다음 Rollout 리소스를 정의합니다:
# k8s/rollouts/my-app-rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
blueGreen:
activeService: my-app-active # 현재 트래픽을 받는 서비스
previewService: my-app-preview # 새 버전 미리보기 서비스
autoPromotionEnabled: false # 수동 승인 후 전환
scaleDownDelaySeconds: 300 # 전환 후 5분간 구 버전 유지 (롤백 대비)
template:
spec:
containers:
- name: my-app
image: my-app:v2.0
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5새 버전 배포 후 my-app-preview 서비스로 검증하고, 문제없으면 다음 명령으로 전환할 수 있습니다. Argo Rollouts CLI는 공식 문서의 kubectl 플러그인 방식으로 설치할 수 있습니다:
# Argo Rollouts CLI로 수동 프로모션
kubectl argo rollouts promote my-app -n production
# 롤백이 필요할 때
kubectl argo rollouts abort my-app -n production저희 팀은 처음 6개월은 autoPromotionEnabled: false로만 운영했습니다. 트래픽 전환 전에 preview 서비스로 QA 팀이 직접 검증하는 과정을 거쳤고, 이 습관 덕분에 몇 번의 잠재적 장애를 사전에 막을 수 있었습니다.
Canary + Prometheus 자동 분석 — 실제 트래픽으로 검증하기
Canary는 "조금만 먼저 내보내서 실제 사용자로 검증"하는 방식입니다. Argo Rollouts의 AnalysisTemplate과 Prometheus를 연동하면 에러율이 기준치를 넘을 때 자동으로 롤백되도록 구성할 수 있습니다.
Canary도 Service가 두 개 필요합니다:
# k8s/services/my-app-canary-services.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-stable # 기존 안정 버전 트래픽
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-app-canary # 새 버전 카나리 트래픽
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080# k8s/analysis/success-rate.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
metrics:
- name: success-rate
interval: 2m
# result[0]: Prometheus 쿼리 결과의 첫 번째 스칼라 값 (성공률 0.0~1.0)
successCondition: result[0] >= 0.95 # 성공률 95% 이상이어야 통과
# failureLimit: 3 → 누적 3회 실패 시 롤백 (연속이 아닌 총합 기준)
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{app="my-app",status!~"5.."}[5m]))
/
sum(rate(http_requests_total{app="my-app"}[5m]))# k8s/rollouts/my-app-canary-rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
strategy:
canary:
canaryService: my-app-canary
stableService: my-app-stable
steps:
- setWeight: 10 # 10% 트래픽 → 새 버전
- pause: {duration: 5m} # 5분 관찰
- setWeight: 30 # 30%로 확대
- analysis:
templates:
- templateName: success-rate # 에러율 자동 체크
- setWeight: 60
- pause: {duration: 10m}
- setWeight: 100 # 문제없으면 전체 전환AnalysisTemplate의 역할: Canary 배포 중간에 Prometheus 쿼리를 실행해서 에러율, 레이턴시 등을 자동으로 평가합니다. 기준을 넘으면 사람 개입 없이 자동 롤백됩니다. 실제로 새벽 배포 중 알람 하나 없이 자동으로 롤백된 경험이 있는데, 그때 이 기능의 가치를 제대로 느꼈습니다. 아침에 출근해서 롤백 이력을 보고서야 밤새 무슨 일이 있었는지 알았습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| Self-Healing | 클러스터 상태가 Git과 달라지면 자동 감지 및 복원 — 실수로 한 직접 수정도 되돌아옴 |
| 감사 추적 | 모든 배포가 Git 커밋으로 기록 — "누가 언제 뭘 배포했는지" 완벽 추적 |
| 즉각 롤백 | 이전 커밋으로 revert 하나면 클러스터가 자동으로 이전 상태로 복구 |
| 시각적 UI | 리소스 토폴로지, 동기화 상태, Health 상태를 한눈에 파악 |
| RBAC + SSO | 세밀한 접근 제어와 사내 SSO(SAML, OIDC) 연동 지원 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 초기 도입 비용 | 설치, Git 저장소 구조 설계, 팀 교육까지 평균 1주일 이상 | 단일 서비스부터 점진적 도입 |
| Kubernetes 전용 | VM, 서버리스 환경은 지원 안 됨 | 비-K8s 환경은 별도 CD 도구 병행 |
| 고급 배포 전략 미내장 | Blue-Green, Canary는 Argo Rollouts 별도 설치 필요 | Argo Rollouts를 세트로 구성 |
| 시크릿 관리 | Git에 시크릿 직접 저장 불가 | External Secrets Operator 또는 Sealed Secrets 도입 |
| 멀티 환경 프로모션 부재 | dev → staging → prod 자동 프로모션 기능 없음 | Kargo 도구로 보완 |
| 대규모 시 UI 지연 | 수천 개 앱 이상에서 렌더링 느려짐 | ApplicationSet으로 분산 관리 |
External Secrets Operator(ESO): AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault 등 외부 시크릿 저장소와 Kubernetes Secret을 동기화해주는 오퍼레이터입니다. Git에는 시크릿 참조 경로만 저장하고, 실제 값은 외부 저장소에서 런타임에 주입됩니다.
실무에서 가장 흔한 실수
-
Readiness Probe 미설정 — 컨테이너가 뜨자마자 트래픽이 들어가서 초기 요청 실패가 발생합니다.
initialDelaySeconds와 함께 반드시 설정해두는 게 좋습니다. 저도 처음 배포에서 이 실수로 수십 초간 에러가 쏟아지는 경험을 했습니다. -
autoPromotionEnabled: true남발 — Blue-Green에서 검증 없이 자동 전환하면 Blue-Green의 의미가 없습니다. 저희 팀은 처음 6개월은 수동 승인으로만 운영하면서 팀 프로세스가 안정된 이후에 특정 서비스에 한해서만 자동화를 적용했습니다. -
App of Apps에서 동기화 순서 미고려 — 여러 Application을 계층 관리할 때 Sync Waves를 설정하지 않으면 의존성이 있는 리소스들이 순서 충돌로
Sync Failed상태에 빠집니다. CRD가 먼저 배포되기 전에 해당 CRD를 사용하는 리소스가 적용되는 상황이 대표적입니다. Application CRD에도argocd.argoproj.io/sync-wave어노테이션을 적용해서 순서를 명시적으로 관리하는 게 안전합니다.
마치며
처음 "배포합니다 🙏"를 입력하며 식은땀을 흘리던 그 시절이 생각납니다. ArgoCD를 도입하고 나서 배포는 그냥 코드 리뷰와 같은 일상적인 프로세스가 됐습니다. ArgoCD는 "배포를 코드처럼 다루는" GitOps 원칙을 가장 잘 구현한 도구이며, Argo Rollouts와 함께 구성하면 Rolling Update부터 메트릭 기반 Canary 배포까지 프로덕션 수준의 무중단 배포 파이프라인을 완성할 수 있습니다.
지금 바로 시작해볼 수 있는 3단계:
-
ArgoCD 설치 및 첫 Application 연결: 아래 명령으로 ArgoCD를 설치하고, 현재 운영 중인 서비스 중 하나의 Kubernetes 매니페스트를 Git 저장소에 올려 Application CRD를 생성해볼 수 있습니다.
kubectl port-forward svc/argocd-server -n argocd 8080:443으로 UI를 확인하면서 동기화 상태를 직접 눈으로 보는 게 이해에 큰 도움이 됩니다.bashkubectl create namespace argocd kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml -
Argo Rollouts 설치 및 Canary 전략 적용: 기존 Deployment를 Rollout 리소스로 마이그레이션하고, 위 Canary 예시 코드를 참고해서 10% → 50% → 100% 3단계 트래픽 이전을 설정해볼 수 있습니다. 처음엔 AnalysisTemplate 없이 수동 pause만으로 시작해도 충분합니다.
bashkubectl create namespace argo-rollouts kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml -
시크릿 관리 체계 갖추기: External Secrets Operator를 설치하고 AWS Secrets Manager 또는 팀이 사용하는 시크릿 저장소와 연동해볼 수 있습니다. 이 과정이 완료되면 진정한 의미의 "Git만 보면 되는" GitOps 환경이 완성됩니다.
다음 글: Kargo로 구현하는 멀티 스테이지 GitOps — dev에서 prod까지 자동 프로모션 파이프라인 설계하기
참고 자료
- Argo CD 공식 문서 | argo-cd.readthedocs.io
- Argo Rollouts 공식 문서 | argo-rollouts.readthedocs.io
- CNCF — Argo CD v3 발표 | cncf.io
- Implementing Zero-Downtime Deployments with Argo CD | OpsMx
- Zero-Downtime Rollbacks in Kubernetes with ArgoCD | DEV Community
- Automating Blue-Green and Canary Deployments with Argo Rollouts | Akuity
- 멀티 클러스터 GitOps with ArgoCD | 컴투스온 기술 블로그
- GitOps로 DevOps 효율성 극대화 | 카카오클라우드
- Argo CD Anti-Patterns | Codefresh
- Production-Grade GitOps with Argo CD | Medium
- Implementing GitOps and Canary Deployment with Argo Project and Istio | Tetrate
- Kargo 공식 사이트