[K8s] StatefulSet: 순서 보장, 고유 ID, 영속 스토리지
StatefulSet, STS, stable identity, ordered deployment, PV per pod, headless service
정의
StatefulSet = 상태 있는 워크로드를 위한 컨트롤러. Deployment 와의 차이:
| 항목 | Deployment | StatefulSet |
|---|---|---|
| Pod 이름 | random hash (web-7d9c-xpqr) | 순서 (db-0, db-1, db-2) |
| Pod 식별 | 변경됨 | 고정 (sticky identity) |
| 시작 순서 | parallel | 순차 (0 → 1 → 2) |
| 종료 순서 | parallel | 역순 (N-1 → 0) |
| 스토리지 | 공유 또는 ephemeral | pod 별 PVC |
| Headless Service | 옵션 | 필수 |
사용처
flowchart TD
Yes[StatefulSet 적합]
Yes --> Y1["DB (PostgreSQL, MySQL replica)"]
Yes --> Y2["Kafka, Zookeeper"]
Yes --> Y3[Elasticsearch]
Yes --> Y4[etcd]
Yes --> Y5[Cassandra]
No[Deployment 가 충분]
No --> N1[Stateless API]
No --> N2[Web frontend]
No --> N3[Worker]
YAML
apiVersion: v1
kind: Service
metadata: { name: db-headless }
spec:
clusterIP: None # Headless 필수
selector: { app: db }
ports: [{ port: 5432, targetPort: 5432 }]
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: db }
spec:
serviceName: db-headless
replicas: 3
selector: { matchLabels: { app: db } }
template:
metadata: { labels: { app: db } }
spec:
containers:
- name: postgres
image: postgres:17
ports: [{ containerPort: 5432 }]
readinessProbe:
exec:
command: [pg_isready, -U, postgres]
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata: { name: data }
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources: { requests: { storage: 100Gi } }
Pod 이름과 DNS
pod 0: db-0 → db-0.db-headless.default.svc.cluster.local
pod 1: db-1 → db-1.db-headless.default.svc.cluster.local
pod 2: db-2 → db-2.db-headless.default.svc.cluster.local
각 pod 가 고유 DNS 이름. peer 끼리 서로 직접 통신 (replication 등).
Peer Discovery 패턴
StatefulSet + Headless Service 조합이 peer discovery 의 핵심:
flowchart LR
db0["db-0 (Primary)"]
db1["db-1 (Replica 1)"]
db2["db-2 (Replica 2)"]
db0 -->|"WAL 스트리밍"| db1
db0 -->|"WAL 스트리밍"| db2
db1 -.->|"db-0.db-headless 연결"| db0
db2 -.->|"db-0.db-headless 연결"| db0
- Replica 가 Primary 에 연결:
postgresql://db-0.db-headless:5432 - pod 재시작 후에도 같은 DNS 유지
- 애플리케이션이 Primary 주소를 안정적으로 참조 가능
시작 / 종료 순서
sequenceDiagram
autonumber
Note over STS: Create / Scale up
STS->>Pod0: start (ready 대기)
Pod0-->>STS: ready
STS->>Pod1: start (ready 대기)
Pod1-->>STS: ready
STS->>Pod2: start (ready 대기)
Note over STS: Delete / Scale down (역순)
STS->>Pod2: terminate
STS->>Pod1: terminate
STS->>Pod0: terminate
IMPORTANT
Master-replica DB 처럼 순서가 중요 한 경우 핵심. podManagementPolicy: Parallel 로 순차 종속성 제거 가능 (Kafka 같이 peer 끼리 sync).
initContainers로 초기화
Replica pod 가 시작 전 Primary 에서 데이터를 받아야 할 때:
initContainers:
- name: init-replica
image: postgres:17
command:
- bash
- -c
- |
# db-0 (Primary) 은 초기화 스킵
[ "$(hostname)" = "db-0" ] && exit 0
# Primary 에서 base backup 취득
pg_basebackup -h db-0.db-headless \
-U replicator -D /var/lib/postgresql/data \
-Xs -R
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
db-0(Primary) 은 initContainer 를 스킵db-1,db-2는 기동 전 Primary 에서 base backup 취득- 데이터가 있는 상태 에서 Replica 시작 보장
PVC per Pod (영속 스토리지)
flowchart LR
STS[StatefulSet] --> P0[db-0]
STS --> P1[db-1]
STS --> P2[db-2]
P0 --> PVC0[("data-db-0")]
P1 --> PVC1[("data-db-1")]
P2 --> PVC2[("data-db-2")]
- pod 재생성 후에도 같은 PVC 재연결
- pod 삭제 해도 PVC 보존 (직접 삭제 필요)
PVC 생명주기 관리
| 작업 | 동작 |
|---|---|
| Pod 재시작 | PVC 자동 재연결 |
| Scale down | PVC 보존 (삭제 안 됨) |
| StatefulSet 삭제 | PVC 보존 (수동 삭제 필요) |
| PVC 용량 확장 | StorageClass allowVolumeExpansion: true 필요 |
# K8s 1.27+: StatefulSet 삭제/축소 시 PVC 정책 설정
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete # StatefulSet 삭제 시 PVC 도 삭제
whenScaled: Retain # Scale down 시 PVC 유지
Update 전략
| 전략 | 의미 |
|---|---|
RollingUpdate (기본) | 순차 업데이트 (N-1 → 0) |
OnDelete | 수동 삭제 시 만 새 spec |
partition: N | 인덱스 ≥ N 만 업데이트 (canary) |
Canary Update (partition 활용)
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # db-2 만 먼저 업데이트
sequenceDiagram
autonumber
Note over STS: partition=2 설정
STS->>db2: 새 이미지로 업데이트
db2-->>STS: 정상 확인
Note over STS: partition=1 로 변경
STS->>db1: 새 이미지로 업데이트
db1-->>STS: 정상 확인
Note over STS: partition=0 으로 변경
STS->>db0: 새 이미지로 업데이트
문제 발생 시: image 를 이전 버전으로 되돌리면 이미 업데이트된 pod 도 재시작 시 롤백.
운영 패턴: Operator
flowchart LR
Op["Operator (controller)"] -->|관리| STS[StatefulSet]
Op -->|backup| Backup[("S3 backup")]
Op -->|failover| Promote["Replica to Primary"]
Op -->|scale| Add[새 replica 추가]
- DB 운영의 표준 (PostgreSQL Operator, Kafka Strimzi, ElasticSearch ECK)
- StatefulSet 만으로 부족한 애플리케이션 특화 로직 자동화
| 대상 | 대표 Operator |
|---|---|
| PostgreSQL | CNPG (CloudNativePG), Zalando postgres-operator |
| Kafka | Strimzi |
| Elasticsearch | ECK (Elastic Cloud on K8s) |
| Redis | Redis Operator |
Primary 장애 복구 흐름
Operator 없이 수동 failover 시:
sequenceDiagram
autonumber
Note over db0: Primary 다운
db1->>db1: pg_promote 실행
db2->>db1: 복제 재연결 (db-0 → db-1)
Note over Admin: db-0 PVC 데이터 초기화
Admin->>db0: pod 재시작
db0->>db1: base backup 취득 후 Replica 합류
IMPORTANT
Operator (예: CNPG, Zalando) 를 사용하면 이 모든 흐름이 자동화. 수동 StatefulSet 만으로 DB HA 구현은 권장하지 않음.
흔한 함정
WARNING
- Headless Service 없음 = pod DNS 안 잡힘 → peer discovery 실패.
partition활용 안 함 = canary 불가. 모든 pod 한 번에 업데이트.- PVC delete 정책 = StatefulSet 삭제 시 PVC 보존 (기본). 의도적으로 삭제 안 하면 영구 storage cost.
- DB 의 순서 의존성 + parallel 정책 잘못 = 데이터 손상.
- initContainer 없이 Replica 시작 = 빈 PVC 로 Replica 가 기동, 데이터 없는 상태.
- Operator 없이 DB HA 구현 = failover, backup, scale 을 손으로. Operator 사용 권장.
관련 위키
이 글의 용어 (5개)
- [DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성database-internals
- 정의 PostgreSQL 은 오픈소스 ORDBMS. 1986 UC Berkeley POSTGRES 의 후예. MVCC, 확장 가능 타입, JSONB, full-text searc…
- [Distributed] Kafka: 분산 로그, partition, consumer groupdistributed-systems
- 정의 Apache Kafka = 분산 commit log. 고처리량 (수백만 msg/s), 영속, 수평 확장. event-driven 아키텍처 의 de facto. 핵심 개념: …
- [K8s] Deployment: ReplicaSet, rolling update, rollbackkubernetes
- 정의 Deployment = stateless 워크로드를 위한 컨트롤러. 내부적으로 ReplicaSet 관리 + rolling update / rollback. 사용 시나리오 |…
- [K8s] Pod: 컨테이너의 최소 단위, sidecar, lifecyclekubernetes
- 정의 Pod = K8s 의 가장 작은 배포 단위. 1개 이상의 컨테이너 + 공유 네트워크 + 공유 스토리지. [!IMPORTANT] Pod 는 컨테이너의 wrapping 이 아니…
- [K8s] Service: ClusterIP / NodePort / LoadBalancer / ExternalNamekubernetes
- 정의 Service = Pod 집합에 안정 가상 IP + DNS 부여. Pod 가 죽고 다시 만들어져도 Service IP 는 그대로. 4가지 타입 1. ClusterIP (기본…
이 개념을 다룬 위키 페이지 (8)
- wikiStateful
- wikiStateless
- wiki[K8s] Deployment: ReplicaSet, rolling update, rollback
- wiki[Kubernetes] Persistent Volumes (PV / PVC / StorageClass)
- wiki[K8s] Pod: 컨테이너의 최소 단위, sidecar, lifecycle
- wiki[Kubernetes] Scheduling (Taints, Affinity, Topology Spread)
- wiki[K8s] Service: ClusterIP / NodePort / LoadBalancer / ExternalName
- wikiKubernetes
💬 댓글