infra
Platform

모듈 맵

[Kubernetes] ArgoCD로 소스 코드 저장소와 클러스터 상태 동기화하기

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 28 / 29

[Kubernetes] ArgoCD로 소스 코드 저장소와 클러스터 상태 동기화하기

Git을 단일 진실 공급원으로 삼아 ArgoCD로 Kubernetes 배포를 자동화합니다

🚨INCIDENT ALERT
HIGH

운영 클러스터 상태가 Git 저장소와 달라졌지만 누가 언제 수동으로 바꿨는지 찾기 어렵습니다. 배포 이력이 명령어 기록에만 남으면 롤백과 감사가 모두 불안정해집니다. GitOps와 Argo CD는 Git을 기준 상태로 삼아 클러스터 변경을 추적하고 동기화합니다.

GitOps와 ArgoCD

배포 파이프라인이 복잡해질수록 "지금 프로덕션에 떠 있는 버전이 정확히 무엇인가"라는 질문에 답하기 어려워집니다. CI/CD에서 누군가 kubectl 명령을 잘못 실행했거나, 긴급 패치를 적용했거나, 설정을 직접 수정했다면 Git의 코드와 실제 클러스터 상태가 달라집니다. GitOps는 이 문제를 원천 차단하는 운영 패러다임입니다. Git 리포지토리의 선언적 매니페스트가 클러스터의 유일한 진실이 되고, 클러스터를 변경하는 유일한 방법은 Git 커밋입니다. ArgoCD는 이 원칙을 자동화하는 도구로, 60초마다 Git 상태를 폴링해서 클러스터와 차이가 생기면 자동으로 동기화합니다. 롤백은 git revert 한 줄이고, 배포 이력은 곧 커밋 이력입니다. 이 모듈을 마치면 ArgoCD를 설치하고 Application을 정의해서 Git 커밋만으로 배포가 자동화되는 파이프라인을 구성할 수 있습니다.

이번 챕터에서 배울 것
  • 1GitOps 4대 원칙: 선언적, 버전 관리, 자동 적용, 지속적 조정
  • 2ArgoCD 설치 및 Application 정의 (source + destination + syncPolicy)
  • 3자동 동기화, prune, selfHeal 설정으로 드리프트 자동 교정
  • 4ApplicationSet으로 멀티환경/멀티클러스터 배포 자동화
  • 5배포 전략: Sync Waves와 Resource Hooks로 순서 제어
  • 6TroubleCase: Sync 실패 — 매니페스트 오류와 RBAC 권한 문제
실습 환경 준비
ArgoCD 네임스페이스 생성 및 설치
kubectl create namespace argocd && kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
ArgoCD 파드 배포 대기
kubectl wait --for=condition=available --timeout=300s deployment/argocd-server -n argocd
ArgoCD CLI 설치 (Linux)
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64 && chmod +x argocd && mv argocd /usr/local/bin/
초기 admin 패스워드 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
ArgoCD UI 접근 (포트포워딩)
kubectl port-forward svc/argocd-server -n argocd 8080:443

GitOps 4대 원칙

GitOps 운영 흐름:

개발자                 Git 리포지토리           ArgoCD            Kubernetes
  │                        │                     │                   │
  │── git commit ─────────▶│                     │                   │
  │   (image tag 변경)      │                     │                   │
  │                        │── 폴링 (60초마다) ──▶│                   │
  │                        │                     │── 차이 감지       │
  │                        │                     │── kubectl apply ─▶│
  │                        │                     │                   │
  │                        │       (누군가 직접 kubectl edit 실행)    │
  │                        │                     │◀── 드리프트 감지  │
  │                        │                     │── selfHeal ──────▶│
  │                        │                     │   (Git 상태로 복원)│

4대 원칙:

  1. 선언적(Declarative): 시스템 상태를 명령어가 아닌 YAML로 선언
  2. 버전 관리(Versioned): 모든 변경은 Git 커밋으로 기록 — 누가, 언제, 무엇을 변경했는지 추적 가능
  3. 자동 적용(Automatically Applied): 승인된 변경(Git 커밋)은 자동으로 클러스터에 적용
  4. 지속적 조정(Continuously Reconciled): ArgoCD가 주기적으로 Git 상태와 클러스터 상태를 비교하고 차이를 교정
💡개념

GitOps vs 기존 Push 기반 CD — 보안과 운영 관점 차이

기존 CI/CD는 Jenkins나 GitHub Actions가 kubectl 명령을 직접 실행합니다. 이 방식에서 CI 서버는 클러스터 접근 권한(KUBECONFIG)을 보유해야 합니다. CI 서버가 해킹되면 클러스터 전체가 위험에 노출됩니다.

GitOps vs 기존 Push 기반 CD — 보안과 운영 관점 차이

기존 Push 방식 (취약점 존재):
CI 서버 ──[KUBECONFIG 보유]──▶ kubectl apply → Kubernetes
        (CI 서버 탈취 시 클러스터 무방비)

GitOps Pull 방식 (ArgoCD):
Git ◀── ArgoCD (클러스터 내부에서 Git 폴링)
     ArgoCD는 클러스터 내부 서비스어카운트로 동작
     외부에서 클러스터 접근 권한 불필요

ArgoCD는 클러스터 내부에서 실행되며 Git을 폴링합니다. 외부 시스템이 클러스터에 접근하는 것이 아니라, 클러스터가 Git에서 상태를 가져오는 Pull 방식입니다. CI/CD 서버에 클러스터 자격증명을 저장할 필요가 없어 공격 표면이 줄어듭니다.

또한 Git 리포지토리 접근 권한만 있으면 배포 이력 확인, 롤백, 특정 버전 재배포가 가능합니다. 별도의 배포 도구 권한 관리가 필요 없습니다.

ArgoCD Application 정의

기본 Application YAML

YAML
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payment-service
  namespace: argocd            # Application 객체는 argocd 네임스페이스에 생성
  labels:
    environment: production
    team: backend
spec:
  project: default

  # 소스: Git 리포지토리에서 읽을 매니페스트
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main        # 브랜치, 태그, 커밋 SHA 모두 가능
    path: apps/payment/production  # 리포지토리 내 경로

  # 목적지: 어느 클러스터, 어느 네임스페이스에 배포할지
  destination:
    server: https://kubernetes.default.svc   # 현재 클러스터
    namespace: production

  # 동기화 정책
  syncPolicy:
    automated:
      prune: true       # Git에서 삭제된 리소스 자동 제거
      selfHeal: true    # 수동 변경 감지 시 자동 복원
    syncOptions:
      - CreateNamespace=true   # 네임스페이스 없으면 자동 생성
      - PrunePropagationPolicy=foreground  # 종속 리소스 먼저 삭제
      - RespectIgnoreDifferences=true
    retry:
      limit: 5          # 실패 시 재시도 횟수
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

  # 비교 시 무시할 필드 (Kubernetes 자동 추가 필드)
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas   # HPA가 레플리카를 변경해도 OutOfSync로 표시 안 함

Helm Chart Application

YAML
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-ingress
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://kubernetes.github.io/ingress-nginx
    chart: ingress-nginx
    targetRevision: 4.8.3      # Chart 버전 고정
    helm:
      releaseName: ingress-nginx
      values: |                # 인라인 values (또는 valuesFiles 사용)
        controller:
          replicaCount: 2
          service:
            type: LoadBalancer
          metrics:
            enabled: true
  destination:
    server: https://kubernetes.default.svc
    namespace: ingress-nginx
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

ArgoCD CLI로 Application 관리

로컬 터미널
# CLI 로그인
argocd login localhost:8080 \
  --username admin \
  --password $(kubectl -n argocd get secret argocd-initial-admin-secret \
    -o jsonpath='{.data.password}' | base64 -d) \
  --insecure

# Application 목록
argocd app list

# 특정 Application 상태 확인
argocd app get payment-service

# 수동 동기화 (즉시 반영)
argocd app sync payment-service

# 특정 커밋으로 동기화 (rollback)
argocd app sync payment-service --revision abc1234

# Git과 클러스터 차이 확인
argocd app diff payment-service

# 롤백 (이전 성공 배포로)
argocd app rollback payment-service

# Application 삭제 (리소스도 함께 삭제)
argocd app delete payment-service --cascade

ApplicationSet으로 멀티환경 배포

YAML
# 환경 목록 기반 ApplicationSet
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: payment-service-all-envs
  namespace: argocd
spec:
  generators:
    # List 생성기: 환경 목록을 직접 정의
    - list:
        elements:
          - environment: dev
            cluster: https://dev-cluster.example.com
            namespace: payment-dev
            replicaCount: "1"
            imageTag: latest
          - environment: staging
            cluster: https://staging-cluster.example.com
            namespace: payment-staging
            replicaCount: "2"
            imageTag: "{{ .values.stagingTag }}"
          - environment: production
            cluster: https://prod-cluster.example.com
            namespace: payment-production
            replicaCount: "5"
            imageTag: "1.2.3"

  template:
    metadata:
      name: "payment-{{ environment }}"    # 각 환경별 Application 이름
      labels:
        environment: "{{ environment }}"
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: "apps/payment/{{ environment }}"
        helm:
          parameters:
            - name: replicaCount
              value: "{{ replicaCount }}"
            - name: image.tag
              value: "{{ imageTag }}"
      destination:
        server: "{{ cluster }}"
        namespace: "{{ namespace }}"
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
Kubernetes
# ApplicationSet 적용 후 생성된 Application 확인
kubectl apply -f applicationset.yaml -n argocd
argocd app list | grep payment
# NAME                  CLUSTER                            NAMESPACE
# payment-dev           https://dev-cluster.example.com    payment-dev
# payment-staging       https://staging-cluster.example.com payment-staging
# payment-production    https://prod-cluster.example.com   payment-production
🔍실행 후 확인할 것
  • NAME조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
  • STATUS/READYRunning, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
  • RESTARTS/EVENTS재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.

Git 디렉토리 기반 ApplicationSet

YAML
# apps/ 디렉토리의 각 서브디렉토리를 자동으로 Application으로 생성
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: all-apps
  namespace: argocd
spec:
  generators:
    - git:
        repoURL: https://github.com/myorg/k8s-manifests.git
        revision: main
        directories:
          - path: "apps/*/production"  # apps/payment/production, apps/order/production 등
  template:
    metadata:
      name: "{{ path.basenameNormalized }}"
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: "{{ path }}"
      destination:
        server: https://kubernetes.default.svc
        namespace: production
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Sync Waves와 Resource Hooks

배포 순서 제어가 필요할 때 사용합니다. 예를 들어 DB 마이그레이션을 앱 배포 전에 실행해야 하는 경우입니다.

YAML
# DB 마이그레이션 Job (wave: -1 → 앱보다 먼저 실행)
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  annotations:
    argocd.argoproj.io/hook: Sync          # Sync 단계에서 실행
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "-1"     # 음수일수록 먼저 실행
spec:
  template:
    spec:
      containers:
        - name: migration
          image: myapp:latest
          command: ["python", "manage.py", "migrate"]
      restartPolicy: Never
---
# 앱 Deployment (wave: 0 — 마이그레이션 성공 후 배포)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment
  annotations:
    argocd.argoproj.io/sync-wave: "0"
spec:
  # ...
---
# 스모크 테스트 Job (wave: 1 → 앱 배포 후 실행)
apiVersion: batch/v1
kind: Job
metadata:
  name: smoke-test
  annotations:
    argocd.argoproj.io/hook: PostSync      # 배포 완료 후 실행
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
    argocd.argoproj.io/sync-wave: "1"
spec:
  template:
    spec:
      containers:
        - name: test
          image: curlimages/curl
          command:
            - sh
            - -c
            - "curl -f http://payment:8080/health || exit 1"
      restartPolicy: Never

트러블슈팅

ArgoCD UI에서 Application이 SyncFailed 상태이고 에러 메시지가 failed to apply resource 또는 invalid spec으로 표시됩니다.

1단계: ArgoCD UI와 CLI에서 에러 상세 확인

로컬 터미널
# CLI에서 구체적인 에러 메시지 확인
argocd app get payment-service --show-operation

# 출력 예시:
# OPERATION:  sync
# PHASE:      Failed
# MESSAGE:    one or more objects failed to apply, reason:
#             Deployment.apps "payment" is invalid:
#             spec.template.spec.containers[0].resources.requests:
#             Invalid value: "2000m": must be less than or equal to cpu limit

# 전체 동기화 상세
argocd app sync payment-service --dry-run 2>&1

2단계: 매니페스트 로컬 검증

로컬 터미널
# Git에서 매니페스트 가져와서 로컬 검증
git clone https://github.com/myorg/k8s-manifests.git
cd k8s-manifests/apps/payment/production

# kubectl dry-run으로 유효성 확인
kubectl apply -f . --dry-run=server -n production

# YAML 문법 검증 (kubeval 사용)
kubeval deployment.yaml

# 일반 오류 패턴:
# - resource requests > limits (CPU/메모리)
# - 필수 필드 누락 (selector.matchLabels)
# - API 버전 오류 (deprecated API 사용)
# - 잘못된 imagePullPolicy 값

# deprecated API 확인
kubectl api-resources | grep <resource>
pluto detect-files -d . --output wide   # pluto CLI 도구

3단계: RBAC 권한 문제 진단

Kubernetes
# ArgoCD 서비스어카운트 확인
kubectl get sa -n argocd

# ArgoCD가 배포 대상 네임스페이스에 접근 권한이 있는지 확인
kubectl auth can-i create deployment \
  --as=system:serviceaccount:argocd:argocd-application-controller \
  -n production

# kubectl auth can-i list로 필요 권한 전체 확인
kubectl auth can-i '*' '*' \
  --as=system:serviceaccount:argocd:argocd-application-controller \
  -n production

# 출력: "no" 라면 ClusterRole 또는 RoleBinding 추가 필요

# ClusterRole 확인
kubectl get clusterrolebinding | grep argocd
kubectl describe clusterrolebinding argocd-application-controller

4단계: RBAC 권한 추가

YAML
# ArgoCD가 특정 네임스페이스에 배포할 수 있도록 권한 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: argocd-deploy
  namespace: production          # 배포 대상 네임스페이스
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: admin                    # 또는 필요한 최소 권한으로 커스텀 Role
subjects:
  - kind: ServiceAccount
    name: argocd-application-controller
    namespace: argocd

5단계: 개인 리포지토리 접근 오류

로컬 터미널
# Git 리포지토리 연결 상태 확인
argocd repo list
# STATUS   TYPE  NAME  REPO
# Failed   git         https://github.com/myorg/private-manifests.git

# 리포지토리 자격증명 추가 (SSH 키)
argocd repo add git@github.com:myorg/private-manifests.git \
  --ssh-private-key-path ~/.ssh/id_rsa

# 리포지토리 자격증명 추가 (토큰)
argocd repo add https://github.com/myorg/private-manifests.git \
  --username myuser \
  --password ghp_xxxxxxxxxxxx

# 연결 테스트
argocd repo get https://github.com/myorg/private-manifests.git

흔한 실수: OutOfSync가 되는 자동 추가 필드들

Kubernetes
# Kubernetes가 자동으로 추가하는 필드들이 OutOfSync를 유발할 때
# ignoreDifferences로 무시 설정
kubectl apply -f - << 'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payment-service
  namespace: argocd
spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas              # HPA가 변경하는 레플리카 수
    - group: ""
      kind: ConfigMap
      jsonPointers:
        - /data/last-applied          # 자동 추가 어노테이션
    - group: admissionregistration.k8s.io
      kind: MutatingWebhookConfiguration
      jsonPointers:
        - /webhooks/0/clientConfig/caBundle  # 자동 주입 CA 번들
EOF
💼
실무 맥락
현업 패턴

시나리오: 프로덕션 롤백 — 잘못된 배포를 3분 안에 이전 버전으로 복구

배포 직후 에러율이 급등했습니다. 기존 CI/CD라면 이전 이미지 태그를 찾아 파이프라인을 다시 실행해야 합니다. GitOps + ArgoCD라면 Git 히스토리에서 이전 커밋을 찾아 revert하거나 ArgoCD 롤백 명령 하나로 복구됩니다.

로컬 터미널
# 방법 1: ArgoCD CLI 롤백 (가장 빠름 — 이전 성공 배포로 즉시 복원)
argocd app history payment-service
# ID   DATE                           REVISION
# 42   2024-01-15 14:23:45 +0000 UTC  main (abc1234)  ← 정상
# 43   2024-01-15 15:10:02 +0000 UTC  main (def5678)  ← 문제 배포

argocd app rollback payment-service 42
# 즉시 이전 배포(커밋 abc1234) 상태로 복원

# 방법 2: Git revert (GitOps 원칙에 부합 — 변경 이력 보존)
git revert def5678 --no-edit
git push origin main
# ArgoCD가 60초 내 자동 감지하여 revert된 상태로 동기화

# 방법 3: 특정 커밋으로 수동 동기화
argocd app sync payment-service --revision abc1234

# 롤백 후 상태 확인
argocd app get payment-service
# 상태: Synced, Health: Healthy 확인

# 파드 교체 완료 확인
kubectl rollout status deployment/payment -n production
kubectl get pods -n production -l app=payment

Git 커밋 로그가 배포 로그가 됩니다. git log --oneline apps/payment/production/ 명령만으로 누가 언제 무엇을 배포했는지 전체 이력을 확인할 수 있습니다. 별도의 배포 로그 시스템이 필요 없습니다.

핵심 요약

개념설명실무 적용
ApplicationArgoCD 배포 단위 (source + destination)서비스당 하나 또는 환경당 하나
ApplicationSetApplication 템플릿 (멀티환경/클러스터)동일 앱의 dev/staging/prod 자동화
SyncGit → 클러스터 상태 적용automated + selfHeal로 완전 자동화
PruneGit 삭제 리소스 → 클러스터에서도 삭제활성화 필수 (기본값은 false)
Sync Wave리소스 배포 순서 제어DB 마이그레이션 → 앱 배포 → 스모크 테스트

리포지토리 구조 권장 패턴:

k8s-manifests/
├── apps/
│   ├── payment/
│   │   ├── base/           # 공통 매니페스트
│   │   ├── dev/            # dev 오버레이 (kustomize)
│   │   ├── staging/
│   │   └── production/
│   └── order/
└── infrastructure/
    ├── cert-manager/
    ├── ingress-nginx/
    └── monitoring/

다음 단계

  • Kustomize와 ArgoCD 연동으로 환경별 설정 오버레이
  • ArgoCD Image Updater로 이미지 태그 자동 업데이트
  • Argo Rollouts로 Blue/Green, 카나리 배포 고급 전략
  • ArgoCD Notifications로 Slack/PagerDuty 배포 알림 연동

지식 확인

퀴즈 — 4문제

Q1

GitOps의 핵심 원칙 중 '단일 진실 공급원(Single Source of Truth)'의 의미는?

Q2

ArgoCD Application의 syncPolicy.automated.prune: true 설정의 동작은?

Q3

ArgoCD Sync가 'OutOfSync' 상태를 보이지만 실제 서비스는 정상 동작 중일 때 가장 먼저 확인해야 할 것은?

Q4

ApplicationSet을 사용하는 주된 목적은?

0 / 4 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

kubectl로 nginx Pod를 생성하고 Deployment와 Service를 차례로 만들어 클러스터 외부에서 접근 가능한 상태까지 구성한다. K8s 3대 리소스의 역할과 관계를 직접 손으로 익힌다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점
linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점