StatefulSet
StatefulSet은 Stateful한 Pod를 생성해야 하는 경우 사용합니다.
Deployment나 ReplicaSet과는 다르게 복제된 Pod가 완벽히 동일하지 않고 순서에 따라 고유의 역할을 가집니ㅏㄷ.
동일한 이미지로 Pod를 생성하지만 실행 시, 각기 다른 역할을 가지며 사로의 역할을 교체하지 못할 때 StatefulSet을 사용합니다.
프로세서간 서로 치환될 수 없는 클러스터를 구축할 때 많이 사용합니다.
예로 동일한 프로세스가 실행 순서에 따라 마스터와 워커가 결정되는 경우를 생각할 수 있습니다.
StatefulSet은 상태 정보를 저장하는 애플리케이션에서 사용하는 리소스입니다.
Deployment와 유사하게 여러 Pod의 배포와 replica 개수를 관리하지만,
statefulSet에선 각 Pod의 순서와 고유성을 보장합니다.
이는 1개의 Pod가 삭제되어도 다른 Pod로 대체가 불가능함을 의미합니다.
Pod마다 고유 식별자가 존재하며 고유한 데이터를 보관합니다.
따라서 StatefulSet은 고유의 데이터를 보관해야 하는 애플리케이션에서 많이 사용합니다.
- 고유의 Pod 식별자가 필요한 경우
- 명시적으로 Pod마다 저장소가 지정되어야 하는 경우
- Pod끼리 순서에 민감한 애플리케이션
- 애플리케이션이 순서대로 업데이트되어야 하는 경우
Deployment리소스에도 DB를 연결하거나 외부 스토리지를 사용하는 경우 상태를 저장할 수 있습니다.
Pod 내부적으로 상태를 유지해야 하는 경우 StatefulSet을 사용합니다.
StatefulSet의 Deployment와 가장 큰 차이점은 Pod끼리 명시적인 순서와 식별자를 부여받는다는 점입니다.
직접 생성해봅시다.
# mysts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysts
spec:
serviceName: mysts
replicas: 3
selector:
matchLabels:
run: nginx
template:
metadata:
labels:
run: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: vol
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: vol
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: mysts
spec:
clusterIP: None
ports:
- port: 8080
protocol: TCP
targetPort: 80
selector:
run: nginx
- serviceName: 연결할 Service 이름을 지정합니다.
- selector.matchLabels: StatefulSet도 라벨링 시스템을 이용해 Pod를 선택합니다.
- template: 복제할 Pod를 정의합니다.
- volumeCalimTemplates: 동적으로 볼륨을 생성하는 property입니다. 쿠버네티스에서 제공하는 볼륨 타입 중 하나입니다.

StatefulSet을 생성 후 Pod 리소스를 조회하면
Deployment 리소스와는 다르게 mysts-0, mysts-1 .. 와 같이 명시적으로 Pod 순서가 적힌 식별자가 Pod 이름으로 생성됩니다.
또한 생선 순서가 식별자 숮서에 따라 순차 생성됩니다.
각 Pod의 호스트 이름을 출력하여 호스트 이름으로도 구분이 되는지 확인해보겠습니다.

호스트명도 각 Pod의 이름과 마찬가지로 순서가 적힌 식별자로 구분됩니다.
이번엔 nginx의 html 디렉터에 각각의 호스트이름을 저장하고 호출해보겠습니다.

각기 다른 결과가 나오는 것으로 알 수 있듯이 StatefulSet의 Pod들은 동일한 저장소를 바라보지않고 각자의 볼륨을 사용하는 것을 알 수 있습니다.
나중에 보겠지만 볼륨 또한 명시적으로 순서가 적힌 식별자로 각각 생성된 것을 확인할 수 있습니다.

마지막으로 StatefulSet의 replica 개수를 줄여보겠습니다.

삭제는 생성과 반대로 역순으로 삭제되는 것을 확인할 수 있습니다.
Stateful리소스는 각 Pod끼리 완전히 다른 컨테이너를 사용하는 것이 아니지만 완벽히 일치하는 동작을 수행하는 것이 아니라, Pod의 순서에 따라 다른 역할을 맡거나 Pod 생성 순서를 보장받아야 할 때 사용하는 리소스입니다.
대표적인 예로 클러스터 시스템을 만들 때 사용할 수 있ㅅ브니다.
클러스터 시스템 구성 시, 리더 선출이나 primary vs relica를 지정하기 위해서 명시적인 순서를 지정해야 하는 경우 StatefulSet을 활용할 수 있습니다.
StatefulSet 리소스의 좋은 사용 예시로 MySQL DB 클러스터를 구축하는 예제가 쿠버네티스 공식 사이트에 존재합니다.
https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application
DaemonSet
DeameonSet 리소스는 모든 노드에 동일한 Pod를 실행시키고자 할 때 사용하는 리소스입니다.
리소스 모니터링, 로그 수집기 등과 같이 모든 노드에 동일한 Pod가 위치하면서 노드에 관한 정보를 추출할 때 많이 사용합니다.
모든 노드의 로그 정보를 추출하는 fluentd DaemonSet 을 살펴봅시다.
# fluentd.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
spec:
selector:
matchLabels:
name: fluentd
template:
metadata:
labels:
name: fluentd
spec:
containers:
- name: fluentd
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
volumeMounts:
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
ReplicaSet에서 살펴본 것과 같이 selector와 template이 있습니다.
template에 원하는 Pod스펙을 정의하면 해당 Pod가 모든 노드에 일괄적으로 실행됩니다.
- selector.matchLabels: 라벨링 시스템을 이용하여 노드에서 노드 실행될 Pod를 선택합니다.
- template: 모든 노드에서 생성될 Pod를 정의합니다.
kubectl apply -f fluentd.yaml
# daemonset.apps/fluentd created
kubectl get daemonset # 축약 시, ds
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE ..
# fluentd 2 2 2 2 2 ..
kubectl get pod -owide
# NAME READY STATUS RESTARTS AGE IP NODE ..
# fluentd-q9vcc 1/1 Running 0 92s 10.42.0.8 master ..
# fluentd-f3gt3 1/1 Running 0 92s 10.42.0.10 worker ..
kubectl logs fluentd-q9vcc
# 2020-07-05 04:12:05 +0000 [info]: parsing config file is succeeded ..
# 2020-07-05 04:12:05 +0000 [info]: using configuration file: <ROOT>
# <match fluent.**>
# @type null
# </match>
# </ROOT>
# 2020-07-05 04:12:05 +0000 [info]: starting fluentd-1.4.2 pid=1 ..
# 2020-07-05 04:12:05 +0000 [info]: spawn command to main: cmdline=..
# ...
Pod가 서버마다 하나씩 생성됩니다.
DaemonSet 리소스는 모든 노드에 항상 동일한 작업을 수행해야 하는 경우 사용합니다.
장점으론 클러스터에 노드를 새롭게 추가 시 따로 작업을 수행하지 않더라도 신규로 편입된 노드에 자동으로 필요한 Pod들이 생성됩니다.
로그 수집, 리소스 모니터링 등 모든 노드가 동일하게 수행하는 작업에 대해서는 DaemonSet을 사용하는 것이 편리합니다.
Job & CronJob
Job
Job리소스는 일반 Pod처럼 항상 실행되고 있는 서비스 프로세스가 아닌
한번 실행하고 완료가 되는 일괄처리 프로세스용으로 만들어졌습니다.
대표적으로 머신러닝 학습을 생각할 수 있습니다.
간단한 기계학습 모델을 Job으로 실행해보겠습니다.
- train.py: 간단한 기계학습 스크립트
- Dockerfile: 기계학습 스크립트를 도커 이미지로 변환
- job.yaml: Job 실행을 위한 리소스 정의서
train.py 상단 parameters부분에 파라미터를 받는걸 보면 Job리소스에서 매개변수로 넘겨줄 예정입니다.
# train.py
import os, sys, json
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import RMSprop
#####################
# parameters
#####################
epochs = int(sys.argv[1])
activate = sys.argv[2]
dropout = float(sys.argv[3])
print(sys.argv)
#####################
batch_size, num_classes, hidden = (128, 10, 512)
loss_func = "categorical_crossentropy"
opt = RMSprop()
# preprocess
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
# convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
# build model
model = Sequential()
model.add(Dense(hidden, activation='relu', input_shape=(784,)))
model.add(Dropout(dropout))
model.add(Dense(num_classes, activation=activate))
model.summary()
model.compile(loss=loss_func, optimizer=opt, metrics=['accuracy'])
# train
history = model.fit(x_train, y_train, batch_size=batch_size,
epochs=epochs, validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
Job리소스에서 사용할 이미지생성
# Dockerfile
FROM python:3.6.8-stretch
RUN pip install tensorflow==1.5 keras==2.0.8 h5py==2.7.1
COPY train.py .
ENTRYPOINT ["python", "train.py"]
이미지를 빌드하고 도커 원격 저장소에 이미지를 업로드 합니다.
# 도커 이미지 빌드
docker build . -t $USERNAME/train
# Sending build context to Docker daemon 3.249MB
# Step 1/4 : FROM python:3.6.8-stretch
# 3.6.8-stretch: Pulling from library/python
# 6f2f362378c5: Pull complete
# ...
# 도커 이미지 업로드를 위해 도커허브에 로그인합니다.
docker login
# Login with your Docker ID to push and pull images from Docker Hub. ..
# Username: $USERNAME
# Password:
# WARNING! Your password will be stored unencrypted in /home/..
# Configure a credential helper to remove this warning. See
# https://docs.docker.com/engine/reference/commandline/..
#
# Login Succeeded
# 도커 이미지 업로드
docker push $USERNAME/train
# The push refers to repository [docker.io/$USERNAME/train]
마지막으로 Job 리소스를 작성합니다.
# job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
spec:
containers:
- name: ml
image: $USERNAME/train
args: ['3', 'softmax', '0.5']
restartPolicy: Never
backoffLimit: 2
- template: Pod 리소스의 spec과 동일합니다. Job도 결국 내부적으로 Pod를 통해 실행됩니다.
- backoffLimit: 재시도 횟수를 지정합니다.
# Job 생성
kubectl apply -f job.yaml
# job.batch/myjob created
# Job 리소스 확인
kubectl get job
# NAME COMPLETIONS DURATION AGE
# myjob 0/1 9s 9s
# Pod 리소스 확인
kubectl get pod
# NAME READY STATUS RESTARTS AGE
# myjob-l5thh 1/1 Running 0 9s
# 로그 확인
kubectl logs -f myjob-l5thh
# ...
# Layer (type) Output Shape Param #
# =================================================================
# dense_1 (Dense) (None, 512) 401920
# ...
# Pod 완료 확인
kubectl get pod
# NAME READY STATUS RESTARTS AGE
# myjob-l5thh 0/1 Completed 0 3m27s
# Job 완료 확인
kubectl get job
# NAME COMPLETIONS DURATION AGE
# myjob 1/1 51s 4m
Pod를 이용하여 로그를 확인하고 완료 여부를 확인할 수 있습니다.
다만 Pod가 계속 Running상태가 아니라 완료 이후에 Completed로 남습니다.
의도적으로 장애 상황을 만들어 재시도 작업을 수행하는지 봅시다.
지금은 backoffLimit을 2로 설정하여 총 3번(첫 시도 + 재시도 2) 실행됩니다.
# job-bug.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob-bug
spec:
template:
spec:
containers:
- name: ml
image: $USERNAME/train
# int 타입이 아닌 string 타입 전달
args: ['bug-string', 'softmax', '0.5']
restartPolicy: Never
backoffLimit: 2
kubectl apply -f job-bug.yaml
# 2번 재시도 후(총 3번 실행) failed
kubectl get pod
# NAME READY STATUS RESTARTS AGE
# myjob-bug-8f867 0/1 Error 0 6s
# myjob-bug-s23xs 0/1 Error 0 4s
# myjob-bug-jz2ss 0/1 ContainerCreating 0 1s
kubectl get job myjob-bug -oyaml | grep type
# type: Failed
# 에러 원인 확인
kubectl logs -f myjob-bug-jz2ss
# /usr/local/lib/python3.6/site-packages/tensorflow/python/framework/
# dtypes.py:502: FutureWarning: Passing (type, 1) or '1type'
# as a synonym of type is deprecated; in a future version of numpy,
# it will be understood as (type, (1,)) / '(1,)type'.
# np_resource = np.dtype([("resource", np.ubyte, 1)])
# Traceback (most recent call last):
# File "train.py", line 11, in <module>
# epochs = int(sys.argv[1])
# ValueError: invalid literal for int() with base 10: 'bug-string'
CronJob
CronJob은 주기적으로 Job을 실행할 수 있도록 확장된 리소스입니다.
crontab과 마찬가지로 cron 형식을 이용하여 주기를 설정합니다.
# cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
- schedule: Job 리소스를 실행할 주기를 설정합니다.
- jobTemplate: Job 리소스에서 사용하는 스펙을 동일하게 사용합니다.
이를 통해 생성하면 등록된 주기(1분)마다 Job이 실행되는 것을 확인할 수 있습니다.
kubectl apply -f cronjob.yaml
# cronjob.batch/hello created
kubectl get cronjob
# NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
# hello */1 * * * * False 0 <none> 4s
kubectl get job
# NAME COMPLETIONS DURATION AGE
# hello-1584873060 0/1 3s 3s
# hello-1584873060 0/1 3s 62s
CronJob은 동일한 Job을 특정 주기마다 반복해서 실행하고자 할 때 활용할 수 있는 리소스입니다.
마무리
쿠버네티스는 모든 것을 리소스로 표현합니다.
마치 빌딩 블럭처럼 작은 단위의 리소스를 조합하여 점점 더 큰 리소스로 만듭니다.
작은 컨테이너를 모아 Pod를 만들고 이를 Deployment와 Service로 엮어 작은 컴포넌트로 만들고,
이것을 조합해 하나의 거대한 어플리케이션을 만들 수 있습니다.
다음엔 쿠버네티스 패키지 매니저인 helm에 대해 알아봅시다.
'개발 > DevOps' 카테고리의 다른 글
| [kubernetes] Ingress 리소스 - Basic Auth, cert-manager, Issuer (0) | 2025.11.04 |
|---|---|
| [kubernetes] helm 패키지 매니저 (0) | 2025.10.06 |
| [Kubernetes] 쿠버네티스 컨트롤러 1 - ReplicaSet, Deployment (0) | 2025.10.02 |
| [Kubernetes] 쿠버네티스 네트워킹(Service) - ClusterIP, NodePort, LoadBalancer, ExternalName (0) | 2025.09.27 |