관리 메뉴

근묵자흑

Kubernetes Pattern: 선언적 배포(Declarative Deployment) + FluxCD 본문

k8s/kubernetes-pattern

Kubernetes Pattern: 선언적 배포(Declarative Deployment) + FluxCD

Luuuuu 2025. 8. 9. 19:25

왜 선언적 배포인가?

여러분이 개발한 애플리케이션을 Kubernetes에 배포한다고 상상해보세요. 새 버전을 출시할 때마다 이런 고민을 하게 됩니다.

  • "서비스 중단 없이 업데이트할 수 있을까?"
  • "문제가 생기면 빠르게 롤백할 수 있을까?"
  • "일부 사용자에게만 먼저 테스트해볼 수 없을까?"

이 모든 질문의 답이 바로 선언적 배포 패턴에 있습니다.

명령형 vs 선언적: 패러다임의 전환

# 명령형 방식 (Imperative) - "어떻게" 할지 일일이 지시
kubectl create deployment my-app --image=nginx:1.0
kubectl scale deployment my-app --replicas=3
kubectl set image deployment/my-app nginx=nginx:2.0
kubectl rollout status deployment/my-app

# 선언적 방식 (Declarative) - "무엇"을 원하는지만 선언
kubectl apply -f deployment.yaml

선언적 방식의 핵심은 원하는 상태(Desired State)를 정의하면, Kubernetes가 알아서 현재 상태를 원하는 상태로 만들어준다는 것입니다.

4가지 배포 전략 심층 분석

이번장의 실습 코드는 GitHub 저장소에서 확인할 수 있습니다.

1.1 롤링 업데이트 (Rolling Update) - 안정적인 점진적 교체

롤링 업데이트는 구 버전의 파드를 하나씩 새 버전으로 교체하는 방식입니다. 마치 달리는 기차의 바퀴를 하나씩 교체하는 것과 같습니다.

실제 구현 코드 분석

# apps/rolling-update/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: random-generator
  namespace: rolling-demo
spec:
  replicas: 3  # 총 3개의 파드 운영
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 업데이트 중 추가로 생성 가능한 파드 수
      maxUnavailable: 1  # 업데이트 중 사용 불가능한 파드 수
  template:
    spec:
      containers:
      - name: random-generator
        image: k8spatterns/random-generator:1.0
        livenessProbe:    # 파드가 살아있는지 확인
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
        readinessProbe:    # 트래픽을 받을 준비가 되었는지 확인
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 5

핵심 파라미터 설명:

  • maxSurge: 1: 업데이트 중 최대 4개(3+1)까지 파드 운영 가능
  • maxUnavailable: 1: 최소 2개(3-1)의 파드는 항상 서비스 제공
  • livenessProbe: 파드 헬스체크 실패 시 재시작
  • readinessProbe: 준비되지 않은 파드로는 트래픽 안 보냄

실제 테스트 결과

# 테스트 실행
./scripts/test-rolling-update.sh

# 결과: 3개 파드가 순차적으로 업데이트
NAME                                READY   STATUS    AGE
random-generator-5c7d8f9b4f-xyz123  1/1     Running   10s  # 새 버전
random-generator-5c7d8f9b4f-abc456  1/1     Running   20s  # 새 버전  
random-generator-6b8d7f5c9d-def789  1/1     Terminating   5m  # 구 버전 종료 중

장점:

  • 무중단 배포
  • 리소스 효율적 (110-125% 사용)
  • 자동 롤백 가능

단점:

  • 배포 중 신/구 버전 혼재
  • 데이터베이스 스키마 변경 시 주의 필요

1.2 블루-그린 배포 - 즉각적인 전환과 롤백

블루-그린은 두 개의 완전히 동일한 환경을 운영하며, 트래픽을 한 번에 전환하는 방식입니다.

실제 구현 코드 분석

# apps/blue-green/deployment-blue.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: random-generator-blue
  labels:
    version: blue  # Blue 버전 식별자
spec:
  replicas: 3
  selector:
    matchLabels:
      app: random-generator
      version: blue
  template:
    metadata:
      labels:
        app: random-generator
        version: blue  # 파드 레이블
    spec:
      containers:
      - name: random-generator
        image: k8spatterns/random-generator:1.0
        env:
        - name: VERSION
          value: "blue"
# apps/blue-green/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: random-generator
spec:
  selector:
    app: random-generator
    version: blue  # 초기: Blue로 트래픽 라우팅
  ports:
  - port: 80
    targetPort: 8080
---
# Preview 서비스 - Green 테스트용
apiVersion: v1
kind: Service
metadata:
  name: random-generator-preview
spec:
  selector:
    app: random-generator
    version: green  # 항상 Green을 가리킴

트래픽 전환 메커니즘

# Blue에서 Green으로 전환 (Service의 selector 변경)
kubectl patch service random-generator \
  -p '{"spec":{"selector":{"version":"green"}}}'

# 문제 발생 시 Blue로 즉시 롤백
kubectl patch service random-generator \
  -p '{"spec":{"selector":{"version":"blue"}}}'

실제 테스트 결과:

# 전환 전: Blue 버전 서비스 중
$ curl http://localhost:8080/actuator/info
{"pattern": "Blue-Green (Blue Version)"}

# 전환 명령 실행 (1초 이내 완료)
$ kubectl patch service random-generator -p '{"spec":{"selector":{"version":"green"}}}'

# 전환 후: Green 버전 서비스 중
$ curl http://localhost:8080/actuator/info  
{"pattern": "Blue-Green (Green Version)"}

장점:

  • 즉각적인 전환 (1초 이내)
  • 간단한 롤백
  • 전체 환경 테스트 가능

단점:

  • 리소스 2배 필요
  • 데이터베이스 동기화 복잡
  • 스테이트풀 앱 적용 어려움

1.3 카나리 배포 - 위험을 최소화하는 점진적 릴리스

카나리 배포는 새 버전을 일부 사용자에게만 먼저 배포하여 위험을 최소화합니다. 위험을 먼저 감지하는 역할을 수행합니다.

Flagger를 활용한 자동화된 카나리 배포

# apps/canary/canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: random-generator
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: random-generator

  # 단계적 트래픽 증가 설정
  analysis:
    interval: 30s        # 분석 주기
    threshold: 5         # 실패 허용 횟수
    maxWeight: 50        # 최대 카나리 트래픽
    stepWeight: 10       # 단계별 증가율

    # 메트릭 기반 자동 판단
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99  # 99% 이상 성공률 유지
      interval: 30s

    - name: request-duration  
      thresholdRange:
        max: 500  # 500ms 이하 응답시간
      interval: 30s

    # 부하 테스트 자동 실행
    webhooks:
    - name: load-test
      type: rollout
      url: http://flagger-loadtester/
      metadata:
        cmd: "hey -z 2m -q 10 -c 2 http://random-generator/"

카나리 배포 진행 과정

# 1. 초기 상태: Stable 버전만 운영
Status: Initialized | Canary Weight: 0% | Iterations: 0

# 2. 새 버전 배포 시작
$ kubectl set image deployment/random-generator \
    random-generator=k8spatterns/random-generator:2.0

# 3. Flagger가 자동으로 진행 상황 관리
Status: Progressing | Canary Weight: 10% | Iterations: 1
# → 10% 트래픽을 새 버전으로, 메트릭 확인

Status: Progressing | Canary Weight: 20% | Iterations: 2  
# → 메트릭 정상, 20%로 증가

Status: Progressing | Canary Weight: 30% | Iterations: 3
# → 계속 증가...

# 4-1. 성공 시나리오
Status: Succeeded | Canary Weight: 0% | Iterations: 5
# → 100% 전환 완료, 구 버전 제거

# 4-2. 실패 시나리오 (에러율 증가 감지)
Status: Failed | Canary Weight: 0% | Iterations: 3
# → 자동 롤백, 구 버전 유지

장점:

  • 위험 최소화
  • 실사용자 피드백
  • 자동 롤백
  • A/B 테스트 가능

단점:

  • 복잡한 설정
  • 모니터링 인프라 필요
  • 배포 시간 길어짐

GitOps with FluxCD

 

GitOps: 선언적 배포의 진화

GitOps는 Git을 단일 진실의 원천(Single Source of Truth)으로 사용하는 운영 모델입니다.

FluxCD 아키텍처와 동작 원리

# fluxcd-system/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 1m          # Git 동기화 주기
  path: ./apps          # 모니터링할 경로
  prune: true          # Git에서 삭제되면 클러스터에서도 삭제
  sourceRef:
    kind: GitRepository
    name: flux-system
  validation: client   # 적용 전 검증
  healthChecks:       # 배포 후 상태 확인
    - apiVersion: apps/v1
      kind: Deployment
      name: my-app
      namespace: default

FluxCD 핵심 컴포넌트:

  1. Source Controller: Git/Helm/OCI 저장소 모니터링
  2. Kustomize Controller: Kustomization 리소스 적용
  3. Helm Controller: Helm 차트 관리
  4. Notification Controller: 알림 전송

실전 구현 및 테스트

환경 구축: Minikube + FluxCD

#!/bin/bash
# scripts/setup-minikube.sh 핵심 부분

# 1. Minikube 시작 (충분한 리소스 할당)
minikube start \
  --cpus=4 \
  --memory=8192 \
  --kubernetes-version=v1.28.0

# 2. 필수 애드온 활성화
minikube addons enable metrics-server  # 메트릭 수집
minikube addons enable ingress        # 인그레스 컨트롤러

# 3. FluxCD 설치
flux install \
  --namespace=flux-system \
  --network-policy=false \
  --components=source-controller,kustomize-controller,helm-controller

실제 테스트 수행 및 결과 분석

테스트 1: 롤링 업데이트 성능 측정

# 부하 생성하며 업데이트
while true; do 
  curl -s http://localhost:8080/ > /dev/null
  echo -n "."
done &

# 업데이트 실행
kubectl set image deployment/random-generator \
  random-generator=k8spatterns/random-generator:2.0

# 결과: 0% 에러율, 평균 응답시간 50ms 유지

테스트 2: 블루-그린 전환 시간 측정

# 전환 시간 측정 스크립트
START=$(date +%s%N)
kubectl patch service random-generator \
  -p '{"spec":{"selector":{"version":"green"}}}'
END=$(date +%s%N)
echo "전환 시간: $(( ($END - $START) / 1000000 ))ms"

# 결과: 평균 230ms (네트워크 지연 포함)

테스트 3: 카나리 배포 메트릭 분석

# Flagger 메트릭 확인
kubectl -n canary-demo get canary random-generator -o json | \
  jq '.status.conditions'

# 결과:
[
  {
    "type": "Promoted",
    "status": "True",
    "reason": "Succeeded",
    "message": "Canary analysis completed successfully"
  }
]

 

 


비교 분석 및 의사결정 가이드

배포 전략 선택 기준

시나리오 추천 전략 이유
일반 웹 애플리케이션 롤링 업데이트 간단하고 효율적
금융 거래 시스템 블루-그린 즉시 롤백 필요
ML 모델 배포 카나리 A/B 테스트 및 점진적 검증
마이크로서비스 (100+) 롤링 + 카나리 혼합 서비스별 중요도 차별화
스타트업 MVP 롤링 업데이트 리소스 효율성

트러블슈팅 

자주 발생하는 문제와 해결법

문제 1: ImagePullBackOff

# 진단
kubectl describe pod <pod-name>

# 해결
# 1. 이미지 태그 확인
# 2. 레지스트리 인증 설정
kubectl create secret docker-registry regcred \
  --docker-server=<registry> \
  --docker-username=<username> \
  --docker-password=<password>

문제 2: 롤링 업데이트 중단

# 진단
kubectl rollout status deployment/<name>

# 해결
# 1. 헬스체크 타임아웃 조정
livenessProbe:
  initialDelaySeconds: 60  # 증가
  periodSeconds: 20        # 증가

# 2. 강제 재시작
kubectl rollout restart deployment/<name>

문제 3: FluxCD 동기화 실패

# 진단
flux get all
flux logs --level=error

# 해결
# 1. 수동 조정
flux reconcile source git flux-system

# 2. 캐시 삭제
kubectl -n flux-system delete pod -l app=source-controller

참고 자료