StatefulSet: 개념, 장점, 그리고 동작 흐름도
- StatefulSet은 쿠버네티스에서 상태를 가지는 애플리케이션(Stateful Application)을 안정적으로 배포하고 관리하기 위한 핵심 오브젝트다.
- 일반적인 웹 서버처럼 상태 보관 필요없는(Stateless) 애플리케이션은
Deployment로 관리하는 데 반해, 데이터베이스나 메시지 큐처럼 데이터를 지속적으로 저장하고 고유한 식별자가 필요한 애플리케이션은StatefulSet을 사용하는 것이 필수적이다.
StatefulSet은 이름 그대로 '상태'를 유지하는 데 중점을 둔다. Deployment가 Pod을 교체할 때마다 새로운 이름과 IP를 할당받는 일시적인(Ephemeral) 존재로 다룬다면, StatefulSet은 Pod이 재시작되거나 재배포되어도 동일한 '정체성'과 '데이터'를 유지하도록 보장한다.
이를 위해 StatefulSet은 다음과 같은 3가지 핵심적인 보장 사항을 제공한다.
| 핵심 특징 | 설명 | 예시 (my-app StatefulSet, my-service Headless Service) |
|---|---|---|
| 안정적인 고유 식별자 | Pod이 재생성되어도 변하지 않는 고유한 이름을 부여한다 | my-app-0, my-app-1, my-app-2 |
| 안정적인 네트워크 | Pod의 고유한 이름을 기반으로 DNS 주소를 고정적으로 제공한다 | my-app-0.my-service, my-app-1.my-service |
| 안정적인 스토리지 | 각 Pod에 영구적인 스토리지(PVC)를 1:1로 연결하고, Pod이 재생성되어도 동일한 스토리지를 다시 연결해 준다 | my-app-0은 항상 pvc-my-app-0에 연결 |
왜 필요한가?
my-db-0.my-db)를 알아야 한다. 또한, 각 노드는 자신의 데이터를 잃어버리면 안 된다.StatefulSet을 사용하면 상태를 가진 애플리케이션을 쿠버네티스 환경에서 운영할 때 다음과 같은 명확한 이점을 얻을 수 있다.
가장 큰 장점이다. Pod이 어떤 이유로든(노드 장애, 업데이트 등) 삭제되고 새로 생성되더라도, 기존에 사용하던 PVC(PersistentVolumeClaim)를 그대로 다시 연결한다. 따라서 데이터가 유실되지 않고 안전하게 보존된다.
<pod-name>.<service-name> 형태의 안정적인 DNS 이름을 제공하기 때문에, 클러스터 내 다른 Pod들이 변경 없는 주소로 상대방을 식별하고 통신할 수 있다. 이는 서비스 디스커버리(Service Discovery) 복잡성을 크게 줄여준다.
-> 왜냐면 pod 가 재기동(reschedule)되어도 pod명이 계속 유지되기 때문에 가능
deployment는 재기동되면 계속 이름이 바뀐다.
StatefulSet은 Pod을 생성, 스케일링, 삭제할 때 정해진 순서(Ordered)를 따른다.
0번 Pod부터 순서대로 생성된다. (my-app-0 -> my-app-1 -> my-app-2)my-app-2 -> my-app-1 -> my-app-0)이 순서 보장은 데이터베이스 클러스터처럼 마스터 노드가 먼저 준비되어야 슬레이브 노드가 연결될 수 있는 환경에서 매우 중요하다.
위의 장점들이 모두 합쳐져, 복잡한 분산 시스템(클러스터)을 쿠버네티스 위에 구축하는 것이 훨씬 쉬워진다. 개발자가 직접 Pod의 상태를 추적하거나 스토리지를 수동으로 연결할 필요 없이, StatefulSet이라는 선언적(Declarative) YAML 파일만으로 시스템 전체의 안정성을 보장받을 수 있다.
StatefulSet을 사용하려면 반드시 알아야 할 필수 구성 요소들이 있다.
StatefulSet(sts)은 반드시 Headless Service와 함께 사용해야 한다.
Headless Service는 clusterIP: None으로 설정된 서비스로, 각 Pod에 개별 DNS 레코드를 생성해준다.
생각해보면, sts장점이 pod 명이유지된다는 것이기 때문에 이를 최대한 활용하기 위한 방법이지 않을까싶다. IP는 POD 재기동시 DEPLOYMENT와 동일하게 변경되기 때문에
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
clusterIP: None # Headless Service 핵심 설정
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
이렇게 설정하면 각 Pod은 다음과 같은 DNS로 접근 가능해진다:
my-app-0.my-service.default.svc.cluster.localmy-app-1.my-service.default.svc.cluster.localvolumeClaimTemplates는 StatefulSet의 핵심 기능 중 하나이다. 각 Pod마다 독립적인 PVC를 자동으로 생성해준다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-app
spec:
serviceName: "my-service"
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard"
resources:
requests:
storage: 10Gi
위 설정으로 생성되는 PVC:
data-my-app-0data-my-app-1data-my-app-2StatefulSet은 두 가지 Pod 관리 정책을 제공한다.
spec:
podManagementPolicy: OrderedReady
spec:
podManagementPolicy: Parallel
StatefulSet의 업데이트 전략은 두 가지가 있다.
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0 # 이 번호 이상의 Pod만 업데이트
partition 값을 설정하면 카나리 배포가 가능하다partition: 2이면 my-app-2, my-app-3, ... 만 업데이트되고 my-app-0, my-app-1은 그대로 유지된다spec:
updateStrategy:
type: OnDelete
중요한 점: StatefulSet을 삭제하거나 스케일 다운해도 PVC는 자동으로 삭제되지 않는다.
이는 데이터 보호를 위한 의도적인 설계이다. PVC를 삭제하려면 수동으로 삭제해야 한다.
# StatefulSet 삭제 후에도 PVC는 남아있음
kubectl delete statefulset my-app
# PVC 수동 삭제
kubectl delete pvc data-my-app-0 data-my-app-1 data-my-app-2
Kubernetes 1.27+에서는 persistentVolumeClaimRetentionPolicy로 이 동작을 제어할 수 있다:
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete # StatefulSet 삭제 시 PVC도 삭제
whenScaled: Retain # 스케일 다운 시 PVC 유지
아래 흐름도는 replicas: 3으로 설정된 my-app StatefulSet을 생성(kubectl apply)할 때의 동작 과정을 순서대로 나타낸 것이다.

StatefulSet YAML 파일을 적용한다.StatefulSet 컨트롤러가 이 변경 사항을 감지한다.0번 Pod부터 생성을 시도한다.data-my-app-0 라는 이름의 PVC를 생성하고, 스토리지가 할당될 때까지(Bound 상태) 기다린다.my-app-0 라는 이름의 Pod를 생성한다. 이 Pod는 방금 생성한 PVC를 자동으로 연결한다.my-app-0 Pod가 완전히 실행 준비(Ready) 상태가 되면, 그다음 1번 Pod를 동일한 과정으로 생성한다.replicas에 지정된 수(여기서는 3)만큼 반복한다.replicas를 3에서 4로 늘리면, 기존에 생성된 Pod(0, 1, 2)는 그대로 두고 3번 Pod부터 동일한 순서에 따라 생성된다.replicas를 3에서 2로 줄이면, 가장 마지막에 생성된 Pod부터 역순으로 삭제된다. 즉, my-app-2 Pod가 삭제되지만 PVC는 유지된다 (기본 동작).| 항목 | StatefulSet | Deployment |
|---|---|---|
| Pod 이름 | 고정 (my-app-0, my-app-1) | 랜덤 (my-app-7d8f9-abc12) |
| 네트워크 ID | 고정 DNS | 변경될 수 있음 |
| 스토리지 | Pod별 고정 PVC | 공유 또는 없음 |
| 생성 순서 | 순차적 (0→1→2) | 병렬 |
| 삭제 순서 | 역순 (2→1→0) | 병렬 |
| 롤링 업데이트 | 역순 | 임의 |
| 사용 사례 | DB, Kafka, Elasticsearch | 웹 서버, API 서버 |
대부분 PVC가 Bound되지 않아서 발생한다. StorageClass가 올바르게 설정되어 있는지, PV가 충분한지 확인해야 한다.
kubectl get pvc
kubectl describe pvc data-my-app-0
Pod을 삭제하면 StatefulSet 컨트롤러가 동일한 이름, 동일한 PVC로 재생성해준다.
kubectl delete pod my-app-1
OnDelete 업데이트 전략을 사용하면 Pod을 하나씩 수동으로 삭제하며 안전하게 마이그레이션할 수 있다.
##주로 deployment와 statefulset 많이 사용하는데,
볼륨 활용관점에서 헷갈려서 비교


개인 서비스를 deployment 가 아닌 statefulset 으로 띄운다면 pod 가 내려가도 ip와 도메인이 안바뀌고 유지되는 건가?
이것이 StatefulSet의 핵심 장점이자 가장 중요한 부분이다.
StatefulSet은 Pod를 생성할 때 my-app-0, my-app-1과 같이 순번(ordinal)이 포함된 고유한 이름을 부여한다.
StatefulSet은 일반적으로 clusterIP: None 설정의 Headless Service와 함께 사용된다. 이 서비스는 자체적으로 클러스터 IP를 갖지 않고, 대신 DNS 조회 요청이 오면 연결된 Pod들의 IP 주소 목록을 반환한다.
이 두 가지가 결합되어 my-app-0.my-headless-service와 같은 고정된 DNS 이름이 생성된다.
만약 my-app-0 Pod가 노드 장애 등으로 삭제되고 새로 생성되더라도, 새로운 Pod의 이름은 여전히 my-app-0이다. 따라서 DNS 이름인 my-app-0.my-headless-service는 절대 변하지 않는다.
애플리케이션이 다른 Pod를 찾을 때 IP 주소가 아닌 이 고정된 DNS 이름을 사용한다면, Pod가 교체되어도 통신에 문제가 없다.
쿠버네티스에서 Pod의 IP 주소는 Pod가 스케줄링된 노드(Worker Node)에 의해 동적으로 할당된다.
만약 my-app-0 Pod가 다른 노드로 재스케줄링되거나, 같은 노드에서 다시 생성되더라도 새로운 IP 주소를 할당받을 가능성이 매우 높다.
StatefulSet은 Pod의 이름이 고정되도록 보장할 뿐, Pod가 특정 IP 주소를 가지도록 보장하지는 않는다.
StatefulSet은
my-app-0.my-headless-service라는 이름표(DNS)는 고정시켜주지만, 그 이름표가 붙은 Pod의 실제 주소(IP)는 여전히 바뀔 수 있다.
| 구분 | Deployment | StatefulSet |
|---|---|---|
| Pod 이름 | 매번 재생성 시 랜덤 해시값 포함 (예: my-app-5f7d8b9c-abcde) | 순번으로 고정됨 (예: my-app-0) |
| Pod IP | 변경됨 (보장 안 됨) | 변경됨 (보장 안 됨) |
| 도메인 (DNS) | 변경됨 (Pod 이름이 바뀌므로) | 고정됨 (Pod 이름이 고정되므로) |
| 스토리지 | 여러 Pod가 하나의 PVC를 공유 가능 (ReadWriteMany) | 각 Pod이 전용 PVC를 1:1로 연결 (ReadWriteOnce) |
대부분의 경우, 일반적인 서비스는 Deployment를 사용하는 것이 표준이고 올바른 선택이다.
Deployment에 PVC(PersistentVolumeClaim)를 하나만 연결해서 사용하면 된다. Pod가 재생성되어도 같은 PVC를 다시 붙이기 때문에 데이터는 유지된다.