
데이터를 잃어버린 밤, 스테이트풀셋이 필요한 순간
어느 날 새벽, 갑작스러운 알람 소리에 잠에서 깨어 모니터를 확인했는데 데이터베이스 클러스터의 모든 데이터가 증발해 버린 상황을 상상해 보세요. 로그를 살펴보니 쿠버네티스의 Deployment로 데이터베이스를 운영하다가 파드가 재시작되면서 기존에 연결되어 있던 스토리지와 끊겨버린 것이 원인이었어요. 상태(State)가 없는 앱이라면 아무런 문제가 없었겠지만, 데이터의 정합성과 고유한 이름이 생명인 데이터베이스에게는 재앙과도 같은 일이죠.
인프라 운영자라면 한 번쯤 겪어봤을 법한 이 끔찍한 시나리오는 단순히 설정을 잘못해서 생기는 문제가 아니에요. 쿠버네티스가 기본적으로 제공하는 파드 관리 방식이 ‘무상태(Stateless)’에 최적화되어 있기 때문이에요. 파드가 죽었다 살아날 때마다 새로운 IP를 받고, 새로운 이름으로 태어나며, 기존 데이터를 찾아가지 못한다면 그것은 진정한 의미의 데이터베이스라고 부를 수 없어요.
그래서 우리에게는 스테이트풀셋 YAML 설정 파일 작성이 필요해요. 스테이트풀셋은 파드에게 고유한 번호를 부여하고, 파드가 재시작되더라도 자신이 사용하던 저장소를 끝까지 추적해서 다시 연결해 주는 아주 영리한 도구예요. 이 글을 끝까지 읽고 나면, 단순히 코드를 복사해서 붙여넣는 수준을 넘어 각 필드가 왜 필요한지, 그리고 실무에서 데이터 손실을 막기 위해 어떤 옵션을 반드시 챙겨야 하는지 완벽하게 이해하게 될 거예요.
이번 가이드에서 함께 살펴볼 내용은 다음과 같아요.
- 스테이트풀셋이 기존 디플로이먼트와 결정적으로 다른 점
- 매니페스트를 구성하는 핵심 필드별 상세 해설
- 바로 실무에 적용 가능한 데이터베이스용 YAML 예제
- 실무에서 가장 자주 마주치는 설정 오류와 해결 방법
작성 전 필수 체크리스트와 핵심 개념 이해하기
스테이트풀셋 매니페스트를 작성하기 전에 먼저 머릿속에 그림을 그려야 해요. 단순히 YAML 문법을 아는 것보다, 쿠버네티스가 어떻게 파드의 ‘정체성’을 유지하는지 이해하는 것이 훨씬 중요하거든요. 스테이트풀셋은 파드가 0번부터 N번까지 순차적으로 생성되며, 각 파드는 자신만의 고유한 이름을 갖게 돼요. 예를 들어 mysql-0, mysql-1 같은 식이죠.
또한, 가장 핵심적인 차이는 스토리지와의 관계에 있어요. 디플로이먼트는 여러 파드가 하나의 저장소를 공유하거나, 파드가 바뀌면 저장소도 새로 만드는 방식이 기본이지만, 스테이트풀셋은 volumeClaimTemplates를 통해 파드 하나당 전용 저장소를 딱 붙여줘요. 파드가 죽었다가 다시 살아나도, 쿠버네티스는 ‘아, 너는 아까 그 0번 파드구나? 네가 쓰던 그 저장소 여기 있어’라고 말하며 연결해 주는 거예요.
본격적인 작성에 들어가기 전, 현재 여러분의 클러스터 환경이 아래 조건을 충족하는지 반드시 확인해 보세요.
스테이트풀셋을 운영하려면 반드시 Headless Service가 필요해요. 일반적인 서비스는 로드밸런싱을 위해 여러 파드를 하나로 묶지만, 스테이트풀셋용 서비스는 개별 파드의 DNS 이름을 직접 알려주는 역할을 하거든요. 이 서비스가 없으면 파드끼리 서로를 찾을 수 없어요.
운영 환경의 요구사항에 따라 어떤 방식을 선택할지 아래 표를 보고 판단해 보세요.
| 비교 항목 | 디플로이먼트(Deployment) | 스테이트풀셋(StatefulSet) |
|---|---|---|
| 주요 목적 | 웹 서버, API 서버 등 무상태 앱 | DB, 메시지 큐 등 상태 유지 앱 |
| 파드 식별성 | 모두 동일한 이름(랜덤 접미사) | 고유하고 예측 가능한 이름 |
| 스토리지 연결 | 여러 파드가 공유하거나 새로 생성 | 파드와 1:1로 영구 매칭 |
| 배포 순서 | 한꺼번에 혹은 무작위로 배포 | 0번부터 순차적으로 배포 |
준비가 되셨나요? 이제 본격적으로 실제 코드를 하나씩 뜯어보며 작성법을 익혀볼게요.
실무를 위한 스테이트풀셋 YAML 단계별 작성법
스테이트풀셋 매니페스트는 생각보다 복잡해 보일 수 있지만, 흐름을 알면 아주 논리적이에요. 크게 세 가지 기둥, 즉 네트워크(Service), 정체성(Metadata), 저장소(Volume)를 세운다고 생각하면 편해요. 이제 단계별로 하나씩 구성해 볼게요.
STEP 1. 파드의 이름을 지켜줄 Headless Service 만들기
가장 먼저 해야 할 일은 파드들에게 개별적인 주소를 부여하는 거예요. 일반적인 서비스와 달리 clusterIP를 None으로 설정한 Headless Service를 만들어야 해요. 이렇게 하면 쿠버네티스는 각 파드에 대해 pod-name.service-name.namespace.svc.cluster.local 형식의 고유 DNS 이름을 부여해요. 이 주소가 있어야 데이터베이스 클러스터 내의 노드들이 서로를 인식하고 데이터를 동기화할 수 있어요.
STEP 2. 스테이트풀셋의 기본 골격 구성하기
이제 본격적인 스테이트풀셋 설정을 시작해요. 여기서 가장 중요한 필드는 serviceName이에요. 아까 만든 Headless Service의 이름을 여기에 정확히 적어줘야 파드와 서비스가 연결되거든요. 만약 이 이름을 틀리면 파드는 생성되지만, 고유한 DNS 주소를 갖지 못하는 유령 상태가 돼요.
podManagementPolicy 옵션을 주목하세요. 기본값인 OrderedReady는 0번 파드가 완전히 준비될 때까지 1번 파드가 뜨지 않게 막아줘요. 만약 빠른 배포가 중요하다면 Parallel로 바꿀 수 있지만, 데이터 정합성이 중요한 DB라면 기본값을 권장해요.STEP 3. 데이터의 영속성을 책임지는 volumeClaimTemplates 설정
스테이트풀셋의 꽃이자 가장 어려운 부분이에요. 디플로이먼트처럼 volumes 섹션에 저장소를 정의하면 안 돼요. 그러면 모든 파드가 똑같은 저장소를 바라보게 되어 데이터가 엉망이 되거든요. 대신 volumeClaimTemplates를 사용해야 해요. 이 섹션에 정의된 내용은 파드가 하나씩 생길 때마다 자동으로 새로운 PersistentVolumeClaim(PVC)을 생성해 줘요.
예를 들어 복제본(replicas)을 3개로 설정했다면, 쿠버네티스는 자동으로 data-mysql-0, data-mysql-1, data-mysql-2라는 이름의 PVC를 만들어 각 파드에 딱 붙여준답니다.
STEP 4. 실무형 통합 예제: MySQL 클러스터 구축 매니페스트
이론만으로는 부족하죠? 바로 복사해서 테스트해 볼 수 있는 완성형 스테이트풀셋 yaml 예제를 준비했어요. 이 코드는 Headless Service와 스테이트풀셋이 결합된 형태예요.
# 1. Headless Service 정의
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
name: mysql
# 2. StatefulSet 정의
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:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "password123"
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 10Gi
위 예제에서 storageClassName은 여러분의 클러스터 환경(AWS EBS, Azure Disk, NFS 등)에 맞게 반드시 수정해야 해요. 이 설정이 틀리면 파드는 Pending 상태에서 영영 깨어나지 못할 거예요.
STEP 5. 배포 후 검증 및 모니터링
매니페스트를 적용한 후에는 반드시 다음 명령어로 상태를 확인해야 해요. kubectl apply -f filename.yaml 명령어로 배포를 마쳤다면, kubectl get pods -l app=mysql를 입력해 보세요. 파드 이름 뒤에 -0, -1, -2가 붙어 있고, 상태가 Running인지 확인하는 것이 첫 번째예요. 그 다음으로는 kubectl get pvc를 통해 각 파드에 대응하는 PVC가 정상적으로 Bound 되었는지 확인하는 과정을 절대 잊지 마세요.
자주 하는 실수와 해결법 + FAQ
스테이트풀셋은 예민한 리소스예요. 설정 하나만 어긋나도 데이터베이스 클러스터 전체가 무너질 수 있거든요. 운영자들이 가장 많이 하는 실수들을 정리했으니, 작업 전에 꼭 한 번 읽어보세요.
자주 하는 실수와 해결법
❌ 실수: Headless Service를 만들지 않고 serviceName만 적음
왜 발생하는가: 스테이트풀셋은 네트워크 정체성을 위해 반드시 이름이 없는(clusterIP: None) 서비스가 필요해요. 일반 서비스를 연결하면 개별 파드 DNS를 생성할 수 없어요.
✅ 해결법: 서비스의 clusterIP: None 설정을 확인하고, 스테이트풀셋의 serviceName과 일치시키세요.
❌ 실수: volumeClaimTemplates 대신 volumes를 사용함
왜 발생하는가: 디플로이먼트 작성 습관 때문이에요. volumes를 쓰면 모든 파드가 하나의 PVC를 공유하려다가 권한 문제로 에러가 나거나 데이터가 꼬여요.
✅ 해결법: 파드마다 독립된 스토리지를 가져야 한다면 반드시 volumeClaimTemplates 섹션을 사용하세요.
❌ 실수: storageClassName 오기입
왜 발생하는가: 클라우드 환경마다 제공하는 스토리지 이름이 다른데, 이를 무시하고 기본값만 사용하기 때문이에요.
✅ 해결법: kubectl get sc 명령어로 현재 클러스터에서 사용 가능한 스토리지 클래스 이름을 먼저 확인하세요.
❌ 실수: 파드 삭제 시 PVC를 같이 지워버림
왜 발생하는가: 스테이트풀셋은 파드를 지워도 데이터(PVC)를 보호하기 위해 PVC를 남겨둬요. 하지만 운영자가 실수로 PVC까지 삭제하면 데이터는 영구히 사라져요.
✅ 해결법: 삭제 명령어를 쓸 때 -A 같은 옵션을 남발하지 말고, 반드시 PVC 상태를 먼저 점검하세요.
❌ 실수: 업데이트 전략(updateStrategy) 설정 미숙
왜 발생하는가: 데이터베이스는 순차적으로 하나씩 업데이트해야 하는데, 갑자기 모든 파드가 재시작되면 클러스터가 깨져요.
✅ 해결법: RollingUpdate 전략을 사용하고, 파드 간의 순서가 보장되는지 확인하세요.
자주 묻는 질문
Q. 스테이트풀셋의 파드 이름은 왜 항상 숫자로 끝나나요?
파드에게 고유한 식별자를 부여하기 위해서예요. 0번부터 시작하는 숫자는 파드가 재시작되더라도 변하지 않는 고정된 이름이 되어, 네트워크 통신에서 ‘누가 누구인지’ 명확하게 알려주는 역할을 해요.
Q. 파드가 삭제되었는데 왜 PVC는 그대로 남아있나요?
이것은 쿠버네티스의 의도된 설계예요. 데이터는 매우 소중하니까요! 파드는 사라져도 데이터는 보존되어야 하므로, 사용자가 직접 PVC를 삭제하기 전까지는 데이터가 안전하게 유지되도록 설계되어 있어요.
Q. 디플로이먼트와 스테이트풀셋 중 무엇을 써야 할지 모르겠어요.
앱이 데이터를 저장하는지 확인해 보세요. 이미지나 설정 파일을 읽기만 하는 웹 서버라면 Deployment가 훨씬 효율적이고 관리하기 편해요. 하지만 MySQL, Redis, Kafka처럼 데이터를 직접 관리하는 앱이라면 고민할 것도 없이 StatefulSet을 선택해야 해요.
Q. 스테이트풀셋에서 스토리지 용량을 늘릴 수 있나요?
네, 가능해요. 하지만 단순히 YAML을 수정한다고 바로 늘어나지는 않아요. 사용 중인 StorageClass가 allowVolumeExpansion: true를 지원하는지 확인한 뒤, PVC의 용량을 수정하고 파드를 재시작해야 하는 경우가 많아요.
Q. 파드 생성 순서가 너무 느려요.
스테이트풀셋은 안정성을 위해 0번 파드가 ‘Ready’ 상태가 될 때까지 기다린 후 1번을 만들기 때문이에요. 만약 순서가 상관없는 작업이라면 podManagementPolicy: Parallel 옵션을 고려해 보세요.
완벽한 스테이트풀셋 운영을 위한 마무리
지금까지 스테이트풀셋 YAML 설정 파일을 작성하는 방법부터 실무 예제, 그리고 흔히 저지르는 실수까지 자세히 살펴보았어요. 스테이트풀셋은 처음에는 복잡해 보이지만, 데이터의 안정성을 보장해 주는 가장 든든한 인프라의 동반자예요. 오늘 배운 내용을 바탕으로 여러분의 클러스터에 더 견고한 데이터베이스 환경을 구축해 보세요.
- 데이터베이스 같은 상태 유지 앱에는 반드시 스테이트풀셋을 사용하세요.
- 파드의 네트워크 식별을 위해 Headless Service를 먼저 정의해야 해요.
- 데이터 영속성을 위해서는 volumeClaimTemplates를 활용하세요.
- 매니페스트 적용 후 반드시 PVC의 Bound 상태를 확인하세요.
- 파드 이름의 규칙성(0, 1, 2…)이 네트워크 통신의 핵심이에요.
설정이 막히거나 예상치 못한 에러 메시지가 나온다면 당황하지 마세요. 대부분의 문제는 serviceName 불일치나 storageClassName 오류에서 시작된답니다. 실습 환경에서 직접 다양한 옵션을 바꿔가며 테스트해 보는 것이 가장 빠른 학습 방법이에요.
🚀 다음 단계로 나아가기
- 오늘 할 일: 현재 운영 중인 DB 파드가 디플로이먼트로 되어 있다면, 스테이트풀셋으로 전환하기 위한 계획 세우기
- 이번 주 할 일: 테스트 클러스터에 오늘 배운 YAML 예제를 적용하고, 파드를 강제로 삭제하며 데이터가 유지되는지 확인하기
- 실행 직전 할 일: 사용 중인 클라우드 환경의 스토리지 클래스(StorageClass) 이름 최종 확인하기
실습 중에 궁금한 점이 생기거나, 특정 데이터베이스를 위한 더 복잡한 설정이 필요하다면 언제든 댓글로 질문을 남겨 주세요. 여러분의 인프라 운영을 응원해요!
함께 읽으면 좋은 글: 쿠버네티스 스테이트풀셋 기본 개념 정리, 쿠버네티스 클러스터 구축 입문 가이드