Kubernetes에서의 StatefulSets 및 지속 가능한 저장소
순차적 확장 및 지속 가능한 데이터와 함께 상태 있는 앱 배포
Kubernetes StatefulSets은 안정적인 정체성, 지속 가능한 저장소, 순서 있는 배포 패턴이 필요한 상태가 있는 애플리케이션을 관리하는 데 이상적인 솔루션입니다. 데이터베이스, 분산 시스템, 캐싱 레이어와 같은 필수적인 작업에 사용됩니다.
Kubernetes에 처음 접하거나 클러스터를 설정 중이라면, 개발 환경에서는 k3s나 MicroK8s와 같은 Kubernetes 배포판을 고려하거나, 프로덕션 등급의 클러스터를 설치하려면 Kubespray로 Kubernetes 설치을 참고하세요.
이 아름다운 이미지는 AI 모델 Flux 1 dev에 의해 생성되었습니다.
StatefulSets란 무엇인가요?
StatefulSets는 상태가 있는 애플리케이션을 관리하기 위해 설계된 Kubernetes 워크로드 API 객체입니다. 모든 Pod를 동등하게 처리하는 Deployments와 달리, StatefulSets는 각 Pod에 고유한 정체성을 유지하며 순서와 고유성에 대한 보장이 있습니다.
주요 기능:
- 안정적인 네트워크 식별자: 각 Pod은 재시작을 거치더라도 예측 가능한 호스트 이름을 유지합니다.
- 지속 가능한 저장소: Pod이 재스케줄링 되더라도 따라가는 PersistentVolumeClaims가 있습니다.
- 순서 있는 배포: Pod은 순차적으로 생성되며 (0, 1, 2…) 역순으로 종료됩니다.
- 순서 있는 업데이트: 롤링 업데이트는 순서대로 진행되어 애플리케이션의 안정성을 보장합니다.
PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis, etcd와 같은 애플리케이션에서 Pod의 정체성과 데이터 지속성이 중요한 경우 StatefulSets는 필수적입니다.
Kubernetes에서의 지속 가능한 저장소 이해
Kubernetes는 Pod의 생명주기에서 저장소 관리를 분리하는 복잡한 저장소 추상화 계층을 제공합니다:
저장소 구성 요소
PersistentVolume (PV): 관리자에 의해 클러스터 내에서 프로비저닝되거나 StorageClass를 통해 동적으로 생성된 저장소의 일부입니다. PV는 Pod과 독립적으로 존재합니다.
PersistentVolumeClaim (PVC): Pod에 의해 요청된 저장소입니다. PVC는 크기, 접근 모드, 저장소 클래스와 같은 요구사항에 맞는 사용 가능한 PV에 바인딩됩니다.
StorageClass: 다양한 프로비저너(AWS EBS, GCE PD, Azure Disk, NFS 등)와 복제, 성능 계층, 백업 정책과 같은 매개변수를 정의하는 저장소의 “종류"를 정의합니다.
접근 모드
- ReadWriteOnce (RWO): 단일 노드에서 읽기/쓰기로 마운트됩니다.
- ReadOnlyMany (ROX): 여러 노드에서 읽기 전용으로 마운트됩니다.
- ReadWriteMany (RWX): 여러 노드에서 읽기/쓰기로 마운트됩니다 (특수한 저장소 백엔드가 필요합니다).
StatefulSet 저장소 아키텍처
StatefulSets는 volumeClaimTemplates를 사용하여 각 Pod 복제본에 대해 자동으로 PersistentVolumeClaims를 생성합니다. 이는 Deployments와 근본적으로 다릅니다:
volumeClaimTemplates 작동 방식
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-service
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "fast-ssd"
resources:
requests:
storage: 10Gi
이 StatefulSet을 생성할 때:
- Kubernetes는 Pod
mysql-0과 PVCdata-mysql-0을 생성합니다. - 그 다음 Pod
mysql-1과 PVCdata-mysql-1을 생성합니다. - 마지막으로 Pod
mysql-2와 PVCdata-mysql-2를 생성합니다.
각 Pod은 자체적인 10GB 지속 가능한 볼륨을 받습니다. mysql-1이 삭제되거나 재스케줄링 되면 Kubernetes는 동일한 data-mysql-1 PVC를 다시 연결하여 모든 데이터를 보존합니다.
StatefulSets를 위한 헤드리스 서비스 생성
StatefulSets는 안정적인 네트워크 정체성을 제공하기 위해 헤드리스 서비스가 필요합니다:
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
clusterIP: None # 이는 헤드리스 서비스를 만듭니다.
selector:
app: mysql
ports:
- port: 3306
name: mysql
이것은 각 Pod에 대한 DNS 엔트리를 생성합니다:
mysql-0.mysql-service.default.svc.cluster.localmysql-1.mysql-service.default.svc.cluster.localmysql-2.mysql-service.default.svc.cluster.local
애플리케이션은 이러한 안정적인 DNS 이름을 사용하여 특정 Pod 인스턴스에 직접 연결할 수 있습니다.
StatefulSet 배포 패턴
복잡한 Kubernetes 배포를 관리하는 팀에게 Helm 차트는 템플릿, 버전 관리 및 종속 관리를 통해 StatefulSets를 패키징하고 배포하는 강력한 방법을 제공합니다. Helm은 다양한 환경에서 StatefulSet 구성 관리를 간소화합니다.
순서 있는 확장
3개에서 5개 복제본으로 확장할 때:
kubectl scale statefulset mysql --replicas=5
Kubernetes는 순서대로 Pod을 생성합니다: mysql-3 → Ready 상태가 될 때까지 대기 → mysql-4
5개에서 3개로 축소할 때:
kubectl scale statefulset mysql --replicas=3
Kubernetes는 역순으로 종료합니다: mysql-4 → 종료가 완료될 때까지 대기 → mysql-3
롤링 업데이트
StatefulSets는 두 가지 업데이트 전략을 지원합니다:
OnDelete: 수동 업데이트 - Pod이 삭제될 때만 업데이트됩니다. RollingUpdate: 역순 정렬에 따라 자동으로 순차적으로 업데이트됩니다.
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 2 이상의 ordinal을 가진 Pod만 업데이트
partition 매개변수는 캐니리 배포를 가능하게 합니다 - 먼저 고번호 Pod을 업데이트하고 모든 복제본으로 확장하기 전에 테스트할 수 있습니다.
저장소 최고 실천 방법
동적 프로비저닝
항상 StorageClasses를 사용하여 동적 볼륨 프로비저닝을 수행하세요:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iops: "3000"
throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain
allowVolumeExpansion: PVC를 재생성하지 않고 크기를 조정할 수 있도록 합니다.
reclaimPolicy: Retain은 PVC 삭제 후에도 PV 데이터를 보존하고, Delete은 자동으로 삭제합니다.
PVC 보존 정책
Kubernetes 1.23+는 persistentVolumeClaimRetentionPolicy를 지원합니다:
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain # StatefulSet이 삭제될 때 PVC를 보존합니다.
whenScaled: Delete # 축소될 때 PVC를 삭제합니다.
옵션:
- Retain: PVC를 보존합니다 (기본값, 가장 안전한 방법)
- Delete: PVC를 자동으로 삭제합니다 (개발 환경에 유용)
백업 전략
볼륨 스냅샷: VolumeSnapshot 리소스를 사용하여 특정 시간의 백업을 생성합니다.
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysql-snapshot-20251113
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: data-mysql-0
애플리케이션 수준 백업: mysqldump, pg_dump, Velero와 같은 도구를 사용하여 데이터베이스 특정 백업을 수행합니다.
분산 복제: MySQL 복제, PostgreSQL 스트리밍 복제와 같은 애플리케이션 수준 복제를 첫 번째 방어선으로 구성합니다.
실제 사용 사례
데이터베이스 클러스터 (PostgreSQL)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard"
resources:
requests:
storage: 20Gi
분산 캐시 (Redis)
Redis 클러스터를 위해 StatefulSet과 주의 깊은 설정이 필요합니다:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: redis-service
replicas: 6
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
command:
- redis-server
- "--appendonly"
- "yes"
- "--appendfsync"
- "everysec"
ports:
- containerPort: 6379
name: redis
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
메시지 큐 (Kafka)
Kafka는 로그에 대한 지속 가능한 저장소와 브로커 조정을 위한 안정적인 네트워크 정체성이 필요합니다:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
spec:
serviceName: kafka-service
replicas: 3
selector:
matchLabels:
app: kafka
template:
metadata:
labels:
app: kafka
spec:
containers:
- name: kafka
image: confluentinc/cp-kafka:7.5.0
ports:
- containerPort: 9092
name: kafka
env:
- name: KAFKA_BROKER_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
volumeMounts:
- name: data
mountPath: /var/lib/kafka/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
모니터링 및 문제 해결
이 섹션에서 사용된 Kubernetes 명령어에 대한 종합적인 참조는 Kubernetes 체크리스트를 참조하세요.
StatefulSet 상태 확인
# StatefulSet 세부 정보 보기
kubectl get statefulset mysql
kubectl describe statefulset mysql
# Pod 생성 순서 및 상태 확인
kubectl get pods -l app=mysql -w
# PVC 상태 보기
kubectl get pvc
kubectl describe pvc data-mysql-0
일반적인 문제
Pod이 대기 상태: PVC 상태와 저장소 가용성을 확인하세요
kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'
저장소가 가득 차 있음: StorageClass가 허용하는 경우 PVC를 확장하세요
kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
Pod이 종료되지 않음: 애플리케이션 수준 잠금이나 볼륨 언마운트 문제를 확인하세요
kubectl delete pod mysql-0 --grace-period=0 --force
모니터링할 메트릭
- 저장소 사용량: PVC 용량과 사용률 퍼센트를 모니터링하세요
- I/O 성능: IOPS, 전송량, 지연 시간을 추적하세요
- Pod 재시작: 빈번한 재시작은 저장소 문제를 나타낼 수 있습니다
- PVC 바인딩 시간: 느린 바인딩은 프로비저닝 문제를 나타냅니다
이전 전략
StatefulSets로 마이그레이션할 때 Kubernetes 클러스터가 올바르게 구성되어 있는지 확인하세요. 홈랩이나 작은 클러스터 설정의 경우, 우리의 Kubernetes 배포판의 종합 비교를 참조하여 작업 부하 요구사항에 적합한 플랫폼을 선택하세요.
Deployment에서 StatefulSet으로
- volumeClaimTemplates와 함께 StatefulSet 생성
- Deployment를 부드럽게 축소
- 백업에서 데이터를 StatefulSet Pod으로 복원
- DNS/Service 참조 업데이트
- 기존 Deployment 및 PVC 삭제
마이그레이션 전 백업
# 기존 PVC 스냅샷
kubectl get pvc -o yaml > pvc-backup.yaml
# 볼륨 스냅샷 생성
kubectl apply -f volume-snapshot.yaml
보안 고려사항
저장소 암호화
StorageClass 매개변수를 사용하여 저장소 암호화를 활성화하세요:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
encrypted: "true"
kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234
접근 제어
RBAC를 사용하여 StatefulSets 및 PVC를 생성/수정할 수 있는 사람을 제한하세요:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: statefulset-manager
rules:
- apiGroups: ["apps"]
resources: ["statefulsets"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "create", "delete"]
네트워크 정책
Pod 간 통신을 제한하세요:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mysql-network-policy
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 3306
성능 최적화
저장소 성능 계층
작업 부하에 따라 적절한 StorageClasses를 선택하세요:
- 높은 IOPS: 무거운 무작위 읽기/쓰기 작업 (gp3, io2)
- 높은 전송량: 로그 집계, 분석 (st1, sc1)
- 균형: 일반적인 목적 애플리케이션 (gp3)
Pod 분산
가용성 존에 걸쳐 StatefulSet Pod을 분산시키기 위해 Pod 반대 친화성을 사용하세요:
spec:
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: mysql
topologyKey: topology.kubernetes.io/zone
리소스 요청 및 제한
일관된 성능을 위해 적절한 리소스를 설정하세요:
resources:
requests:
cpu: "2"
memory: "4Gi"
ephemeral-storage: "10Gi"
limits:
cpu: "4"
memory: "8Gi"
ephemeral-storage: "20Gi"
고급 패턴
초기 컨테이너를 사용한 상태가 있는 애플리케이션
데이터베이스 초기화를 위해 초기 컨테이너를 사용하세요:
spec:
template:
spec:
initContainers:
- name: init-mysql
image: mysql:8.0
command:
- bash
- "-c"
- |
if [[ ! -d /var/lib/mysql/mysql ]]; then
mysqld --initialize-insecure --datadir=/var/lib/mysql
fi
volumeMounts:
- name: data
mountPath: /var/lib/mysql
사이드카를 위한 다중 컨테이너 Pod
백업 사이드카 또는 모니터링 에이전트를 추가하세요:
spec:
template:
spec:
containers:
- name: mysql
image: mysql:8.0
# ... mysql 설정 ...
- name: backup-sidecar
image: mysql-backup:latest
volumeMounts:
- name: data
mountPath: /var/lib/mysql
readOnly: true
동적 설정을 위한 ConfigMaps
StatefulSet 정의에서 설정을 분리하세요:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
my.cnf: |
[mysqld]
max_connections=200
innodb_buffer_pool_size=2G
---
spec:
template:
spec:
containers:
- name: mysql
volumeMounts:
- name: config
mountPath: /etc/mysql/conf.d
volumes:
- name: config
configMap:
name: mysql-config