Theme:

Deployment로 대부분의 워크로드를 배포할 수 있는데, 왜 StatefulSet이나 DaemonSet 같은 별도의 컨트롤러가 필요할까요?

Deployment는 무상태(Stateless) 애플리케이션에 최적화되어 있습니다. Pod 이름이 랜덤하고, 어떤 순서로 생성/삭제되든 상관없으며, 볼륨도 공유합니다. 하지만 데이터베이스처럼 각 인스턴스가 고유한 ID와 전용 스토리지를 가져야 하거나, 모든 노드에 에이전트를 배포해야 하는 경우에는 다른 컨트롤러가 필요합니다.

StatefulSet — 상태를 가진 워크로드

StatefulSet은 다음 세 가지를 보장합니다.

  1. 고유하고 안정적인 네트워크 ID: Pod 이름이 순번으로 결정됩니다 (예: mysql-0, mysql-1)
  2. 순서 보장: 생성은 0번부터, 삭제는 역순으로 진행됩니다
  3. 개별 퍼시스턴트 스토리지: 각 Pod에 전용 PVC가 할당됩니다
YAML
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless  # Headless Service 필수
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: password
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
  volumeClaimTemplates:        # 각 Pod에 개별 PVC 생성
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: gp3
        resources:
          requests:
            storage: 10Gi

Headless Service와의 조합

StatefulSet은 반드시 Headless Service가 필요합니다. 이를 통해 각 Pod에 고유한 DNS 레코드가 생성됩니다.

YAML
apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
    - port: 3306
SHELL
# 각 Pod의 DNS
# mysql-0.mysql-headless.default.svc.cluster.local
# mysql-1.mysql-headless.default.svc.cluster.local
# mysql-2.mysql-headless.default.svc.cluster.local

Primary-Replica 구성에서 쓰기는 mysql-0에, 읽기는 다른 Pod에 보내는 식으로 활용할 수 있습니다.

Pod 관리 정책

YAML
spec:
  podManagementPolicy: OrderedReady  # 기본값: 순차 생성/삭제
  # podManagementPolicy: Parallel    # 병렬 생성/삭제 (순서 불필요 시)

OrderedReady는 mysql-0이 Ready가 되어야 mysql-1이 생성됩니다. 순서가 중요하지 않은 경우 Parallel로 배포 속도를 높일 수 있습니다.

업데이트 전략

YAML
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 1  # 1번 이상의 Pod만 업데이트 (카나리 패턴)

partition을 활용하면 특정 번호 이상의 Pod만 업데이트할 수 있어 단계적 롤아웃이 가능합니다.

DaemonSet — 모든 노드에 하나씩

DaemonSet은 클러스터의 모든 노드에 정확히 하나의 Pod을 실행합니다. 노드가 추가되면 자동으로 Pod이 생성되고, 노드가 제거되면 Pod도 삭제됩니다.

대표적인 사용 사례

  • 로그 수집: Fluentd, Fluent Bit
  • 모니터링 에이전트: Node Exporter, Datadog Agent
  • 네트워크 플러그인: Calico, Cilium
  • 스토리지 드라이버: CSI Node Driver
YAML
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-collector
spec:
  selector:
    matchLabels:
      app: log-collector
  template:
    metadata:
      labels:
        app: log-collector
    spec:
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:2.1
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
            - name: containers
              mountPath: /var/lib/docker/containers
              readOnly: true
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 200m
              memory: 256Mi
      tolerations:
        # 마스터 노드에서도 실행
        - key: node-role.kubernetes.io/control-plane
          effect: NoSchedule
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: containers
          hostPath:
            path: /var/lib/docker/containers

특정 노드에만 배포

YAML
spec:
  template:
    spec:
      nodeSelector:
        disk-type: ssd  # SSD 노드에만 배포
      # 또는 affinity 사용
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: node-role
                    operator: In
                    values: ["worker"]

업데이트 전략

YAML
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1  # 한 번에 1개 노드씩 업데이트

OnDelete 전략을 사용하면 수동으로 Pod을 삭제할 때만 새 버전이 배포됩니다.

Job — 한 번 실행하고 종료

Job은 완료될 때까지 Pod을 실행하고, 성공하면 종료합니다.

YAML
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  completions: 1       # 성공해야 하는 횟수
  parallelism: 1       # 동시 실행 Pod 수
  backoffLimit: 3      # 실패 시 재시도 횟수
  activeDeadlineSeconds: 300  # 최대 실행 시간 (5분)
  template:
    spec:
      restartPolicy: Never  # Job에서는 Never 또는 OnFailure
      containers:
        - name: migrate
          image: my-app:1.0
          command: ["./migrate", "--up"]

병렬 처리 Job

YAML
spec:
  completions: 10    # 10개 작업을 모두 완료해야 함
  parallelism: 3     # 동시에 3개 Pod 실행
SHELL
# Job 상태 확인
kubectl get jobs db-migration
# NAME           COMPLETIONS   DURATION   AGE
# db-migration   1/1           45s        2m

CronJob — 스케줄 기반 반복 실행

CronJob은 cron 표현식에 따라 주기적으로 Job을 생성합니다.

YAML
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-backup
spec:
  schedule: "0 2 * * *"  # 매일 새벽 2시
  concurrencyPolicy: Forbid  # 이전 Job이 실행 중이면 새 Job 생성 안 함
  successfulJobsHistoryLimit: 3   # 성공 이력 보관 수
  failedJobsHistoryLimit: 1       # 실패 이력 보관 수
  startingDeadlineSeconds: 200    # 스케줄 시간 이후 200초 내에 시작 안 되면 스킵
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: backup-tool:1.0
              command: ["/bin/sh", "-c", "pg_dump mydb > /backup/dump.sql"]

concurrencyPolicy 옵션

설명
Allow동시 실행 허용 (기본값)
Forbid이전 Job이 실행 중이면 새 Job 스킵
Replace이전 Job을 종료하고 새 Job 실행

Deployment vs StatefulSet vs DaemonSet 비교

특성DeploymentStatefulSetDaemonSet
Pod 이름랜덤 해시순번 (0, 1, 2...)노드별 1개
스토리지공유 가능개별 PVC보통 hostPath
순서 보장없음있음없음
스케일링replicas 조절replicas 조절노드 수에 연동
사용 사례웹 서버, APIDB, 캐시에이전트, 모니터링

실무에서 주의할 점

  • StatefulSet Pod 삭제 시 PVC는 남습니다: 데이터 보존을 위해 의도적인 설계이지만, 정리를 잊으면 불필요한 볼륨 비용이 발생합니다
  • DaemonSet은 리소스 제한을 꼭 설정하세요: 모든 노드에서 실행되므로 리소스를 과하게 사용하면 전체 클러스터에 영향을 줍니다
  • CronJob의 타임존: Kubernetes 1.27부터 .spec.timeZone 필드로 타임존을 지정할 수 있습니다
YAML
spec:
  schedule: "0 2 * * *"
  timeZone: "Asia/Seoul"  # KST 기준

정리

Deployment는 무상태 워크로드의 기본이지만, 고유 ID와 전용 스토리지가 필요하면 StatefulSet, 모든 노드에 에이전트를 배포하려면 DaemonSet, 한 번 실행하고 끝나는 작업에는 Job/CronJob을 사용합니다. 각 컨트롤러의 특성을 이해하고 워크로드 성격에 맞는 것을 선택하는 것이 중요합니다.

댓글 로딩 중...