infra
Platform

모듈 맵

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

0 / 29 완료

펼치기
0 / 29 완료0%

쿠버네티스 & GitOps · 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으로 멀티환경·멀티클러스터 배포를 자동화할 수 있다
  • 5Sync Waves와 Resource Hooks로 배포 순서를 제어할 수 있다
  • 6매니페스트 오류와 RBAC 권한 문제로 인한 Sync 실패를 진단할 수 있다
실습 환경 준비
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 + ArgoCD 동기화 루프 — 개발자가 Git(단일 진실 공급원)에 commit하면 ArgoCD가 60초마다 폴링해 클러스터와 차이를 감지·kubectl apply. 수동 kubectl edit로 드리프트가 생기면 selfHeal로 Git 상태 복원, 롤백은 argocd app rollback·git revert·특정 revision sync 3가지, ApplicationSet으로 멀티환경 자동화확대

4대 원칙:

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

Git 커밋 한 번이 클러스터에 반영되기까지 — reconcile 루프 6단계

매니페스트를 고쳐 git push만 했을 뿐인데 잠시 뒤 클러스터가 알아서 바뀝니다. 아무도 kubectl apply를 치지 않았는데 어떻게 반영될까요? ArgoCD가 Git(desired state)과 클러스터(live state)를 끊임없이 비교하고 차이를 메우는 reconcile 루프를 돌기 때문입니다. 이 루프를 단계로 알면 "커밋했는데 왜 안 떠", "왜 자꾸 OutOfSync야", "수동으로 바꿨더니 되돌아가네"를 어느 단계의 문제인지로 좁힐 수 있습니다. GitOps에서 "배포"는 단발 명령이 아니라 이 루프가 desired와 live의 차이를 0으로 만든 결과입니다.

TEXT
[개발자]  git commit + push → Git 저장소 (desired state = 유일한 진실)
   │
   ① 감지: ArgoCD가 Git을 폴링(기본 3분)하거나 웹훅으로 즉시 당겨옴
   │    → 매니페스트를 렌더(Helm/Kustomize)해 desired state 생성
   │
   ② 비교(diff): 렌더된 desired state ↔ 클러스터 live state를 필드 단위로 대조
   │
   ③ 판정: 차이가 있으면 OutOfSync, 없으면 Synced로 표시
   │
   ④ sync(apply): 렌더 매니페스트를 kubectl apply로 클러스터에 반영
   │    (automated=true면 자동, 아니면 수동 승인 대기)
   │
   ⑤ health 평가: 적용된 리소스 상태 확인 (Deployment rollout·파드 Ready)
   │
   ⑥ 루프 유지: Synced+Healthy 도달 후에도 계속 폴링하며 drift를 감지·교정
   ▼
[클러스터]  live state = Git desired state  (drift가 생기면 다시 ①로)

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일막히면 증상
① 감지repo-server가 저장소를 폴링(기본 3분)하거나 웹훅으로 당겨와 매니페스트를 렌더(Helm/Kustomize)웹훅 미설정이면 최대 폴링 주기만큼 반영 지연 · repo 자격증명·path 오류면 ComparisonError로 desired state 생성 실패
② 비교렌더된 desired state와 live state를 오브젝트·필드 단위로 대조K8s가 자동 주입한 필드(caBundle·기본값 등) 때문에 실차이가 없는데도 계속 다르게 보임 → ignoreDifferences 필요
③ 판정차이 있으면 OutOfSync, 없으면 Syncedimage가 latest 같은 가변 태그면 스펙이 그대로라 새 이미지인데도 Synced로 보이는 사각지대
④ sync렌더 매니페스트를 apply로 반영. selfHeal=true면 수동 kubectl edit로 생긴 drift를 Git 상태로 되돌림RBAC 부족·매니페스트 invalid → SyncFailed · prune=true면 Git에서 지운 리소스가 클러스터에서도 삭제
⑤ health 평가적용된 리소스의 상태 판정 (rollout 완료·파드 Ready)5분+ Progressing이면 이미지 pull 실패·probe 실패 등 rollout 문제
⑥ 루프 유지Synced+Healthy 이후에도 계속 비교하며 drift 교정폴링·웹훅이 죽으면 커밋해도 감지가 멈춰 반영이 안 됨

반영이 안 될 땐 argocd app get·argocd app diff로 지금 어느 단계에 있는지부터 확인합니다 — 아예 안 떠오면 ①(폴링/웹훅·렌더 실패), 계속 OutOfSync면 ②③(무시할 자동 필드), SyncFailed면 ④(RBAC·매니페스트), 오래 Progressing이면 ⑤(rollout)입니다. 수동 변경이 되돌아가는 것은 ④ selfHeal이 정상 동작한 것이고, 유지하려면 클러스터가 아니라 Git을 바꿔야 합니다.

💡개념

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

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

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

Push 방식 vs GitOps Pull 방식 — Push는 CI 서버가 KUBECONFIG로 kubectl apply(탈취 시 위험), Pull은 ArgoCD가 클러스터 내부 서비스어카운트로 Git을 폴링(외부 접근 권한 불필요)확대

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
🔍실행 후 확인할 것
  • argocd app list에서 SYNC 열 먼저 확인 — Synced이면 Git과 클러스터 상태 일치, OutOfSync이면 Git 변경이 아직 클러스터에 반영 안 된 것으로 argocd app diff <name>으로 차이 확인
  • HEALTH 상태 기준: Healthy=정상, Degraded=파드 이상, Progressing=배포 진행 중. 5분 이상 Progressing이면 Deployment rollout 문제로 kubectl get pods -n <ns>에서 파드 상태 확인
  • SYNC=OutOfSync이고 HEALTH=Healthy이면 → 클러스터에서 직접 수동 변경이 생긴 것. prune:true 설정 시 다음 sync에서 수동 변경이 삭제됨 — Git이 진실의 원본임을 항상 기억

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

심화 — 'Synced'가 '최신'을 뜻하진 않는다

💡개념

심화: ArgoCD의 diff는 매니페스트를 비교한다 — 이미지 태그와 동기화의 함정

ArgoCD가 Synced라고 표시하면 흔히 프로덕션이 최신 코드라고 안심하지만, Synced의 정확한 뜻을 알아야 이 착각에서 벗어납니다.

  • Synced = 선언과 실물의 매니페스트가 같다: ArgoCD는 Git이 선언한 오브젝트 스펙과 클러스터에 존재하는 오브젝트 스펙을 필드 단위로 비교합니다. 둘이 같으면 Synced입니다. 비교 대상은 YAML 스펙이지, 실제로 돌고 있는 컨테이너 이미지의 다이제스트가 아닙니다.
  • 가변(mutable) 태그가 만드는 사각지대: Git에 image가 myapp:latest(또는 :prod)로 적혀 있으면, CI가 같은 태그에 새 이미지를 밀어 넣어도 매니페스트 문자열은 그대로 latest입니다. ArgoCD 눈에는 Git도 latest, 클러스터도 latest라 완벽히 Synced인데, 파드는 예전에 pull한 오래된 이미지를 그대로 돌리고 있을 수 있습니다. selfHeal도 이 차이를 못 봅니다 — 되돌릴 스펙 차이가 없기 때문입니다.
  • 그래서 GitOps는 불변(immutable) 태그를 씁니다: 커밋 SHA나 시맨틱 버전(myapp:1.4.2, myapp:git-abc1234)처럼 한 번 쓰면 바뀌지 않는 태그로 배포하면, 새 배포는 반드시 매니페스트의 이미지 문자열을 바꾸는 커밋이 됩니다. 그 커밋이 곧 배포 이력이 되고, ArgoCD의 Synced가 비로소 이 정확한 이미지가 돈다와 같은 뜻이 됩니다.

정리하면 ArgoCD의 상태 판정은 선언 대비 실물의 형상 일치를 보장할 뿐, 태그 뒤에 숨은 이미지 내용까지 보장하진 않습니다. 이미지 프로모션은 태그 문자열을 바꾸는 방식으로 설계해야 GitOps가 제값을 합니다.

상황: CI가 이미지를 빌드해 레지스트리에 푸시했고 파이프라인은 초록불입니다. ArgoCD UI도 Synced에 Healthy. 그런데 사용자와 로그에는 여전히 옛 동작이 보입니다. 아무것도 실패하지 않았는데 새 버전이 안 떴습니다.

원인: 매니페스트가 image에 myapp:latest 같은 가변 태그를 씁니다. CI는 같은 latest 태그에 새 이미지를 덮어썼지만 Git의 매니페스트 문자열은 바뀌지 않았습니다. ArgoCD는 Git과 클러스터의 스펙이 동일하니 Synced로 보고, 새 이미지를 당길 이유를 못 느낍니다. 파드는 이미 노드에 캐시된 옛 이미지를 계속 돌립니다(imagePullPolicy가 IfNotPresent면 더 확실히 고착됩니다).

진단: argocd app get이 아니라 실제 파드가 무엇을 돌리는지 봅니다. kubectl get pod -o jsonpath로 각 파드의 image와 imageID(다이제스트)를 확인하고, 레지스트리의 latest 다이제스트와 비교합니다. 매니페스트의 이미지 문자열이 최근 배포에서 한 번도 바뀌지 않았다면 확정입니다. argocd app diff는 아무 차이도 안 보여줍니다 — 그게 바로 이 증상입니다.

해결: 이미지 태그를 커밋 SHA나 버전 번호 같은 불변 태그로 바꾸고, 배포를 매니페스트의 태그 문자열을 갱신하는 커밋으로 만듭니다(이미지 업데이트 자동화 도구나 CI가 매니페스트 리포에 태그를 써 넣는 방식). 급한 롤아웃이 필요하면 임시로 kubectl rollout restart로 파드를 새로 띄워 재pull하게 하되, 이는 임시방편일 뿐 근본 해결은 불변 태그 전환입니다.

💼
실무 맥락
현업 패턴

시나리오: 프로덕션 롤백 — 잘못된 배포를 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/·staging/·production/(kustomize 오버레이)
    • order/
  • infrastructure/cert-manager/, ingress-nginx/, monitoring/
실습 단계
1

ArgoCD 설치 및 초기 상태 확인

kubectl create namespace argocd kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml kubectl wait --for=condition=available deployment/argocd-server -n argocd --timeout=120s kubectl get pods -n argocd

예상 출력

NAME                                  READY   STATUS
argocd-application-controller-0       1/1     Running
argocd-argocd-redis-xxx               1/1     Running
argocd-repo-server-xxx                1/1     Running
argocd-server-xxx                     1/1     Running
2

ArgoCD 초기 비밀번호 확인 및 포트 포워딩

kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 -d kubectl port-forward svc/argocd-server -n argocd 8080:443 & echo 'ArgoCD UI: https://localhost:8080 (admin/위에서 확인한 비밀번호)'

예상 출력

abcdefghijklmnop
ArgoCD UI: https://localhost:8080 (admin/위에서 확인한 비밀번호)
3

ArgoCD Application 생성 (선언적)

kubectl apply -f - <<'EOF' apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: guestbook namespace: argocd spec: project: default source: repoURL: https://github.com/argoproj/argocd-example-apps.git targetRevision: HEAD path: guestbook destination: server: https://kubernetes.default.svc namespace: default syncPolicy: automated: prune: true selfHeal: true EOF kubectl get application guestbook -n argocd

예상 출력

NAME         SYNC STATUS   HEALTH STATUS
guestbook    Synced        Healthy
4

동기화 상태 및 배포된 리소스 확인

kubectl get pods -n default -l app=guestbook-ui kubectl get svc -n default | grep guestbook

예상 출력

NAME                  READY   STATUS    AGE
guestbook-ui-xxx      1/1     Running   1m
guestbook-ui   ClusterIP   10.96.x.x   <none>   80/TCP
5

실습 리소스 정리

kubectl delete application guestbook -n argocd kubectl delete namespace argocd

예상 출력

application.argoproj.io "guestbook" deleted
namespace "argocd" deleted

명령어·단축키 빠른 참조

이 모듈에서 ArgoCD 설치부터 Application 동기화·롤백, 드리프트/이미지 진단까지 쓴 kubectl·argocd·git 명령을 모았습니다.

명령어/단축키용도자주 쓰는 예
argocd app listApplication 목록·SYNC 상태SYNC=OutOfSync면 diff로 원인 확인
argocd app getApplication 상세·동기화 상태... --show-operation (sync 실패 사유)
argocd app diffGit과 클러스터 매니페스트 차이차이 없는데 미반영이면 가변 태그 함정 의심
argocd app sync수동 즉시 동기화... --revision abc1234 (특정 커밋) / --dry-run
argocd app rollback이전 성공 배포로 복원argocd app history로 ID 확인 후 rollback <id>
argocd app history배포 이력·리비전 확인롤백 대상 ID·커밋 SHA 파악
argocd app delete --cascade앱+리소스 함께 삭제종속 리소스까지 정리
argocd repo add프라이빗 Git 자격증명 등록--ssh-private-key-path / --username --password
kubectl apply -f install.yaml -n argocdArgoCD 설치kubectl create namespace argocd 먼저
kubectl wait --for=condition=available컨트롤러 배포 대기... deployment/argocd-server -n argocd --timeout=300s
kubectl get secret ... | base64 -d초기 admin 비밀번호 확인argocd-initial-admin-secret의 password
kubectl port-forwardArgoCD UI 로컬 접근kubectl port-forward svc/argocd-server -n argocd 8080:443
kubectl auth can-iArgoCD SA의 배포 권한 진단--as=system:serviceaccount:argocd:argocd-application-controller
kubectl get pod -o jsonpath파드 실제 image·imageID 대조Synced인데 옛 이미지일 때 다이제스트 비교
git revert / git logGitOps 롤백·배포 이력git revert <sha> --no-edit && git push

관련 모듈로 더 깊이:

다음 모듈 helm-basics에서는 복잡한 Kubernetes 매니페스트를 패키지로 관리하는 Helm을 다룹니다. Chart 설치, values.yaml 오버라이드, 롤백으로 멀티 환경 배포를 표준화하는 방법을 실습합니다.

지식 확인

퀴즈 — 8문제

Q1

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

Q2

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

Q3

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

Q4

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

Q5

운영자가 kubectl edit으로 클러스터의 Deployment를 직접 수정했다. ArgoCD가 selfHeal: true이면 어떻게 되는가?

Q6

GitOps(ArgoCD)의 'pull 기반 배포'가 CI가 클러스터에 직접 kubectl apply하는 'push 배포'보다 나은 점은?

Q7

[심화] ArgoCD가 어떤 앱을 Synced/Healthy로 표시한다. 이것이 보장하는 것은?

Q8

[심화] 파이프라인은 초록불이고 ArgoCD는 Synced/Healthy인데 새로 머지한 코드가 프로덕션에 안 보인다. 원인을 확인하는 가장 정확한 방법은?

0 / 8 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

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

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

이것도 배워보세요