☁️ 클라우드

StatefulSet

상태가 있는 애플리케이션을 위한 Kubernetes 리소스

📖 상세 설명

StatefulSet은 상태가 있는(Stateful) 애플리케이션을 관리하기 위한 Kubernetes 워크로드 리소스입니다. Deployment와 달리 각 Pod에 고유한 네트워크 ID와 안정적인 영구 스토리지를 제공하며, 순서대로 배포/삭제됩니다.

StatefulSet vs Deployment:

  • 고유 Pod 이름: myapp-0, myapp-1, myapp-2 (랜덤 접미사 아님)
  • Headless Service: 각 Pod에 DNS 엔트리 제공 (myapp-0.myapp-svc)
  • 순서 보장: 0부터 순차적으로 생성, 역순으로 삭제
  • PVC 보존: Pod 삭제 시 PersistentVolumeClaim 유지

주요 사용 사례:

  • 데이터베이스: MySQL, PostgreSQL, MongoDB 클러스터
  • 메시지 브로커: Kafka, RabbitMQ, ZooKeeper
  • 분산 캐시: Redis Cluster, Memcached
  • 분산 스토리지: Elasticsearch, Cassandra

Pod 관리 정책:

  • OrderedReady (기본): 순차적 생성/삭제, 이전 Pod Ready 후 다음 생성
  • Parallel: 모든 Pod 동시 생성/삭제 (순서 무관한 경우)

업데이트 전략: RollingUpdate(역순 롤링), OnDelete(수동 삭제 시 업데이트), partition(특정 인덱스 이상만 업데이트) 옵션을 제공합니다.

💻 코드 예제

PostgreSQL StatefulSet 예제

# postgres-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres-headless
  labels:
    app: postgres
spec:
  ports:
    - port: 5432
      name: postgres
  clusterIP: None  # Headless Service
  selector:
    app: postgres

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-headless  # Headless Service 연결
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15
          ports:
            - containerPort: 5432
              name: postgres
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "postgres"]
            initialDelaySeconds: 5
            periodSeconds: 10

  # PersistentVolumeClaim 템플릿
  volumeClaimTemplates:
    - metadata:
        name: postgres-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: standard
        resources:
          requests:
            storage: 10Gi

  # 업데이트 전략
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0  # 모든 Pod 업데이트

  # Pod 관리 정책
  podManagementPolicy: OrderedReady

Pod DNS 및 접근

# StatefulSet Pod DNS 형식
# {pod-name}.{headless-service}.{namespace}.svc.cluster.local

# 예시: postgres StatefulSet (3 replicas)
# postgres-0.postgres-headless.default.svc.cluster.local
# postgres-1.postgres-headless.default.svc.cluster.local
# postgres-2.postgres-headless.default.svc.cluster.local

# 같은 네임스페이스에서 접근
psql -h postgres-0.postgres-headless -U postgres

# 애플리케이션 연결 문자열 (Primary)
postgresql://postgres:password@postgres-0.postgres-headless:5432/mydb

# kubectl로 상태 확인
kubectl get statefulset postgres
# NAME       READY   AGE
# postgres   3/3     10m

kubectl get pods -l app=postgres
# NAME         READY   STATUS    RESTARTS   AGE
# postgres-0   1/1     Running   0          10m
# postgres-1   1/1     Running   0          9m
# postgres-2   1/1     Running   0          8m

# PVC 확인 (Pod별 별도 PVC)
kubectl get pvc
# NAME                    STATUS   VOLUME    CAPACITY   ACCESS MODES
# postgres-data-postgres-0   Bound   pvc-xxx   10Gi       RWO
# postgres-data-postgres-1   Bound   pvc-yyy   10Gi       RWO
# postgres-data-postgres-2   Bound   pvc-zzz   10Gi       RWO

Canary 업데이트 (partition)

# partition을 사용한 단계적 롤아웃
# partition=2면 인덱스 2 이상 Pod만 업데이트 (postgres-2만)

# 1. partition 설정으로 일부만 업데이트
kubectl patch statefulset postgres -p \
  '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'

# 2. 이미지 업데이트 (postgres-2만 업데이트됨)
kubectl set image statefulset/postgres postgres=postgres:16

# 3. postgres-2 테스트 후 partition 낮추기
kubectl patch statefulset postgres -p \
  '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":1}}}}'
# postgres-1, postgres-2 업데이트됨

# 4. 전체 롤아웃
kubectl patch statefulset postgres -p \
  '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
# postgres-0, postgres-1, postgres-2 모두 업데이트됨

🗣️ 실무 대화 예시

아키텍처 설계

개발자: "DB를 Kubernetes에 올리려는데 Deployment로 하면 될까요?"

시니어: "DB는 StatefulSet을 써야 해. Deployment는 Pod 이름이 랜덤이고, PVC도 Pod 삭제되면 관리가 어려워. StatefulSet은 postgres-0, postgres-1처럼 고정 이름이고, 각 Pod마다 전용 PVC가 유지돼."

개발자: "마스터-슬레이브 구성은요?"

시니어: "postgres-0을 마스터로 고정하고, postgres-1, postgres-2를 슬레이브로 설정해. Headless Service 덕분에 각 Pod에 직접 DNS로 접근할 수 있어서 역할 분리가 쉬워."

기술 면접

면접관: "StatefulSet과 Deployment의 차이점을 설명해주세요."

지원자: "세 가지 주요 차이가 있습니다. 첫째, Pod 이름이 고정됩니다. Deployment는 랜덤 해시가 붙지만 StatefulSet은 0, 1, 2처럼 순차 인덱스입니다. 둘째, Headless Service와 함께 각 Pod에 고유 DNS가 부여됩니다. 셋째, PVC가 Pod와 1:1로 매핑되어 Pod가 재시작되어도 같은 볼륨을 사용합니다."

면접관: "언제 StatefulSet을 사용해야 하나요?"

지원자: "데이터베이스, Kafka, ZooKeeper처럼 각 인스턴스가 고유한 ID를 가지고 영구 저장소가 필요한 경우입니다. 반면 웹 서버처럼 상태가 없는 앱은 Deployment가 적합해요."

트러블슈팅

운영팀: "StatefulSet 스케일아웃이 안 돼요. Pod가 Pending 상태예요."

개발자: "PVC 프로비저닝 문제일 가능성이 높아요. StorageClass가 제대로 설정되어 있는지, 그리고 스토리지 용량이 충분한지 확인해보세요. kubectl describe pvc로 이벤트 확인하면 원인 나올 거예요."

운영팀: "Pod 삭제했는데 PVC가 남아있어요."

개발자: "StatefulSet은 의도적으로 PVC를 삭제하지 않아요. 데이터 보존을 위해서죠. 정말 삭제하려면 수동으로 kubectl delete pvc 해야 해요."

⚠️ 주의사항

🔗 관련 용어

Deployment PersistentVolume Headless Service VPA Operator

📚 더 배우기