coredot.today
ArgoCD와 GitOps: Git에 푸시하면 쿠버네티스가 알아서 배포한다
블로그로 돌아가기
ArgoCDGitOpsKubernetes배포DevOpsCD자동화

ArgoCD와 GitOps: Git에 푸시하면 쿠버네티스가 알아서 배포한다

kubectl apply를 수동으로 치는 것은 FTP 시대와 다를 바 없다. Git 저장소를 단일 진실의 원천으로 삼고, ArgoCD가 쿠버네티스 클러스터를 자동으로 동기화하는 GitOps 패러다임의 모든 것을 풀어본다.

코어닷투데이2026-04-0150

들어가며: kubectl apply를 수동으로 치는 것은 FTP 시대와 다를 바 없다

2000년대 초반, 웹사이트를 배포하는 방법은 단순했다. FTP 클라이언트를 열고, 파일을 끌어다 서버에 올린다. 누가, 언제, 무엇을 올렸는지는 아무도 몰랐다. 실수로 잘못된 파일을 올리면? 되돌리는 방법도 없었다.

20년이 지난 2026년, 쿠버네티스가 인프라의 표준이 되었다. 그런데 배포 방식을 가만히 들여다보면 — FTP 시대와 비슷한 패턴이 보인다.

hljs language-bash
# 누군가의 노트북에서...
kubectl apply -f deployment.yaml
kubectl set image deployment/my-app my-app=my-registry/my-app:v1.2.3

이것이 문제다:

  • 누가 이 명령을 실행했는가?
  • 이전 상태는 무엇이었는가?
  • 문제가 생기면 어떻게 롤백하는가?
  • 이 명령을 실행한 사람의 노트북이 진실의 원천(Source of Truth) 인가?

수동 kubectl apply의 세계에서는 클러스터의 현재 상태가 곧 진실이다. 그런데 그 상태가 어떻게 만들어졌는지는 아무도 모른다. 클러스터에 직접 접근할 수 있는 사람 누구나 상태를 바꿀 수 있고, 그 변경은 어디에도 기록되지 않는다.

💡
핵심 질문: "지금 운영 클러스터에 배포된 것이 정확히 무엇인가?"에 대해 팀원 모두가 동일한 답을 할 수 있는가? 그 답의 근거가 Git 커밋 해시가 아니라 "내가 어제 적용한 것 같은데…"라면, 당신의 팀은 사실상 FTP 시대에 머물러 있는 것이다.

GitOps는 이 문제의 근본적인 해결책이다. 그리고 ArgoCD는 GitOps를 쿠버네티스에서 실현하는 가장 널리 쓰이는 도구다.


1. GitOps: Git이 단일 진실의 원천이다

GitOps를 자동화된 스마트 창고로 비유한 일러스트

GitOps의 탄생

2017년, 쿠버네티스 모니터링 회사 Weaveworks의 CEO Alexis Richardson이 "GitOps"라는 용어를 만들었다. 블로그 포스트 "GitOps — Operations by Pull Request"에서 제안한 개념은 단순했다:

"운영 환경의 원하는 상태(desired state)를 Git에 선언적으로 정의하고, 자동화된 에이전트가 실제 상태(actual state)를 원하는 상태에 맞춰 수렴시킨다."

전통적 배포는 명령형(imperative) — "이 이미지를 이 서버에 배포해라"라는 명령이다. GitOps는 선언형(declarative) — "이 시스템은 이런 상태여야 한다"고 정의하면, 도구가 알아서 그 상태를 만든다.

GitOps의 4가지 원칙

OpenGitOps 프로젝트(CNCF 샌드박스)가 정의한 공식 원칙이다:

원칙 1
선언적(Declarative) — "nginx를 3개 실행해라"가 아니라 "nginx Replica는 3이다"로 정의한다.
원칙 2
버전 관리(Versioned & Immutable) — 원하는 상태는 Git에 저장. 모든 변경은 커밋으로 추적되고 불변이다.
원칙 3
자동 풀링(Pulled Automatically) — Git에 머지되면 에이전트가 자동으로 가져와 적용한다.
원칙 4
지속 조정(Continuously Reconciled) — 실제 상태와 원하는 상태를 지속 비교하고, 차이가 나면 자동 수정한다.

핵심은 하나: Git이 단일 진실의 원천(Single Source of Truth) 이다.

GitOps가 해결하는 문제들

문제전통적 배포GitOps
감사 추적"누가 변경했지?"Git 커밋 로그에 전부 기록
롤백"이전 상태가 뭐였지?"git revert로 즉시 복원
환경 일관성직접 접근으로 드리프트 발생자동 조정으로 드리프트 차단
코드 리뷰배포 스크립트를 누가 리뷰?PR/MR로 인프라 변경도 리뷰
접근 제어kubectl 권한 = 배포 권한Git 권한 = 배포 권한

2. Push 기반 vs Pull 기반 CD: 왜 Pull이 쿠버네티스에 맞는가

Push 기반 배포

전통적 CI/CD는 Push 기반이다. Jenkins, GitHub Actions 같은 CI 서버가 빌드·테스트 후 직접 클러스터에 밀어 넣는다.

개발자가 코드 Push
CI 서버가 빌드·테스트
CI 서버가 kubectl apply

문제점:

  • CI 서버에 클러스터 접근 권한 필요 — CI가 해킹되면 운영 클러스터가 뚫린다
  • SPOF(단일 장애 지점) — Jenkins가 죽으면 배포 불가
  • 드리프트 미감지 — 누가 kubectl로 직접 변경해도 CI 서버는 모른다
  • CI와 CD의 결합 — 빌드·테스트와 배포가 하나의 파이프라인에 섞임

Pull 기반 배포

GitOps의 Pull 기반은 방향이 반대다. 클러스터 안의 에이전트가 Git을 주기적으로 감시하고, 변경을 스스로 당겨온다(pull).

개발자가 Git에 Push
Git 저장소 (원하는 상태)
클러스터 내 에이전트가 Git 감시
차이 감지 시 자동 Sync

장점:

  • CI 서버에 클러스터 접근 권한 불필요 — 외부→클러스터 인바운드 접근이 필요 없다
  • 지속적 조정 — 3분(기본)마다 Git과 클러스터를 비교, kubectl 직접 변경도 되돌린다
  • CI와 CD 분리 — CI는 이미지 빌드까지만, 배포는 클러스터 에이전트의 몫
💡
보안 관점: Push 기반에서 CI 서버는 kubeconfig를 가져야 한다. CI가 뚫리면 운영 클러스터에 무엇이든 배포할 수 있다. Pull 기반은 클러스터 밖→안 접근이 없으므로 공격 표면이 대폭 줄어든다.

3. ArgoCD: 역사와 아키텍처

ArgoCD의 탄생

2018년, Applatix(이후 Intuit에 인수)의 엔지니어들이 ArgoCD를 만들었다. 미국 최대 세무 소프트웨어 기업 Intuit은 수천 개의 마이크로서비스를 쿠버네티스에서 운영하고 있었고, 안정적 GitOps 배포 도구가 필요했다.

CNCF Graduated 프로젝트(2022년 12월 승격). Kubernetes, Prometheus, Envoy와 같은 최고 성숙도 등급이다.

18,000+ GitHub Stars K8s CD 도구 중 최다
900+ 컨트리뷰터 활발한 오픈소스 커뮤니티
Intuit, Red Hat, IBM 주요 사용 기업 프로덕션 검증 완료

아키텍처

ArgoCD는 쿠버네티스 클러스터 안에서 동작하는 컴포넌트들로 구성된다.

ArgoCD 아키텍처
API Server Web UI, CLI, gRPC API 진입점. 인증·RBAC·이벤트 전달
Repo Server 매니페스트 생성 Git 클론 후 Helm/Kustomize를 최종 K8s 매니페스트로 렌더링
Application Controller 핵심 루프 원하는 상태(Git)와 실제 상태(클러스터)를 비교, 차이 시 Sync
Redis 렌더링 결과와 상태를 캐싱
Dex (선택) OIDC/SAML/LDAP SSO 연동
Notification Controller Slack, 이메일, 웹훅 알림

Application Controller가 핵심이다. K8s 컨트롤러 패턴을 그대로 따른다 — 관찰(Observe) → 비교(Diff) → 행동(Act) 루프를 무한 반복.


4. 핵심 개념: Application, Project, Sync, Health

Application

ArgoCD의 가장 기본 단위. "이 Git 경로의 매니페스트를 이 클러스터 네임스페이스에 배포하라"를 정의한다.

hljs language-yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-nextjs-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/k8s-manifests.git
    targetRevision: main
    path: apps/my-nextjs-app
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true       # Git에서 삭제된 리소스를 클러스터에서도 삭제
      selfHeal: true     # 직접 변경을 Git 상태로 되돌림

Project (AppProject)

Application들을 논리적으로 그룹화하고 접근 제어를 적용하는 경계다.

hljs language-yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-frontend
  namespace: argocd
spec:
  sourceRepos:
    - 'https://github.com/my-org/frontend-*'
  destinations:
    - namespace: 'frontend-*'
      server: https://kubernetes.default.svc
  clusterResourceWhitelist: []   # 클러스터 수준 리소스 배포 불가

이렇게 하면 프론트엔드 팀은 자기 네임스페이스에만 배포할 수 있다. ClusterRole이나 PersistentVolume은 건드리지 못한다.

Sync Status와 Health Status

상태 종류정상진행 중문제
Sync StatusSynced — Git = 클러스터Syncing — 동기화 중OutOfSync — Git ≠ 클러스터
Health StatusHealthy — 모든 Pod ReadyProgressing — 롤링 업데이트 중Degraded — CrashLoopBackOff 등

ArgoCD 대시보드에서 Synced + Healthy가 우리가 원하는 상태다.


5. ArgoCD 동작 원리: 감시 → 비교 → 동기화

ArgoCD가 Git 저장소를 보안 카메라처럼 감시하는 일러스트

감시
Repo Server가 Git 저장소를 3분 간격으로 폴링. 웹훅 설정 시 Push 즉시 감지.
렌더링
Repo Server가 Helm/Kustomize/순수 YAML을 최종 K8s 매니페스트로 렌더링. Redis에 캐싱.
비교 (3-Way Diff)
Application Controller가 원하는 상태(Git), 실제 상태(클러스터), 마지막 적용 상태를 3자 비교. K8s 자동 추가 필드와 사용자 의도적 변경을 구분한다.
동기화
차이가 있으면 OutOfSync 표시. 자동 동기화 설정 시 kubectl apply를 대신 실행. 수동 모드라면 사용자가 Sync 버튼을 누를 때까지 대기.
건강 확인
동기화 후 리소스의 Health를 확인. Pod Ready 여부, Service Endpoint 존재 여부 등.

이 루프는 무한 반복된다. GitOps 원칙 4번 "지속 조정"의 구현이다.

왜 3-Way Diff인가?

쿠버네티스 컨트롤러들이 리소스에 status, metadata.generation 등 필드를 자동 추가한다. 2-way diff만 하면 이런 자동 필드 때문에 항상 OutOfSync로 나온다. 3-way diff는 Git 원하는 상태 + 클러스터 실제 상태 + 마지막 적용 상태(last-applied-configuration 어노테이션) 를 비교하여 "사용자 의도적 변경"과 "시스템 자동 변경"을 구분한다.


6. 선언적 설정: App of Apps와 ApplicationSet

App of Apps 패턴

ArgoCD Application 자체가 K8s CRD이므로, Application을 관리하는 Application을 만들 수 있다.

Root Application
App: frontend
App: backend
App: monitoring

Root Application이 Git의 apps/ 디렉토리를 감시하고, 각 Application YAML을 자동 관리. 새 서비스 추가는 Application YAML 하나를 Git에 Push하면 끝.

ApplicationSet: 멀티 클러스터 자동화

App of Apps보다 강력한 ApplicationSet은 템플릿 기반으로 여러 Application을 자동 생성한다.

hljs language-yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app-all-clusters
spec:
  generators:
    - clusters:
        selector:
          matchLabels:
            env: production
  template:
    metadata:
      name: 'my-app-{{name}}'
    spec:
      source:
        repoURL: https://github.com/my-org/k8s-manifests.git
        path: 'apps/my-app/overlays/{{metadata.labels.region}}'
      destination:
        server: '{{server}}'
        namespace: production

env: production 레이블이 있는 모든 클러스터에 자동으로 Application 생성. 서울, 도쿄, 미국 — 클러스터를 추가하면 ApplicationSet이 감지하고 배포한다.

주요 ApplicationSet Generator
Cluster Generator 등록된 클러스터 자동 탐색. 레이블 기반 필터링.
Git Generator Git 디렉토리 구조 기반으로 생성.
Pull Request Generator PR이 열리면 프리뷰 환경 자동 생성. 닫히면 삭제.

7. ArgoCD vs Flux vs Jenkins X

항목ArgoCDFluxJenkins X
CNCF 등급GraduatedGraduated없음
Web UI강력한 내장 UI없음 (별도 설치)기본 UI
아키텍처중앙 집중형분산형 (컨트롤러 조합)중앙 집중형
멀티 클러스터네이티브 허브-스포크클러스터별 설치제한적
RBAC세밀한 자체 RBACK8s RBAC 활용기본 수준
채택률 (2025)~70%~25%소수
🅰️
ArgoCD — 이런 팀에 맞다
강력한 Web UI와 가시성이 필요한 팀. 멀티 클러스터 중앙 관리. 세밀한 RBAC과 SSO 통합이 중요한 조직.
🅱️
Flux — 이런 팀에 맞다
가볍고 모듈화된 도구를 원하는 팀. 각 클러스터가 독립적으로 운영. K8s 네이티브 CRD 방식 선호.

8. 실전: Next.js 앱을 ArgoCD로 배포하기

전제 조건

  • 쿠버네티스 클러스터 (EKS, GKE, AKS, 또는 minikube)
  • ArgoCD 설치 완료
  • GitHub 저장소 2개: 앱 코드용, K8s 매니페스트용
💡
왜 저장소를 2개로 분리하는가? 같은 저장소에 넣으면 앱 코드 변경 때마다 ArgoCD가 반응하고, CI가 매니페스트의 이미지 태그를 업데이트하면서 무한 루프가 발생할 수 있다.

Step 1: Next.js 앱 컨테이너화

hljs language-dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

Step 2: CI 파이프라인이 이미지 태그를 Git에 업데이트

hljs language-yaml
name: CI
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build & Push Image
        run: |
          docker build -t ghcr.io/my-org/my-app:${{ github.sha }} .
          docker push ghcr.io/my-org/my-app:${{ github.sha }}
      - name: Update K8s Manifests
        run: |
          git clone https://github.com/my-org/k8s-manifests.git
          cd k8s-manifests/apps/my-app/base
          kustomize edit set image ghcr.io/my-org/my-app=ghcr.io/my-org/my-app:${{ github.sha }}
          git commit -am "chore: update image to ${{ github.sha }}" && git push

CI의 역할은 여기서 끝. 매니페스트 저장소에 커밋이 Push되면 ArgoCD가 나머지를 처리한다.

Step 3: K8s 매니페스트 (Kustomize)

k8s-manifests 저장소 구조
apps/my-nextjs-app/ base/ ← 공통 Deployment, Service overlays/ dev/ ← replicas: 1, 최신 이미지 staging/ ← replicas: 2, 테스트된 이미지 production/ ← replicas: 5, 검증된 특정 버전

Step 4: ArgoCD Application 등록

hljs language-bash
argocd app create my-nextjs-app \
  --repo https://github.com/my-org/k8s-manifests.git \
  --path apps/my-nextjs-app/overlays/production \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace production \
  --sync-policy automated \
  --auto-prune --self-heal

전체 흐름 요약

1
개발자가 Next.js 코드를 main에 Push
2
GitHub Actions가 이미지 빌드 후 매니페스트 저장소의 이미지 태그 업데이트
3
ArgoCD가 변경 감지 → 렌더링 → Diff → OutOfSync
4
자동 Sync로 롤링 업데이트 실행
5
Health Check 통과 → Synced + Healthy. Slack 알림 전송.

개발자는 코드를 Push한 것 외에 아무것도 하지 않았다. kubectl을 치지 않았고, 클러스터에 접근하지도 않았다.


9. 멀티 환경: dev → staging → production 프로모션

멀티 환경 배포를 러시아 인형(마트료시카)에 비유한 일러스트

Kustomize 오버레이 전략

각 환경별 Kustomize 오버레이로 차이를 관리한다:

  • dev: replicas: 1, dev-latest 이미지, 리소스 제한 낮음
  • staging: replicas: 2, 테스트된 이미지 태그
  • production: replicas: 5, 검증된 특정 버전, 리소스 제한 높음

환경별 Sync 정책

devstagingproduction
자동 Sync자동 Sync + PR 승인수동 Sync (automated: null)
Push 즉시 배포이미지 태그 업데이트는 PR로PR 리뷰 + UI에서 Sync 클릭

production에서 automated: null은 Git에 변경이 있어도 자동 배포하지 않는다. OutOfSync 확인 후 팀 합의 → 수동 Sync. 이것이 안전장치다.

Sync Wave와 Hook

DB 마이그레이션이 앱보다 먼저 실행되어야 할 때:

hljs language-yaml
# Wave -1: DB 마이그레이션 (먼저 실행)
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
    argocd.argoproj.io/hook: PreSync
hljs language-yaml
# Wave 0: 앱 Deployment (마이그레이션 완료 후)
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "0"

Wave 숫자가 작은 리소스부터 순서대로 적용. Wave -1 성공 후 Wave 0이 시작된다.


10. 보안: RBAC, SSO, 감사 로그

ArgoCD RBAC

hljs language-csv
# 프론트엔드 팀: 자기 프로젝트 앱 조회/동기화만 허용
p, role:frontend-dev, applications, get, team-frontend/*, allow
p, role:frontend-dev, applications, sync, team-frontend/*, allow
p, role:frontend-dev, applications, override, team-frontend/*, deny

# 플랫폼 팀: 모든 권한
p, role:platform-admin, applications, *, */*, allow
p, role:platform-admin, clusters, *, *, allow

SSO 통합

GitHub, Okta, Azure AD 등과 OIDC/SAML로 연동. GitHub 팀 구조가 ArgoCD RBAC 그룹에 매핑되어, 새 팀원이 GitHub에 추가되면 ArgoCD 권한도 자동 부여.

시크릿 관리

Git에 평문 시크릿을 넣을 수 없다. 주요 대안:

시크릿 관리 방법
Sealed Secrets 공개키로 암호화 후 Git에 저장. 클러스터 내 컨트롤러가 복호화.
External Secrets Operator AWS Secrets Manager, Vault 등에서 시크릿 동기화. 가장 유연.
SOPS + KMS YAML 값만 암호화. ksops 플러그인으로 ArgoCD 통합.
HashiCorp Vault ArgoCD Vault Plugin으로 매니페스트 내 placeholder를 Vault 값으로 치환.

보안 체크리스트

필수
Git에 시크릿 평문 저장 금지. Sealed Secrets, SOPS, 또는 External Secrets Operator 사용.
필수
ArgoCD Web UI 공용 인터넷 차단. VPN 또는 SSO + MFA 필수.
권장
Git 브랜치 보호 규칙. main 직접 Push 금지, PR 리뷰 + 승인 필수. Git 권한 = 배포 권한.
권장
이미지 서명 검증. Cosign/Notation으로 서명된 이미지만 배포 허용.
💡
규정 준수(Compliance): 금융, 의료 등 규제 산업에서는 "누가, 언제, 무엇을 배포했는가" 감사 추적이 법적 요구사항이다. GitOps + ArgoCD 조합은 Git 커밋 로그 + ArgoCD 감사 로그로 이 요구를 자동 충족한다.

마치며: Git이 곧 인프라다

이 글에서 다룬 모든 내용은 하나의 문장으로 수렴한다:

"Git 저장소의 상태가 곧 인프라의 상태여야 한다."

Before: 수동 배포의 세계
"이거 누가 배포했어?" / "롤백 어떻게 해?" / "운영 서버에 뭐가 돌고 있는 거야?" — 불확실성과 불안의 연속.
After: GitOps + ArgoCD
모든 배포는 Git 커밋으로 추적. 롤백은 git revert. 클러스터 상태는 Git이 말해준다. 개발자는 코드만 Push하면 된다.
🎯
결론
GitOps는 도구 변경이 아니라 운영 철학의 전환이다. "누군가 수동으로 하던 일"을 "시스템이 자동으로 보장하는 일"로 바꾸는 것.

ArgoCD를 시작하고 싶다면, minikube로 로컬 클러스터를 띄우고 ArgoCD를 설치한 뒤, 간단한 nginx 앱을 Git에 올려 배포해 보라. 처음 대시보드에서 Synced + Healthy 초록색 상태를 보는 순간, FTP 시대로 돌아가고 싶지 않을 것이다.


참고 자료