Kubernetes 정복기: Helm + ArgoCD로 GitOps 파이프라인 구축 (Day 8)

문한성·4일 전
0

K8S 정복하기

목록 보기
8/9

2025년 11월 8일
수동 배포는 이제 그만! Git Push 한 번으로 자동 배포되는 마법

들어가며

Day 7에서 Ceph 분산 스토리지로 진정한 동적 프로비저닝을 경험했습니다. 이제 Day 8에서는 GitOps 패러다임의 핵심 도구인 Helm과 ArgoCD를 마스터하여 선언적 배포 자동화를 완성했습니다.

오늘 배운 것:
1. Helm의 철학과 Custom Chart 생성 (Chart.yaml, templates/, values)
2. 환경별 Values 관리 (values-dev.yaml, values-staging.yaml, values-prod.yaml)
3. ArgoCD 설치 및 아키텍처 이해 (Application Controller, Repo Server)
4. GitOps 워크플로우 (Git → ArgoCD → Kubernetes 자동 동기화)
5. Self-Heal 기능 (수동 변경 자동 복구)
6. Sync Waves와 Hooks (순차적 배포, DB 마이그레이션)
7. 다중 환경 배포 (하나의 차트, 세 가지 환경)


1. Helm이 뭐길래?

왜 Helm인가?

🤔 내가 이해한 것:

Kubernetes YAML을 직접 관리하다 보면 이런 문제가 생깁니다:

문제점:
❌ YAML 파일 수십 개 (Deployment, Service, ConfigMap, Secret, Ingress...)
❌ 환경별 복사본 (dev, staging, prod 각각 관리)
❌ 변수 관리 어려움 (이미지 태그, 포트, 리소스 등)
❌ 롤백 복잡함 (어떤 YAML을 되돌릴지?)
❌ 재사용 불가 (다른 프로젝트에서 복사-붙여넣기)

Helm의 해결책:
✅ 패키지 단위 관리 (Chart = 모든 리소스를 하나로)
✅ 템플릿화 ({{ .Values.image.tag }} 같은 변수)
✅ 환경별 Values 파일 (values-dev.yaml, values-prod.yaml)
✅ 버전 관리 (helm rollback으로 즉시 복구)
✅ Chart 재사용 (공식 차트 저장소, 자체 차트)

Kubernetes YAML vs Helm:

기존 YAML 방식:
myapp/
├─ deployment-dev.yaml
├─ deployment-staging.yaml
├─ deployment-prod.yaml
├─ service-dev.yaml
├─ service-staging.yaml
├─ service-prod.yaml
├─ configmap-dev.yaml
└─ ... (복사본 지옥!)

→ 환경 추가 시 모든 파일 복사
→ 이미지 태그 변경 시 3개 파일 수정

Helm 방식:
myapp/
├─ Chart.yaml
├─ values.yaml (기본값)
├─ values-dev.yaml
├─ values-staging.yaml
├─ values-prod.yaml
└─ templates/
    ├─ deployment.yaml  (템플릿)
    ├─ service.yaml     (템플릿)
    └─ configmap.yaml   (템플릿)

→ 환경 추가 시 values-*.yaml 하나만 추가
→ 이미지 태그 변경 시 values 파일 한 줄만 수정!

Helm 핵심 개념 정리

Chart (차트):

  • Kubernetes 리소스의 패키지
  • 템플릿 + 기본 설정 + 메타데이터
  • helm create로 생성

Values (값):

# values.yaml (기본값)
replicaCount: 2
image:
  repository: nginx
  tag: "1.25.3"
service:
  port: 80

# values-prod.yaml (프로덕션 오버라이드)
replicaCount: 5  # ← 프로덕션은 5개로!
resources:
  limits:
    memory: "512Mi"

템플릿 (Template):

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
spec:
  replicas: {{ .Values.replicaCount }}  # ← 값 주입!
  template:
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        ports:
        - containerPort: {{ .Values.service.port }}

Release (릴리스):

Chart를 특정 환경에 설치한 인스턴스

예시:
myapp-dev    (myapp 차트의 dev 릴리스)
myapp-staging (myapp 차트의 staging 릴리스)
myapp-prod   (myapp 차트의 prod 릴리스)

2. Custom Helm Chart 생성 실습

실습 1: 기본 차트 생성

Helm 설치 (이미 설치됨):

$ helm version
version.BuildInfo{Version:"v3.16.3", GitCommit:"cfd07493f46efc9debd9cc1b02a0961186df7fdf", GitTreeState:"clean", GoVersion:"go1.22.7"}

기본 차트 스캐폴딩:

# 차트 뼈대 생성
$ helm create myapp
Creating myapp

# 디렉토리 구조 확인
$ tree myapp
myapp/
├── Chart.yaml           # 차트 메타데이터
├── values.yaml          # 기본 설정값
├── templates/           # Kubernetes 리소스 템플릿
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── _helpers.tpl     # 헬퍼 함수
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── serviceaccount.yaml
│   └── NOTES.txt        # 설치 후 출력 메시지
└── charts/              # 의존성 차트

Chart.yaml 이해:

apiVersion: v2            # Helm 3 = v2
name: myapp               # 차트 이름
description: A Helm chart for myapp
type: application         # application or library
version: 0.1.0            # 차트 버전 (SemVer)
appVersion: "1.25.3"      # 앱 버전 (nginx 1.25.3)
  • version: 차트 자체의 버전 (YAML 구조 변경 시 증가)
  • appVersion: 배포되는 애플리케이션 버전

실습 2: 환경별 Values 파일 작성

문제: 피곤한 작업이지만 한 번만 하면 계속 재사용 가능!

# values.yaml (기본값)
replicaCount: 2
image:
  repository: nginx
  tag: "1.25.3"
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: 80
resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 50m
    memory: 64Mi
env: "default"

환경별 오버라이드:

# values-dev.yaml (개발 환경 - 최소 리소스)
replicaCount: 1
resources:
  limits:
    cpu: 50m
    memory: 64Mi
  requests:
    cpu: 25m
    memory: 32Mi
env: "development"
# values-staging.yaml (스테이징 - 중간 리소스)
replicaCount: 2
resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi
env: "staging"
# values-prod.yaml (프로덕션 - 고가용성)
replicaCount: 5
resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi
env: "production"
service:
  type: NodePort  # 외부 접근
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

🎯 핵심: 템플릿은 한 번만 작성, 환경별 설정만 변경!


3. ArgoCD가 뭐길래?

GitOps란?

전통적인 배포:

개발자 로컬 PC
  ↓
kubectl apply -f deployment.yaml
  ↓
Kubernetes 클러스터

문제점:
❌ 누가 언제 무엇을 배포했는지 추적 어려움
❌ 환경마다 다른 상태 (drift)
❌ 롤백 복잡함
❌ 권한 관리 어려움 (개발자마다 kubectl 권한 필요)

GitOps 방식:

개발자 → Git Push
  ↓
Git Repository (단일 진실 소스, Single Source of Truth)
  ↓
ArgoCD (자동 감지)
  ↓
Kubernetes 클러스터 (자동 동기화)

장점:
✅ Git = 모든 변경 이력 추적
✅ Pull Request 기반 코드 리뷰
✅ 롤백 = Git Revert
✅ 선언적 상태 (Desired State in Git)
✅ 중앙 집중식 배포 (ArgoCD만 kubectl 권한 필요)

ArgoCD 아키텍처

GitHub Repository
  ├─ myapp/ (Helm Chart)
  │   ├─ templates/
  │   └─ values-*.yaml
  └─ argocd-apps/
      ├─ myapp-dev.yaml
      ├─ myapp-staging.yaml
      └─ myapp-prod.yaml
        ↓
        ↓ (Git Poll/Webhook)
        ↓
ArgoCD 컴포넌트
  ├─ Application Controller
  │   - Git 저장소 모니터링
  │   - 실제 상태 vs 원하는 상태 비교
  │   - 동기화 실행
  │
  ├─ Repo Server
  │   - Git Clone
  │   - Helm Template 렌더링
  │   - Kubernetes 매니페스트 생성
  │
  ├─ API Server
  │   - Web UI / CLI 제공
  │   - RBAC 인증/인가
  │
  └─ Redis
      - 캐시 (Cluster State, Git Commit)
        ↓
        ↓ (kubectl apply)
        ↓
Kubernetes 클러스터
  ├─ myapp-dev (Namespace)
  ├─ myapp-staging (Namespace)
  └─ myapp-prod (Namespace)

4. ArgoCD 설치 및 구성 실습

실습 3: ArgoCD 설치

네임스페이스 생성 및 배포:

# 네임스페이스 생성
$ kubectl create namespace argocd
namespace/argocd created

# ArgoCD 설치 (공식 YAML)
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/applicationsets.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created
serviceaccount/argocd-application-controller created
...
deployment.apps/argocd-server created
deployment.apps/argocd-repo-server created
deployment.apps/argocd-applicationset-controller created

# Pod 상태 확인
$ kubectl get pods -n argocd
NAME                                                READY   STATUS
argocd-application-controller-0                     1/1     Running
argocd-applicationset-controller-xxxxx              1/1     Running
argocd-dex-server-xxxxx                             1/1     Running
argocd-notifications-controller-xxxxx               1/1     Running
argocd-redis-xxxxx                                  1/1     Running
argocd-repo-server-xxxxx                            1/1     Running
argocd-server-xxxxx                                 1/1     Running

ArgoCD가 알아서 Helm Chart 인식!

  • 연동 설정 없이도 자동 동작
  • Git 저장소에 Chart.yaml 있으면 Helm으로 처리
  • helm.valueFiles 파라미터로 환경별 values 선택

실습 4: ArgoCD 웹 UI 접근

NodePort로 외부 노출:

# 서비스 타입 변경
$ kubectl patch svc argocd-server -n argocd -p '{"spec":{"type":"NodePort"}}'
service/argocd-server patched

# NodePort 확인
$ kubectl get svc -n argocd argocd-server
NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)
argocd-server   NodePort   10.103.51.239   <none>        80:31080/TCP,443:31443/TCP

# 접속 URL
http://172.30.1.38:31080

초기 비밀번호 확인:

$ kubectl get secret -n argocd argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
hZa4rP9qK7mE3nF2

로그인:

Username: admin
Password: hZa4rP9qK7mE3nF2

5. GitOps 워크플로우 구현

실습 5: Git 저장소 준비

GitHub 저장소 구조:

ArgoCD-gitops/
├─ myapp/                   # Helm Chart
│   ├─ Chart.yaml
│   ├─ values.yaml
│   ├─ values-dev.yaml
│   ├─ values-staging.yaml
│   ├─ values-prod.yaml
│   └─ templates/
│       ├─ deployment.yaml
│       ├─ service.yaml
│       └─ configmap.yaml
│
└─ sync-waves-demo/         # Sync Waves 실습
    ├─ database.yaml
    ├─ migration-job.yaml
    └─ application.yaml

Git Push:

$ cd /root/argocd-demo
$ git add myapp/
$ git commit -m "Add Helm chart with multi-env values"
$ git push origin main

실습 6: ArgoCD Application 생성 (3개 환경)

ArgoCD Application CRD 이해:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-dev
  namespace: argocd
spec:
  project: default

  # Git 소스
  source:
    repoURL: https://github.com/hansungmoon/ArgoCD-gitops.git
    targetRevision: main
    path: myapp  # Helm Chart 경로
    helm:
      valueFiles:
        - values-dev.yaml  # ← 환경별 values 선택!

  # 배포 대상
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp-dev

  # 동기화 정책
  syncPolicy:
    automated:
      prune: true      # Git에서 삭제된 리소스 자동 제거
      selfHeal: true   # 수동 변경 자동 복구
    syncOptions:
      - CreateNamespace=true

helm.valueFiles는 어디 있지?

  • 위치: /root/argocd-helm-apps.yaml 파일의 15-17, 44-46, 73-75 라인!
  • 각 환경별 Application에서 다른 values 파일 지정

3개 환경 배포:

$ kubectl apply -f /root/argocd-helm-apps.yaml
application.argoproj.io/myapp-dev created
application.argoproj.io/myapp-staging created
application.argoproj.io/myapp-prod created

# Application 상태 확인
$ kubectl get applications -n argocd
NAME             SYNC STATUS   HEALTH STATUS
myapp-dev        Synced        Healthy
myapp-staging    Synced        Healthy
myapp-prod       Synced        Healthy

네임스페이스별 Pod 확인:

$ kubectl get pods -n myapp-dev
NAME                     READY   STATUS    RESTARTS   AGE
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m

$ kubectl get pods -n myapp-staging
NAME                     READY   STATUS    RESTARTS   AGE
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m  # ← Replica 2

$ kubectl get pods -n myapp-prod
NAME                     READY   STATUS    RESTARTS   AGE
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m
myapp-xxxxxxxxx-xxxxx    1/1     Running   0          2m  # ← Replica 5

🎉 성공! 하나의 Helm Chart, 세 가지 환경!


6. Self-Heal 기능 검증

Self-Heal이 어떤 기능이지?

Self-Heal:

  • Git에 선언된 상태(Desired State)를 강제
  • 수동으로 변경된 리소스를 자동으로 원래대로 복구
  • 5초마다 상태 확인 (기본값)

작동 원리:

1. Git: replicas: 2
2. ArgoCD: Kubernetes에 Deployment 배포 (replicas=2)
3. 개발자: kubectl scale deployment myapp --replicas=10
4. ArgoCD: "어? Git에는 2인데 실제는 10이네?" (Drift 감지)
5. ArgoCD: kubectl apply (replicas=2로 복구)
   → 약 5초 후 자동 복구!

실습 7: Self-Heal 테스트

시나리오: 개발자가 실수로 Replica를 수동 변경

# 현재 상태 (Git: 2, 실제: 2)
$ kubectl get deployment -n myapp-dev
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
myapp   2/2     2            2           5m

# 수동 변경 (kubectl로 직접 수정!)
$ kubectl scale deployment myapp -n myapp-dev --replicas=10
deployment.apps/myapp scaled

# 즉시 확인
$ kubectl get deployment -n myapp-dev
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
myapp   10/10   10           10          5m

# 5초 대기...
$ sleep 5

# Self-Heal 작동! (Git 상태로 복구)
$ kubectl get deployment -n myapp-dev
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
myapp   2/2     2            2           5m  # ← 자동으로 2로 복구!

ArgoCD UI에서 확인:

Application: myapp-dev
Status: OutOfSync → Syncing → Synced
Message: "Deployment replicas reverted to 2 (Git state)"

🎯 교훈:

  • Git이 단일 진실 소스 (Single Source of Truth)
  • 수동 변경은 무의미 (Self-Heal이 복구)
  • 변경하려면 Git 수정 → PR → Merge!

7. Sync Waves: 순차적 배포 제어

왜 Sync Waves가 필요한가?

문제 상황:

Application 배포 시:
1. Database Pod 생성 중... (아직 준비 안 됨)
2. Application Pod 시작 → DB 연결 실패! (CrashLoopBackOff)
3. DB 준비 완료
4. Application 계속 재시작...

→ 배포 순서가 랜덤!

Sync Waves 해결책:

annotations:
  argocd.argoproj.io/sync-wave: "0"  # ← 낮은 번호부터 배포!

배포 순서:
Wave 0: Database (postgres)
  ↓ (DB 준비 완료 대기)
Wave 1: DB Migration Job (PreSync Hook)
  ↓ (마이그레이션 완료)
Wave 2: Application (webapp)
  ↓ (모든 리소스 Healthy)

실습 8: Sync Waves 구현

Wave 0: Database (먼저 배포)

# database.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  annotations:
    argocd.argoproj.io/sync-wave: "0"  # ← Wave 0
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    spec:
      containers:
      - name: postgres
        image: postgres:14-alpine
        env:
        - name: POSTGRES_PASSWORD
          value: "password123"
        - name: POSTGRES_DB
          value: "myapp"
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  annotations:
    argocd.argoproj.io/sync-wave: "0"
spec:
  selector:
    app: postgres
  ports:
  - port: 5432

Wave 1: DB Migration (PreSync Hook)

# migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  annotations:
    argocd.argoproj.io/sync-wave: "1"  # ← Wave 1
    argocd.argoproj.io/hook: PreSync    # ← 앱 배포 전 실행!
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migration
        image: postgres:14-alpine
        command:
        - /bin/sh
        - -c
        - |
          echo "=== DB Migration 시작 ==="
          echo "데이터베이스 연결 대기 중..."
          sleep 5

          echo "테이블 생성 중..."
          PGPASSWORD=password123 psql -h postgres -U postgres -d myapp -c "
            CREATE TABLE IF NOT EXISTS users (
              id SERIAL PRIMARY KEY,
              name VARCHAR(100),
              created_at TIMESTAMP DEFAULT NOW()
            );
          "

          echo "샘플 데이터 삽입 중..."
          PGPASSWORD=password123 psql -h postgres -U postgres -d myapp -c "
            INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie')
            ON CONFLICT DO NOTHING;
          "

          echo "=== DB Migration 완료 ==="

Hook 종류:

  • PreSync: 동기화 전 실행 (DB 마이그레이션, 사전 검증)
  • Sync: 일반 리소스 배포 (기본값)
  • PostSync: 동기화 후 실행 (테스트, Slack 알림)
  • SyncFail: 동기화 실패 시 실행 (롤백, 에러 알림)

Wave 2: Application (마지막 배포)

# application.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  annotations:
    argocd.argoproj.io/sync-wave: "2"  # ← Wave 2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    spec:
      initContainers:
      - name: wait-for-db
        image: postgres:14-alpine
        command:
        - /bin/sh
        - -c
        - |
          echo "데이터베이스 준비 대기 중..."
          until PGPASSWORD=password123 psql -h postgres -U postgres -d myapp -c '\l'; do
            echo "데이터베이스 연결 대기..."
            sleep 2
          done
          echo "데이터베이스 준비 완료!"
      containers:
      - name: webapp
        image: nginx:1.25.3

Git Push 및 ArgoCD Application 생성:

# Git Push
$ cd /root/argocd-demo/sync-waves-demo
$ git add .
$ git commit -m "Add Sync Waves demo with DB migration"
$ git push origin main

# ArgoCD Application 생성
$ kubectl apply -f /root/sync-waves-app.yaml
application.argoproj.io/sync-waves-demo created

# 배포 순서 확인 (실시간 모니터링)
$ kubectl get pods -n sync-waves -w
NAME                        READY   STATUS              RESTARTS   AGE
postgres-xxxxxxxxx-xxxxx    0/1     ContainerCreating   0          3s   # ← Wave 0
postgres-xxxxxxxxx-xxxxx    1/1     Running             0          15s
db-migration-xxxxx          0/1     ContainerCreating   0          5s   # ← Wave 1 (PreSync)
db-migration-xxxxx          1/1     Running             0          8s
db-migration-xxxxx          0/1     Completed           0          25s
webapp-xxxxxxxxx-xxxxx      0/1     Init:0/1            0          3s   # ← Wave 2
webapp-xxxxxxxxx-xxxxx      0/1     PodInitializing     0          12s
webapp-xxxxxxxxx-xxxxx      1/1     Running             0          15s

최종 상태:

$ kubectl get pods -n sync-waves
NAME                        READY   STATUS      RESTARTS   AGE
db-migration-dvlft          0/1     Completed   0          2m    # ← Job 완료
postgres-6b9c5b7d9c-4kvxn   1/1     Running     0          2m
webapp-7f8b6c9d8f-7kxqm     1/1     Running     0          1m
webapp-7f8b6c9d8f-9hnpz     1/1     Running     0          1m

$ kubectl get application -n argocd sync-waves-demo
NAME              SYNC STATUS   HEALTH STATUS
sync-waves-demo   Synced        Healthy  # ← 모두 성공!

🎉 순차적 배포 성공! DB → Migration → App 순서 보장!


8. ArgoCD Application CRD 완전 분석

Application Spec 주요 필드

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-dev
  namespace: argocd  # ← ArgoCD는 argocd 네임스페이스에서 실행
spec:
  # 1. 프로젝트 (RBAC, 리소스 격리)
  project: default

  # 2. Git 소스
  source:
    repoURL: https://github.com/user/repo.git
    targetRevision: main  # 브랜치, 태그, 커밋 SHA
    path: myapp           # Chart 경로

    # Helm 설정
    helm:
      valueFiles:
        - values-dev.yaml   # ← 여러 개 가능!
        - secrets-dev.yaml
      parameters:           # CLI 오버라이드
        - name: replicaCount
          value: "3"
      releaseName: myapp-dev

  # 3. 배포 대상
  destination:
    server: https://kubernetes.default.svc  # 클러스터 URL
    namespace: myapp-dev

  # 4. 동기화 정책
  syncPolicy:
    automated:
      prune: true       # Git 삭제 → Kubernetes 삭제
      selfHeal: true    # Drift 자동 복구
      allowEmpty: false # 빈 커밋 거부

    syncOptions:
      - CreateNamespace=true   # 네임스페이스 자동 생성
      - PruneLast=true         # 삭제는 마지막에
      - ApplyOutOfSyncOnly=true # OutOfSync만 적용

    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

  # 5. Health 체크 (선택)
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas  # HPA 사용 시 replicas 무시

syncPolicy 상세 설명

automated.prune:

Git에서 deployment.yaml 삭제
  ↓
ArgoCD: "Deployment가 Git에 없네?"
  ↓
kubectl delete deployment myapp  # ← 자동 삭제!

automated.selfHeal:

kubectl scale deployment myapp --replicas=10
  ↓
ArgoCD: "Git에는 2인데 실제는 10이네?" (5초마다 체크)
  ↓
kubectl apply (Git 상태로 복구)

syncOptions:

- CreateNamespace=true   # 네임스페이스 없으면 생성
- PrunePropagationPolicy=foreground  # 삭제 순서 제어
- Validate=false         # kubectl validation 건너뛰기

retry:

1차 시도 실패
  ↓ 5초 대기
2차 시도 실패
  ↓ 10초 대기 (factor=2)
3차 시도 실패
  ↓ 20초 대기
...
최대 3분까지 재시도

9. 최종 GitOps 워크플로우

개발 → 배포 전체 흐름

1. 개발자: 코드 수정
   $ vi src/app.py
   $ docker build -t myapp:v1.2.3 .
   $ docker push ghcr.io/user/myapp:v1.2.3

2. Helm Values 업데이트
   $ vi myapp/values-prod.yaml
   ---
   image:
     tag: "v1.2.3"  # ← 새 버전
   ---

3. Git Push
   $ git add myapp/values-prod.yaml
   $ git commit -m "Update to v1.2.3"
   $ git push origin main

4. Pull Request (선택)
   - Code Review
   - CI 테스트 (GitHub Actions)
   - Approve & Merge

5. ArgoCD 자동 감지
   - 3분마다 Git Poll (또는 Webhook)
   - "main 브랜치 커밋 감지!"

6. ArgoCD Sync
   - Helm Template 렌더링
   - Kubernetes 매니페스트 생성
   - kubectl apply

7. Kubernetes 롤링 업데이트
   - Pod v1.2.2 → v1.2.3 교체
   - Readiness Probe 확인
   - 무중단 배포 완료!

8. ArgoCD 상태 업데이트
   - Sync Status: Synced
   - Health Status: Healthy
   - Slack 알림 (설정 시)

Git Commit → 자동 배포까지 소요 시간

Git Push
  ↓
GitHub (즉시)
  ↓
ArgoCD Polling (최대 3분) or Webhook (즉시)
  ↓
Helm Rendering (5초)
  ↓
kubectl apply (10초)
  ↓
Pod 롤링 업데이트 (30초~2분)
  ↓
Healthy 상태 확인 (10초)
  ↓
Total: 약 1~5분 (Webhook 사용 시 더 빠름!)

배운 점

1. Helm은 피곤하지만 가치 있다

Custom Chart 생성:

처음엔 피곤한 작업:
❌ Chart.yaml 작성
❌ templates/ 디렉토리 구조 설계
❌ values.yaml 변수 정의
❌ {{ .Values.xxx }} 템플릿 문법

하지만 한 번 만들면:
✅ 무한 재사용
✅ 환경 추가 = values 파일 하나
✅ 버전 관리 (helm rollback)
✅ 공유 가능 (Helm Repository)

실무 팁:

  • 공식 Chart 먼저 검색 (bitnami/nginx, stable/mysql)
  • 없으면 helm create로 시작
  • 복잡한 로직은 _helpers.tpl로 분리

2. ArgoCD는 Helm을 알아서 인식

연동 설정 없이 동작:

Git 저장소에 Chart.yaml 있으면?
  ↓
ArgoCD: "아, Helm Chart구나!"
  ↓
자동으로:
  - helm template 실행
  - values 파일 머지
  - Kubernetes 매니페스트 생성
  - kubectl apply

helm.valueFiles 위치:

  • Application CRD의 spec.source.helm.valueFiles
  • 배열로 여러 파일 지정 가능
  • 우선순위: 나중 파일이 앞 파일 덮어씀

3. Self-Heal은 Git 강제 동기화

작동 원리:

매 5초마다:
1. Git에서 최신 매니페스트 가져오기
2. kubectl get으로 실제 상태 확인
3. Diff 계산 (Desired vs Actual)
4. OutOfSync 발견 시 kubectl apply

→ 수동 변경은 무의미!
→ Git만 수정하세요!

예외 상황:

  • HPA로 replicas 자동 조정 → ignoreDifferences 설정
  • StatefulSet ordinal → ignoreDifferences 설정

4. Sync Waves는 순서 보장의 핵심

왜 필요한가:

일반 배포:
DB, App, Migration Job 동시 생성
→ 랜덤 순서!
→ App이 먼저 뜨면 DB 연결 실패

Sync Waves:
Wave 0: DB
Wave 1: Migration (PreSync Hook)
Wave 2: App
→ 순서 보장!
→ 안정적 배포!

Hook 활용:

  • PreSync: DB 스키마 마이그레이션
  • Sync: 일반 리소스
  • PostSync: 통합 테스트, Slack 알림
  • SyncFail: 롤백, 에러 로그 수집

5. 환경별 배포는 Values로 해결

하나의 Chart, 여러 환경:

myapp/ (Helm Chart)
├─ values.yaml       (공통 기본값)
├─ values-dev.yaml   (개발: replica=1, resources 최소)
├─ values-staging.yaml (스테이징: replica=2, 중간)
└─ values-prod.yaml  (프로덕션: replica=5, HPA, NodePort)

Application CRD:
myapp-dev      → valueFiles: [values-dev.yaml]
myapp-staging  → valueFiles: [values-staging.yaml]
myapp-prod     → valueFiles: [values-prod.yaml]

→ 템플릿 중복 없음!
→ 환경별 차이만 values에!

삽질 포인트

1. Helm Chart 생성 피곤함

증상: 템플릿 하나하나 다 작성해야 함

해결:

  • helm create myapp로 스캐폴딩 활용
  • 불필요한 템플릿 삭제 (hpa.yaml, ingress.yaml 등)
  • 공식 Chart 참고 (github.com/bitnami/charts)

2. values-*.yaml 찾기 어려움

증상: ArgoCD UI에서 어떤 values 쓰는지 안 보임

해결:

# Application YAML 확인
$ kubectl get application myapp-dev -n argocd -o yaml
spec:
  source:
    helm:
      valueFiles:
        - values-dev.yaml  # ← 여기!

3. Self-Heal이 내 변경을 계속 되돌림

증상: kubectl edit으로 수정해도 5초 후 복구됨

교훈:

  • Self-Heal이 켜져 있으면 Git만 수정!

  • 긴급 수정 필요 시:

    # Self-Heal 임시 비활성화
    $ kubectl patch application myapp-dev -n argocd --type=merge \
      -p '{"spec":{"syncPolicy":{"automated":{"selfHeal":false}}}}'
    
    # 수정 작업
    $ kubectl edit deployment myapp -n myapp-dev
    
    # 다시 활성화
    $ kubectl patch application myapp-dev -n argocd --type=merge \
      -p '{"spec":{"syncPolicy":{"automated":{"selfHeal":true}}}}'

4. Sync Waves 순서 헷갈림

증상: Wave 2가 Wave 1보다 먼저 배포됨

원인: 숫자가 아닌 문자열 정렬

❌ sync-wave: 10  # "10"은 "2"보다 앞!
✅ sync-wave: "10"

교훈:

  • Wave 번호는 항상 따옴표로 감싸기
  • 음수 가능: -1 (Wave 0보다 먼저)

다음 계획 (Day 9)

Day 8에서 Helm + ArgoCD로 GitOps 파이프라인을 완성했습니다. 이제 Day 9부터는 마이크로서비스 통신을 관리하는 Istio Service Mesh를 단계별로 학습합니다.

Day 9 주제: Istio 소개와 첫걸음

  1. 마이크로서비스 문제점과 Service Mesh 필요성

    • 서비스 간 통신 복잡도 (N×N 연결)
    • 장애 전파 (Cascading Failure)
    • 관찰성 부족 및 보안 이슈
  2. Istio 아키텍처 이해

    • Control Plane (Istiod): Pilot, Citadel, Galley
    • Data Plane (Envoy): Sidecar 패턴
    • xDS API를 통한 설정 전달
  3. 실습 환경 구축

    • Kind 클러스터 구성
    • Istio 설치 (default 프로파일, 프로덕션 설정)
    • Bookinfo 샘플 애플리케이션 배포
    • Sidecar 자동 주입 확인
  4. 관측 도구 설치

    • Kiali: 서비스 토폴로지 시각화
    • Prometheus & Grafana: 메트릭 수집
    • Vector + OpenTelemetry + OpenSearch: 로그 수집 준비

Day 10 이후 학습 로드맵

Istio 심화 학습 (단계별):

  • Day 10: Sail Operator (Istio 관리 자동화)
  • Day 11: Envoy Proxy 심화 (Listener, Route, Cluster)
  • Day 12: Istio Gateways (Ingress/Egress)
  • Day 13: Traffic Management (Canary, A/B 테스팅, Blue-Green)
  • Day 14: Resiliency (Retry, Circuit Breaker, Timeout)
  • Day 15: Observability (Kiali, Jaeger 분산 추적)
  • Day 16: Security (mTLS, Authorization Policy)
  • Day 17: Troubleshooting & Performance Tuning
  • Day 18: Scaling & VM Support
  • Day 19: Ambient Mesh (Sidecar-less 아키텍처)

실전 도전 과제:

  • ArgoCD + Istio로 Progressive Delivery 구현
  • Multi-Cluster Service Mesh 구성
  • Fault Injection을 통한 카오스 엔지니어링
  • Zero Trust 보안 모델 적용

마무리

GitOps는 단순한 도구가 아니라 철학입니다. Git을 단일 진실 소스로 삼고, 모든 변경을 코드로 관리하는 것!

핵심 요약:

  • ✅ Helm = 템플릿 + Values로 환경별 배포
  • ✅ ArgoCD = Git → Kubernetes 자동 동기화
  • ✅ Self-Heal = Git 상태 강제 (5초마다)
  • ✅ Sync Waves = 순차적 배포 (DB → Migration → App)
  • ✅ GitOps = Pull Request 기반 배포 (코드 리뷰 + 이력 추적)

Day 9에서 만나요! 🚀


참고 자료

profile
기록하고 공유하려고 노력하는 DevOps 엔지니어

0개의 댓글