statefulset

YYY·2025년 5월 11일

deploy,replica의 경우 pvc공유 같은 곳에 저장되는거임

Kubernetes StatefulSet 완벽 가이드

🎯 목차

  1. StatefulSet이란?
  2. 주요 특징
  3. Deployment vs StatefulSet
  4. PersistentVolume / PersistentVolumeClaim 필요성
  5. 동적 vs 정적 프로비저닝
  6. CephFS + StatefulSet 연동 예시
  7. 샤딩(Sharding)과 StatefulSet
  8. 정리 및 활용 팁

1. StatefulSet이란?

  • 정의: 파드의 안정된 정체성(identity)과 영구 스토리지를 보장하는 컨트롤러
  • 사용 사례:
    • 데이터베이스(MySQL/Vitess)
    • 메시징 시스템(Kafka)
    • 분산 캐시(Redis Cluster)
  • 다음 중 하나 이상이 필요한 애플리케이션에 유용
    • 안정적이고 고유한 네트워크 식별자.
    • 안정적이고 지속적인 저장.
    • 체계적이고 원활한 배포 및 확장.
    • 순서대로 자동 업데이트가 진행됩니다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  serviceName: myapp-headless      # Headless Service 연동
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: your-image
          volumeMounts:
            - name: data
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 10Gi
        storageClassName: standard
        

2. 주요 특징

StatefulSet은 일반적인 Deployment와는 다르게, 파드마다 고유한 네트워크 ID와 볼륨을 유지하는 데 중점을 둡니다.
다음은 StatefulSet이 제공하는 주요 기능들입니다.

🔹 1. Stable Hostname (고정된 파드 이름)

  • 파드는 항상 name-ordinal 형식으로 생성됩니다.
    예: myapp-0, myapp-1, myapp-2
  • DNS도 고정됨:
    myapp-0.myapp-headless.default.svc.cluster.local

🔹 2. 고유한 PVC (1:1 스토리지)

  • volumeClaimTemplates 필드를 통해
    각 파드에 전용 PVC(PersistentVolumeClaim) 가 자동 생성됩니다.
  • 예:
    • myapp-0data-myapp-0 PVC
    • myapp-1data-myapp-1 PVC

🔹 3. 순차적 롤아웃 및 스케일링 (OrderedReady)

  • 기본적으로 파드는 순차적으로 생성되고 준비됩니다.
    • myapp-0이 Ready 되어야 myapp-1이 생성됨
  • 삭제할 때도 역순으로 제거됩니다.

🔹 4. Headless Service와 결합

  • clusterIP: None 으로 설정된 Headless Service와 함께 사용하면
    파드마다 개별 DNS 이름을 사용할 수 있습니다.
  • 예:
    • myapp-0.myapp-headless.default.svc

이 특성들 덕분에 StatefulSet은 각 인스턴스가 자기 데이터를 유지해야 하는

데이터베이스, 메시지 브로커, 캐시 서버 등에 매우 적합합니다.

3. Deployment vs StatefulSet

쿠버네티스에서 파드를 여러 개 띄우는 대표적인 방식은 DeploymentStatefulSet입니다.
이 둘은 파드를 관리하는 방식, 스토리지 사용 여부, 네트워크 접근 방식 등에서 큰 차이를 보입니다.

📊 주요 차이점 비교

항목Deployment (무상태)StatefulSet (상태 저장)
파드 이름무작위 이름 (예: nginx-7d5f7c5d9b-abcde)고정 이름 (예: nginx-0, nginx-1)
생성/삭제 순서무순서 (동시 처리 가능)순차적으로 생성/삭제 (OrderedReady)
네트워크 접근 방식ClusterIP + 라운드로빈 로드밸런싱Headless Service + 고정 DNS (pod-0.svc)
스토리지공유 PVC 사용하거나 볼륨 없음파드마다 고유 PVC (volumeClaimTemplates)
파드 교체 시 상태 유지 여부유지 안 됨 (새 파드 = 새 볼륨 또는 무볼륨)유지됨 (이전 PVC 그대로 재사용)
주요 사용 사례웹 서버, API 서버, 워커 등 무상태 서비스DB, Kafka, Redis 등 상태 저장 서비스

✅ 언제 무엇을 써야 할까?

  • Deployment는 모든 파드가 서로 바뀌어도 무관한 경우 → 웹/API 서버
  • StatefulSet은 각 파드가 자기 상태나 데이터를 보존해야 할 때 → DB, 메시징 시스템 등

4. 왜 PVC/PV가 필요한가?

쿠버네티스에서 파드는 기본적으로 휘발성(ephemeral) 입니다.
즉, 파드가 삭제되거나 재시작되면 로컬 디스크에 있던 모든 데이터가 사라집니다.

하지만 상태 저장 서비스(데이터베이스, 업로드 파일 저장 등)는 다음과 같은 이유로
지속적인 저장소(Persistent Storage) 가 필요합니다:

💾 주요 이유

  • 데이터 손실 방지
    • 파드가 재시작되어도 이전 데이터를 유지해야 함
  • 장기 보존 및 백업 가능
    • 스냅샷, 백업을 PVC 단위로 수행 가능
  • 파드와 데이터의 분리
    • 애플리케이션(파드)은 바뀌어도, 데이터는 유지됨

🔧 구조 설명

  • PV (PersistentVolume)

    • 쿠버네티스 클러스터에서 운영자가 제공하는 실제 스토리지 리소스
    • 예: AWS EBS, Ceph RBD, NFS 등
  • PVC (PersistentVolumeClaim)

    • 파드가 원하는 크기와 접근 방식에 따라 스토리지를 요청하는 리소스
    • PVC가 있으면 쿠버네티스가 자동으로 PV와 연결해 줌
# PVC 예시
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-data
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard

StatefulSet에서는 volumeClaimTemplates를 통해
파드마다 자동으로 고유 PVC를 생성하여 바인딩합니다.

✅ 정리

파드가 사라져도 데이터를 유지하고 싶다면 → PVC + PV 필수

StatefulSet은 PVC를 파드 수만큼 자동으로 생성해 관리합니다.


5. 동적 vs 정적 프로비저닝

쿠버네티스에서 PVC(PersistentVolumeClaim)를 만들 때,
어떻게 PV(PersistentVolume)를 연결하느냐에 따라 프로비저닝 방식은 두 가지로 나뉩니다.

🔹 동적 프로비저닝 (Dynamic Provisioning)

  • 사용자가 PVC만 작성하면,
  • StorageClass에 따라 Kubernetes가 PV를 자동으로 생성하고 연결합니다.
  • 실무에서 가장 널리 사용되는 방식입니다.

✅ PVC 예시

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard

🔹 정적 프로비저닝 (Static Provisioning)

  • 운영자가 PersistentVolume(PV)미리 수동으로 생성하고,
  • PVC에서 volumeName 을 명시해 특정 PV를 직접 바인딩합니다.
  • Kubernetes가 자동으로 PV를 만들어 주지 않기 때문에 완전히 수동입니다.

✅ 예시 1: PV 수동 생성

apiVersion: v1
kind: PersistentVolume
metadata:
  name: static-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /mnt/data/static
  persistentVolumeReclaimPolicy: Retain

✅ 예시 2: PVC에서 volumeName 으로 PV 지정

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: static-pvc
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 5Gi
  volumeName: static-pv

위 PVC는 volumeName: static-pv 를 통해
사전에 수동으로 만들어둔 PersistentVolume 리소스에 직접 연결됩니다.

  • 이 방식에서는 storageClassName 을 사용하지 않으며,
    Kubernetes가 PV를 자동 생성해주지 않습니다.
  • PVC와 PV는 다음 조건이 모두 일치해야 바인딩이 성사됩니다:
    • accessModes
    • resources.requests.storage (크기)
    • volumeMode (Block/File)

PVC가 바인딩되었는지 확인하려면 다음 명령어를 실행합니다:

kubectl get pvc static-pvc

✅ 정리

  • 동적 프로비저닝은 PVC만 만들면 Kubernetes가 자동으로 PV를 생성해 주는 방식입니다.
    실무에서 가장 일반적이고, 대부분의 StatefulSet, Deployment, 웹서비스 등에서 사용됩니다.

  • 정적 프로비저닝은 운영자가 직접 PV를 만들어 놓고,
    PVC에서 해당 PV를 volumeName으로 명시해 연결하는 방식입니다.
    일반적인 상황에서는 불편하지만, 특정 디스크나 특수한 환경에서는 필요할 수 있습니다.

  • 둘의 핵심 차이는 다음과 같습니다:

항목동적 프로비저닝정적 프로비저닝
PV 생성 시점PVC 생성 시 자동 생성운영자가 미리 수동 생성
PVC 작성 방식storageClassName 지정만 필요volumeName 으로 특정 PV 직접 지정
자동화 수준높음 (간편함)낮음 (수동 매핑 필요)
세부 설정 제어 수준낮음 (StorageClass에 위임됨)높음 (PV 정의에서 직접 제어 가능)
일반적인 사용 예시대부분의 워크로드, StatefulSet 포함테스트 환경, 로컬 디스크, 수동 디스크 지정 시 등

6. CephFS + StatefulSet 연동 예시

StatefulSet을 사용할 때 파드 간에 데이터를 공유하거나,
파드 재시작 후에도 데이터를 유지하려면 영속적이고 공유 가능한 파일 시스템이 필요합니다.

CephFS는 ReadWriteMany(RWX)를 지원하는 대표적인 분산 파일 시스템이며,
쿠버네티스에서는 Ceph CSI 드라이버를 통해 StorageClass 로 연동할 수 있습니다.

📦 StorageClass 정의 (CephFS)

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cephfs
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
  clusterID: rook-ceph
  fsName: myfs
  pool: myfs-data0
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true

이 StorageClass는 CephFS 클러스터를 기반으로 PVC 요청이 들어올 때마다
RWX(ReadWriteMany)를 지원하는 공유 볼륨을 자동으로 프로비저닝합니다.

  • provisioner: CephFS CSI 드라이버를 사용하여 실제 볼륨을 생성합니다.
  • parameters: Ceph 클러스터 ID, 풀 이름, 인증 시크릿 등을 설정합니다.
  • volumeBindingMode: Immediate: PVC가 생성되자마자 볼륨이 바인딩됩니다.
  • allowVolumeExpansion: true: 나중에 PVC 용량을 확장할 수 있습니다.

이 StorageClass는 StatefulSet의 volumeClaimTemplates 내부에서
storageClassName: cephfs 로 연결해 사용할 수 있습니다.

🌐 Headless Service (StatefulSet용)

apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
spec:
  clusterIP: None
  selector:
    app: myapp
  ports:
    - port: 80

이 Headless Service는 clusterIP: None 으로 설정되어 있어
파드마다 고유한 DNS 이름이 생성됩니다.

예를 들어, StatefulSet 파드가 myapp이라는 이름으로 구성되었다면:

  • myapp-0.myapp-headless.default.svc.cluster.local
  • myapp-1.myapp-headless.default.svc.cluster.local

이런 식으로 각 파드별 DNS가 자동 생성되어,
다른 파드나 외부에서 직접 해당 파드로 통신할 수 있는 주소를 제공합니다.

이 구조는 다음과 같은 상황에서 유용합니다:

  • 각 파드가 서로를 고정된 이름으로 참조해야 할 때 (예: DB 클러스터, 리더 선출 등)
  • 파드 간 직접 통신이 필요할 때
  • 외부 시스템에서 특정 파드에 직접 접근하고자 할 때

Headless Service는 StatefulSet의 특성과 함께 사용할 때
고정된 네트워크 식별자를 제공하는 핵심 역할을 합니다.

🧱 StatefulSet 구성 예시

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  serviceName: myapp-headless
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: uploads
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: uploads
    spec:
      accessModes: ["ReadWriteMany"]
      resources:
        requests:
          storage: 5Gi
      storageClassName: cephfs

이 StatefulSet 구성은 다음과 같은 목적을 가지고 있습니다:

  • 파드마다 고유한 PVC를 생성하여 각 인스턴스가 독립된 스토리지를 사용하거나,
  • 또는 CephFS처럼 RWX를 지원하는 공유 스토리지를 마운트해
    모든 파드가 같은 경로에 접근할 수 있도록 구성할 수도 있습니다.

구성 요약

  • replicas: 2myapp-0, myapp-1 파드가 생성됩니다.
  • serviceName: myapp-headless → Headless Service를 통해
    각 파드는 고정된 DNS를 가지게 됩니다.
  • volumeClaimTemplates → StatefulSet이 자동으로 PVC를 생성합니다.
    • 예: uploads-myapp-0, uploads-myapp-1
  • storageClassName: cephfs → CephFS StorageClass를 통해 RWX 지원 볼륨을 사용합니다.
  • mountPath: /usr/share/nginx/html → 정적 콘텐츠를 공유 디렉터리에 저장합니다.

이 구성을 통해 파드가 재시작되어도 데이터는 유지되며,
여러 파드가 동일한 콘텐츠를 공유할 수 있습니다.


7. 샤딩과 StatefulSet

apiVersion: vitess.io/v1
kind: VitessCluster
metadata:
  name: reservation-db
spec:
  keyspaces:
    - name: reservation
      partitions: 3
      tabletPools:
        readWrite:
          replicas: 1
      storage:
        pvc:
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 10Gi
          storageClassName: ceph-rbd

이 예시는 Vitess를 사용해 MySQL을 샤딩하고,
각 샤드를 StatefulSet 구조로 파드별 고정 볼륨(PVC) 에 연결하는 구조입니다.

설명

  • partitions: 3
    reservation keyspace를 3개의 샤드로 분할합니다.

  • tabletPools.readWrite.replicas: 1
    → 각 샤드마다 1개의 writable MySQL 인스턴스를 띄웁니다.

  • storage.pvc
    → 파드별로 PVC를 생성하며, ceph-rbd StorageClass를 사용해
    Ceph RBD 볼륨을 자동 프로비저닝합니다.

왜 StatefulSet이 필요한가?

  • 각 샤드(vttablet Pod)는 자기만의 데이터를 저장해야 하므로,
    파드 이름(Pod ordinal)과 1:1 매핑되는 고정된 PVC가 필요합니다.

  • 예:

    • vttablet-0 → 샤드 0 → pvc-vttablet-0
    • vttablet-1 → 샤드 1 → pvc-vttablet-1
    • vttablet-2 → 샤드 2 → pvc-vttablet-2

이처럼 StatefulSet은 샤드 분할과 고정 볼륨 제공이라는 두 가지 요구사항을 동시에 만족시켜 줍니다.


8. 요약 및 활용 팁

StatefulSet은 상태 저장 워크로드에 최적화된 쿠버네티스 리소스로,
파드별로 고유한 이름, DNS, 볼륨(PVC)을 자동으로 관리해 줍니다.

하지만, 이를 잘못 구성하면 오히려 Deployment보다 못한 결과를 초래할 수 있습니다.

🚫 잘못된 StatefulSet 예시 (emptyDir 사용)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: bad-statefulset
spec:
  serviceName: bad-headless
  replicas: 2
  selector:
    matchLabels:
      app: bad
  template:
    metadata:
      labels:
        app: bad
    spec:
      containers:
      - name: web
        image: nginx
        volumeMounts:
        - name: tmp
          mountPath: /usr/share/nginx/html
      volumes:
      - name: tmp
        emptyDir: {}
profile
무지렁이 탈출기

0개의 댓글