쿠버네티스의 대표적인 워크로드 리소스인 Statefulset(스테이트풀셋)을 살펴보는 섹션입니다 :)
Statefulset - 스테이트풀셋
먼저 쿠버네티스의 공식문서에 따르면 Statefulset은 다음과 같이 정의되어 있습니다.
"스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는데 사용하는 워크로드 API 오브젝트이다."
"파드 집합의 디플로이먼트와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다."
"디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스팩으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다."
해당 내용을 조금 더 풀어서 해석하자면, 스테이트풀셋(Statefulset)은 어플리케이션의 상태를 관리할때 사용하는 워크로드 리소스입니다.
디플로이먼트(Deployment)와 스테이트풀셋(Statefulset)은 파드 생성과 관리, 스케일 관리 측면에서는 동일합니다. 하지만 스테이트풀셋(Statefulset)은 각 파드의 독자적으로 관리하게 됩니다. 즉, 파드의 고유성과 순서를 보장하며 각각의 파드별로 영구적인 스토리지 볼륨 공간을 갖게됩니다.
디플로이먼트와의 차이점
- 각 파드는 고유하며 영구적인 식별자를 갖게 됩니다.
- 각 파드는 삭제 후 재생성시 자신에게 해당되었던 식별자를 그대로 유지합니다.
- 각 파드의 식별자 끝에는 번호가 붙게됩니다. 이 번호는 Replicas에서 선언한 수를 기준으로 0부터 시작하게 됩니다.
- 각 파드의 생성과 스케일링, 업데이트, 삭제는 위에서 부여된 번호에 따라 순서대로 이루어집니다. 삭제는 반대로 역순으로 진행하게 됩니다.
- 각 파드에는 영구적인 스토리지가 할당됩니다. 해당 파드가 삭제되어도 스토리지는 남게되며, 해당 식별자에 새롭게 생성된 파드는 해당 스토리지를 이어 받게됩니다.
스테이트풀셋 사용
쿠버네티스 공식 문서에 따르면 다음과 같은 상황일때 스테이트풀셋을 사용하면 유용하다고 설명되어 있습니다.
- 파드별로 안정적이고 고유한 네트워크 식별자를 가져야할 때
- 파드별로 안정적이고 지속적인 스토리지 공간을 가져야할 때
- 각 파드별로 순차적이고, 스케일링이 필요할 때
위에 해당하는 예시를 살펴보면 대표적인 예로 데이터베이스(DB)를 떠올릴 수 있습니다. 데이터베이스(DB)를 쿠버네티스 클러스터에 구축한다고 가정했을때, 데이터의 무결성과 안정성을 유지하기 위해서 각 파드별로 고유한 성격을 가지도록 설정해야 합니다.
CRUD(Create, Read, Update, Delete)가 가능한 Pod, R(Read - 읽기전용)만 가능한 Pod, DB의 파드를 감시하고 관리하는 Arbiter Pod로 구성하게 됩니다.
각 Pod 별로 고유한 특성을 가지고 있고, 데이터의 안정성과 무결성을 위해 각각의 스토리지 공간을 별도로 사용해야 합니다. 이런 경우 디플로이먼트(Deployment)가 제공하지 못하는 요소를 해결하기 위해서 나온 워크로드 리소스가 바로 스테이트풀셋(Statefulset) 입니다.
스테이트풀셋 사용 시 제한사항
- 스테이트풀셋을 통해 Pod를 생성하면 각 Pod별 스토리지 공간(PV - Persistent Volume)도 함께 만들어지게 됩니다. 만약 Pod 혹은 스테이트풀셋이 삭제되더라도 스토리지 공간은 삭제되지 않고 유지되게 됩니다. 즉, 스테이트풀셋을 사용하게 된다면 스토리지에 대한 관리도 추가적으로 필요합니다.
- 스테이트풀셋을 통해 Pod를 생성하면 각 Pod별 스토리지 공간(PV - Persistent Volume)도 함께 만들어지게 됩니다. 이를 위해서 클러스터에 StorageClass를 생성하여 동적으로 프로비저닝을 요청하거나 별도의 PV를 생성하여 각 Pod에 매칭시켜야 합니다.
- 스테이트풀셋을 통해 생성된 Pod로 요청(Request)를 전달하기 위해서는 헤드리스 서비스(Headless Service)가 필요합니다. 헤드리스 서비스(Headless)는 각 Pod의 고유한 네트워크 위치를 파악하기 위해서만 사용되므로 ClusterIP는 부여되지 않습니다.
- 스테이트풀셋을 통해 Pod를 생성하면 특정 Pod가 장애가 발생하였을때 자동으로 재시작하는 오토힐링(Autohealing) 기능은 포함되어 있지 않습니다. 이는 앞서 예시로 설명했던 데이터베이스(DB)처럼 데이터의 무결성 및 안정성을 위해서 관리자 판단 하에 수행하도록 설정되어 있습니다. 만약 오토힐링(Autohealing)이 필요한 스테이트풀셋의 경우 Probe 정책을 따로 설정해주어야 합니다.
스테이트풀셋 생성하기
쿠버네티스 공식 문서를 참고하여 yaml 파일을 생성합니다.
(https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # .spec.template.metadata.labels 와 일치해야 한다
serviceName: "nginx"
replicas: 3 # 기본값은 1
minReadySeconds: 10 # 기본값은 0
template:
metadata:
labels:
app: nginx # .spec.selector.matchLabels 와 일치해야 한다
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
- kind : StatefulSet 으로 설정해주세요 ! (대소문자 주의)
- spec.replicas : 디플로이먼트와 마찬가지로 레플리카 수를 입력해주세요.
- spec.serviceName : 헤드리스 서비스(Headless Service) 이름을 입력해주세요 ! (Service 섹션에서 metadata.name이 서비스 이름을 가르키게 됩니다.)
- spec.template.spec : 디플로이먼트와 마찬가지로 배포할 파드에 대해서 정의하는 부분입니다.
- spec.template.spec.volumeMounts , spec.volumeClaimTemplates : 각 파드에 부여할 볼륨에 대한 정보를 정의하는 부분입니다. 스테이트풀셋 특성상 각 Pod에 고유한 스토리지 공간이 할당되게 됩니다. 즉, 관리자는 해당 볼륨을 할당하기 위해서 PV, PVC를 미리 할당해주거나 storageClass를 사용하여 동적 프로비저닝(Dynamic Provisioning)을 수행하도록 설정해야 합니다.
( 필자는 default StorageClass가 생성되어 있어서 spec.volumeClaimTemplates.spec 부분은 생략 후 진행하였습니다 ! )
다음으로 스테이트풀셋의 서비스인 헤드리스 서비스(Headless Service)를 생성합니다.
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
- metadata.name : 스테이트풀셋 Yaml 파일에서 선언한 spec.serviceName과 동일한 이름으로 설정합니다 !
- spec.clusterIP : 헤드리스 서비스는 자체적으로 IP를 가지지 않는 서비스로 None으로 설정합니다 ! (중요)
스테이트풀셋 배포하기
kubectl apply -f statefulset.yaml -n journalctl
kubectl apply -f headless-service.yaml -n journalctl
스테이트풀셋 상태 확인하기
$ kubectl get pod -n journalctl
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2m32s
web-1 1/1 Running 0 82s
web-2 1/1 Running 0 52s
$ kubectl get sts -n journalctl
NAME READY AGE
web 3/3 3m7s
$ kubectl describe sts/web -n journalctl
Name: web
Namespace: journalctl
CreationTimestamp: Sun, 03 Dec 2023 18:54:57 +0900
Selector: app=nginx
Labels: <none>
Annotations: <none>
Replicas: 3 desired | 3 total
Update Strategy: RollingUpdate
Partition: 0
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: registry.k8s.io/nginx-slim:0.8
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts:
/usr/share/nginx/html from www (rw)
Volumes: <none>
Volume Claims:
Name: www
StorageClass:
Labels: <none>
Annotations: <none>
Capacity: 1Gi
Access Modes: [ReadWriteOnce]
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 5m7s statefulset-controller create Claim www-web-0 Pod web-0 in StatefulSet web success
Normal SuccessfulCreate 5m7s statefulset-controller create Pod web-0 in StatefulSet web successful
Normal SuccessfulCreate 3m57s statefulset-controller create Claim www-web-1 Pod web-1 in StatefulSet web success
Normal SuccessfulCreate 3m57s statefulset-controller create Pod web-1 in StatefulSet web successful
Normal SuccessfulCreate 3m27s statefulset-controller create Claim www-web-2 Pod web-2 in StatefulSet web success
Normal SuccessfulCreate 3m27s statefulset-controller create Pod web-2 in StatefulSet web successful
$ kubectl get pvc -n journalctl
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-c75c2705-81ce-44bb-b01f-7b961fbbaa13 1Gi RWO gp3 6m23s
www-web-1 Bound pvc-4f647582-d703-4940-8c55-122c0f02881b 1Gi RWO gp3 5m13s
www-web-2 Bound pvc-a74e957e-315c-402b-ac99-db3e67bd74f6 1Gi RWO gp3 4m43s
앞서 설명한대로 스테이트풀셋(Statefulset)을 사용하여 Pod 생성 시 0부터 시작하는 식별자를 가지게 됩니다. statefulset.yaml 파일에서 replicas를 3으로 설정하여 www-web-0, www-web-1, www-web-2 순차적으로 식별자를 갖는 것을 확인할 수 있습니다.
스테이트풀셋(Statefulset)을 사용하여 Pod 생성 시 독립적인 스토리지 공간을 가지게 됩니다.
"kubectl get pvc -n journalctl" 명령어에서 확인할 수 있는것처럼 3개의 파드에 대해서 개별적인 스토리지 공간이 생성되었습니다.
"kubectl describe sts/web -n journalctl" 명령어의 Events 기록에서 확인할 수 있는 특이한 점이 있습니다.
디플로이먼트(Deployment)와 차이점으로 www-web-0이 생성되고 성공적으로 완료되었을때 다음 Pod가 생성되는 것을 확인할 수 있습니다.
디플로이먼트(Deployment)는 하위 오브젝트로 Replicaset을 통해서 배포를 수행하였다면 스테이트풀셋(Statefulset)은 레플리카셋, 디플로이먼트 오브젝트를 사용하여 관리하는 것이 아닌 Statefulset 오브젝트가 직접 Pod 배포를 수행합니다.
스테이트풀셋 스케일링
디플로이먼트(Deployment)에서 사용한 scale 명령어를 통해서 replicas를 조절할 수 있습니다.
$ kubectl scale sts/web --replicas=5 -n journalctl
statefulset.apps/web scaled
스테이트풀셋 상태 확인하기
$ kubectl get pod -n journalctl
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 18m
web-1 1/1 Running 0 16m
web-2 1/1 Running 0 16m
web-3 1/1 Running 0 75s
web-4 1/1 Running 0 45s
$ kubectl describe sts/web -n journalctl
Name: web
Namespace: journalctl
CreationTimestamp: Sun, 03 Dec 2023 18:54:57 +0900
Selector: app=nginx
Labels: <none>
Annotations: <none>
Replicas: 5 desired | 5 total
Update Strategy: RollingUpdate
Partition: 0
Pods Status: 5 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: registry.k8s.io/nginx-slim:0.8
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts:
/usr/share/nginx/html from www (rw)
Volumes: <none>
Volume Claims:
Name: www
StorageClass:
Labels: <none>
Annotations: <none>
Capacity: 1Gi
Access Modes: [ReadWriteOnce]
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 18m statefulset-controller create Claim www-web-0 Pod web-0 in StatefulSet web success
Normal SuccessfulCreate 18m statefulset-controller create Pod web-0 in StatefulSet web successful
Normal SuccessfulCreate 17m statefulset-controller create Claim www-web-1 Pod web-1 in StatefulSet web success
Normal SuccessfulCreate 17m statefulset-controller create Pod web-1 in StatefulSet web successful
Normal SuccessfulCreate 16m statefulset-controller create Claim www-web-2 Pod web-2 in StatefulSet web success
Normal SuccessfulCreate 16m statefulset-controller create Pod web-2 in StatefulSet web successful
Normal SuccessfulCreate 98s statefulset-controller create Claim www-web-3 Pod web-3 in StatefulSet web success
Normal SuccessfulCreate 98s statefulset-controller create Pod web-3 in StatefulSet web successful
Normal SuccessfulCreate 68s statefulset-controller create Claim www-web-4 Pod web-4 in StatefulSet web success
Normal SuccessfulCreate 68s statefulset-controller create Pod web-4 in StatefulSet web successful
$ kubectl get pvc -n journalctl
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-c75c2705-81ce-44bb-b01f-7b961fbbaa13 1Gi RWO gp3 18m
www-web-1 Bound pvc-4f647582-d703-4940-8c55-122c0f02881b 1Gi RWO gp3 17m
www-web-2 Bound pvc-a74e957e-315c-402b-ac99-db3e67bd74f6 1Gi RWO gp3 17m
www-web-3 Bound pvc-d7c14950-480a-468d-bc06-5582df08feb7 1Gi RWO gp3 2m5s
www-web-4 Bound pvc-53a6b457-b4e5-48b0-8526-d485dc4aedcd 1Gi RWO gp3 95s
replicas를 5로 수정하여 배포했을때도 마찬가지로 스토리지 볼륨 및 고유한 식별자를 가진 것을 확인할 수 있습니다.
이번에는 반대로 replicas 수를 줄여보도록 하겠습니다.
스테이트풀셋 스케일링
$ kubectl scale sts/web --replicas=2 -n journalctl
statefulset.apps/web scaled
스테이트풀셋 상태 확인하기
$ kubectl get pod -n journalctl
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 23m
web-1 1/1 Running 0 22m
$ kubectl describe sts/web -n journalctl
Name: web
Namespace: journalctl
CreationTimestamp: Sun, 03 Dec 2023 18:54:57 +0900
Selector: app=nginx
Labels: <none>
Annotations: <none>
Replicas: 2 desired | 2 total
Update Strategy: RollingUpdate
Partition: 0
Pods Status: 2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: registry.k8s.io/nginx-slim:0.8
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts:
/usr/share/nginx/html from www (rw)
Volumes: <none>
Volume Claims:
Name: www
StorageClass:
Labels: <none>
Annotations: <none>
Capacity: 1Gi
Access Modes: [ReadWriteOnce]
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 23m statefulset-controller create Claim www-web-0 Pod web-0 in StatefulSet web success
Normal SuccessfulCreate 23m statefulset-controller create Pod web-0 in StatefulSet web successful
Normal SuccessfulCreate 22m statefulset-controller create Claim www-web-1 Pod web-1 in StatefulSet web success
Normal SuccessfulCreate 22m statefulset-controller create Pod web-1 in StatefulSet web successful
Normal SuccessfulCreate 21m statefulset-controller create Claim www-web-2 Pod web-2 in StatefulSet web success
Normal SuccessfulCreate 21m statefulset-controller create Pod web-2 in StatefulSet web successful
Normal SuccessfulCreate 6m34s statefulset-controller create Claim www-web-3 Pod web-3 in StatefulSet web success
Normal SuccessfulCreate 6m34s statefulset-controller create Pod web-3 in StatefulSet web successful
Normal SuccessfulCreate 6m4s statefulset-controller create Claim www-web-4 Pod web-4 in StatefulSet web success
Normal SuccessfulCreate 6m4s statefulset-controller create Pod web-4 in StatefulSet web successful
Normal SuccessfulDelete 52s statefulset-controller delete Pod web-4 in StatefulSet web successful
Normal SuccessfulDelete 51s statefulset-controller delete Pod web-3 in StatefulSet web successful
Normal SuccessfulDelete 51s statefulset-controller delete Pod web-2 in StatefulSet web successful
$ kubectl get pvc -n journalctl
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-c75c2705-81ce-44bb-b01f-7b961fbbaa13 1Gi RWO gp3 23m
www-web-1 Bound pvc-4f647582-d703-4940-8c55-122c0f02881b 1Gi RWO gp3 22m
www-web-2 Bound pvc-a74e957e-315c-402b-ac99-db3e67bd74f6 1Gi RWO gp3 21m
www-web-3 Bound pvc-d7c14950-480a-468d-bc06-5582df08feb7 1Gi RWO gp3 6m48s
www-web-4 Bound pvc-53a6b457-b4e5-48b0-8526-d485dc4aedcd 1Gi RWO gp3 6m18s
정상적으로 Pod의 개수가 2개로 조절되었음을 확인할 수 있습니다. Pod의 식별자를 유심히 살펴보면 삭제 시에는 역순으로 삭제되는것을 확인할 수 있습니다.
"kubectl describe sts/web -n journalctl" 명령어의 Events를 살펴보면 "create"시에는 볼륨도 같이 생성되지만, "delete" 시에는 볼륨이 따로 삭제되지 않는 것을 확인할 수 있습니다.
만약 Pod의 개수를 다시 5개로 조절하였을때 기존에 생성되어있던 볼륨 공간을 Pod 식별자에 맞게 자동으로 매칭하여 기존에 사용했던 공간을 이어서 사용할 수 있도록 설정하게 됩니다.

다음 섹션으로는 대표적인 워크로드 리소스인 Daemonset에 대해서 살펴보겠습니다.
'Kubernetes' 카테고리의 다른 글
인증서를 무료로 관리해보자(Cert-manager + Let’s Encrypt) (0) | 2025.01.12 |
---|---|
VKE - Vultr Kubernetes Engine (3) | 2025.01.12 |
kind(Kubernetes in Docker) (4) | 2025.01.12 |
Kubernetes Workload Resources(Daemonset) (7) | 2023.12.03 |
Kubernetes Workload Resources(Deployment, Replicaset) (6) | 2023.11.27 |