
데이터 저장의 한계, 왜 스테이트풀셋인가
어느 날 갑자기 운영 중인 데이터베이스 파드가 재시작되었는데, 그동안 쌓아온 소중한 데이터가 통째로 날아가 버린다면 어떤 기분이 들까요? 아마 눈앞이 캄캄해지고 식은땀이 흐를 거예요. 이런 상황은 쿠버네티스에서 디플로이먼트(Deployment)를 사용하여 데이터베이스를 관리할 때 흔히 마주하는 공포스러운 순간이에요.
디플로이먼트는 원래 상태가 없는 애플리케이션, 즉 웹 서버나 API 서버처럼 언제든 죽었다가 살아나도 상관없는 서비스에 최적화되어 있어요. 파드가 새로 생성될 때마다 새로운 IP를 부여받고, 기존의 데이터를 그대로 이어받지 못하는 특성 때문이죠. 하지만 우리가 다루는 데이터베이스나 메시지 큐는 다릅니다. 각 파드는 자신만의 고유한 이름이 있어야 하고, 재시작되더라도 반드시 이전에 사용하던 저장 공간을 다시 찾아가야만 해요.
이런 요구사항을 해결하기 위해 등장한 것이 바로 스테이트풀셋(StatefulSet)이에요. 단순히 파드를 띄우는 것을 넘어, 파드에 고유한 식별자를 부여하고 저장소와의 연결을 영구적으로 유지해 주는 아주 똑똑한 도구죠. 실무에서 많은 개발자가 디플로이먼트로 데이터베이스를 돌리다가 겪는 ‘데이터 유실’이라는 뼈아픈 실수를 방지하려면, 스테이트풀셋의 원리를 제대로 이해하는 것이 무엇보다 중요해요.
이번 글에서는 실제 서비스 환경에서 데이터베이스 클러스터를 구축하며 겪었던 생생한 스테이트풀셋 운영 사례를 바탕으로 핵심 내용을 전달해 드릴게요. 다음 내용들을 중점적으로 다룰 예정이에요.
- 디플로이먼트와 스테이트풀셋의 결정적인 차이점
- 안정적인 데이터 유지를 위한 핵심 설정값
- 실제 데이터베이스 클러스터 구축 단계
- 운영 중 만난 문제점과 실질적인 해결 방법
사전 준비: 스테이트풀셋의 핵심 개념 이해하기
스테이트풀셋을 제대로 다루기 위해서는 단순히 명령어를 외우는 것보다, 이 객체가 내부적으로 어떤 약속을 지키며 동작하는지 아는 것이 우선이에요. 스테이트풀셋은 크게 세 가지 핵심 요소를 보장하며 동작합니다. 첫째는 안정적인 네트워크 식별자, 둘째는 고유한 파드 이름, 셋째는 지속 가능한 저장소예요.
가장 먼저 이해해야 할 점은 파드의 정체성이에요. 디플로이먼트의 파드는 `web-abcd-1234`처럼 무작위 해시값이 붙지만, 스테이트풀셋의 파드는 `mysql-0`, `mysql-1`처럼 순차적인 번호가 붙어요. 이 번호는 파드가 죽었다 살아나도 변하지 않아요. 덕분에 다른 파드들이 특정 파드를 지칭할 때 매우 안정적인 통신이 가능해지죠.
스테이트풀셋에서 파드 이름이 유지되는 이유는 쿠버네티스가 파드의 상태를 추적하며 해당 인덱스 번호에 맞는 설정을 계속해서 재할당하기 때문이에요. 이는 클러스터 내부의 DNS 설정과 밀접하게 연결되어 있습니다.
저장소 부분도 매우 중요해요. 스테이트풀셋은 VolumeClaimTemplates라는 기능을 사용해요. 일반적인 디플로이먼트는 모든 파드가 하나의 PVC(PersistentVolumeClaim)를 공유하려 시도하는 실수를 범하기 쉽지만, 스테이트풀셋은 각 파드 번호에 맞는 전용 PVC를 자동으로 생성하고 연결해 줍니다. 즉, `mysql-0` 파드는 항상 `pvc-mysql-0`이라는 저장소만 사용하게 되어 데이터가 뒤섞일 염려가 없어요.
그럼 기존에 익숙한 디플로이먼트와 무엇이 다른지 한눈에 비교해 볼까요?
| 비교 항목 | 디플로이먼트 (Deployment) | 스테이트풀셋 (StatefulSet) |
|---|---|---|
| 파드 이름 | 무작위 해시값 (예: web-7f8d) | 순차적 인덱스 (예: db-0) |
| 저장소 연결 | 모든 파드가 동일한 PVC 공유 가능 | 파드별 개별 PVC 자동 할당 |
| 네트워크 식별 | 서비스 IP를 통해서만 통신 | 고유한 DNS 이름 제공 |
| 생성/삭제 순서 | 순서 상관없이 병렬 처리 | 번호 순서대로 순차적 처리 |
이러한 차이점 때문에 상태 유지가 필수적인 데이터베이스, 분산 로그 시스템(Kafka 등), 분산 저장소(Elasticsearch 등)를 운영할 때는 반드시 스테이트풀셋을 선택해야 해요. 만약 이를 무시하고 디플로이먼트를 사용한다면, 나중에 데이터 정합성이 깨지거나 복구가 불가능한 상황을 맞이할 수 있습니다.
실전! 데이터베이스 클러스터 구축 단계별 가이드
실제로 운영 환경에서 MySQL 클러스터를 스테이트풀셋으로 구축하는 과정을 시나리오로 구성해 보았어요. 단순히 YAML 파일을 복사해서 붙여넣는 것이 아니라, 각 단계가 왜 필요한지 이해하며 진행하는 것이 핵심이에요.
STEP 1. 헤드리스 서비스(Headless Service) 설계하기
스테이트풀셋 운영의 첫 단추는 바로 헤드리스 서비스를 만드는 거예요. 일반적인 서비스는 여러 파드를 하나의 IP로 묶어 로드밸런싱을 해주지만, 헤드리스 서비스는 `clusterIP: None` 설정을 통해 서비스 자체의 IP를 갖지 않아요. 대신 클러스터 내부의 DNS를 통해 각 파드의 개별 IP를 직접 찾을 수 있게 해주죠.
데이터베이스 클러스터에서는 ‘Primary(쓰기 담당)’와 ‘Replica(읽기 담당)’를 구분해야 해요. 만약 헤드리스 서비스가 없다면, 클러스터 내부의 다른 애플리케이션이 어떤 파드가 Primary인지 찾아낼 방법이 없게 됩니다. 헤드리스 서비스를 통해 `mysql-0.mysql-service` 같은 고유한 주소를 얻게 되면, 각 파드는 네트워크 상에서 명확한 주소를 가진 존재가 됩니다.
STEP 2. 볼륨 클레임 템플릿(VolumeClaimTemplates) 설정하기
두 번째 단계는 파드마다 독립된 디스크를 제공하는 설정이에요. 스테이트풀셋 매니페스트 파일 내부에 `volumeClaimTemplates` 섹션을 작성해야 합니다. 여기서 주의할 점은 일반적인 `volumes` 항목에 PVC를 직접 적는 것이 아니라는 점이에요.
템플릿 방식을 사용하면 스테이트풀셋이 파드를 하나씩 늘릴 때마다, 미리 정의한 용량과 스토리지 클래스(StorageClass)를 바탕으로 새로운 PVC를 자동으로 생성해 줍니다. 예를 들어 `mysql-0` 파드가 생기면 `data-mysql-0`라는 이름의 PVC가 만들어지고, `mysql-1` 파드가 생기면 `data-mysql-1`이라는 PVC가 생겨나요. 이 과정이 없다면 모든 파드가 같은 디스크를 바라보게 되어 데이터가 덮어씌워지는 대참사가 일어날 거예요.
STEP 3. 파드 생성 순서와 복제 제어하기
스테이트풀셋은 파드를 생성할 때 0번부터 순서대로 생성하며, 이전 파드가 완전히 준비(Ready) 상태가 될 때까지 다음 파드를 만들지 않고 기다립니다. 이는 클러스터 구성 시 매우 유익해요. 데이터베이스의 경우, 첫 번째 파드인 `mysql-0`이 먼저 뜨고 데이터 초기화(Initialization)를 마친 뒤에야 두 번째 파드인 `mysql-1`이 생성되어 복제(Replication)를 시작할 수 있기 때문이죠.
삭제할 때도 마찬가지예요. 스테이트풀셋은 높은 번호의 파드부터 거꾸로 삭제합니다. 이는 클러스터의 가용성을 유지하기 위한 안전장치예요. 만약 번호 순서가 뒤섞인다면 클러스터의 쿼럼(Quorum, 의사결정 정족수)이 깨질 위험이 있으므로, 이러한 순차적 제어는 매우 정밀하게 동작해야 합니다.
STEP 4. 실제 배포 및 클러스터 검증하기
설정이 완료되었다면 이제 배포를 진행합니다. 배포 후에는 반드시 다음 세 가지를 체크해야 해요.
- 파드 네이밍 확인: `kubectl get pods`를 통해 `mysql-0`, `mysql-1` 식으로 이름이 잘 붙었는지 확인하세요.
- 개별 PVC 생성 확인: `kubectl get pvc` 명령어로 각 파드에 대응하는 독립적인 볼륨이 생성되었는지 점검하세요.
- 네트워크 통신 테스트: 클러스터 내부의 다른 파드에서 `nslookup mysql-0.mysql-service` 명령을 날려 해당 파드의 IP가 정확히 응답하는지 확인해야 합니다.
실무에서는 MySQL이나 PostgreSQL을 직접 스테이트풀셋으로 관리하기보다, Operator(오퍼레이터) 패턴을 사용하는 경우가 많습니다. 오퍼레이터는 스테이트풀셋을 기반으로 데이터베이스의 백업, 복구, 업그레이드 같은 복잡한 운영 로직을 자동화해 주는 소프트웨어입니다.
아래는 이해를 돕기 위한 간단한 구성 시나리오 예시입니다.
| 구분 | 구성 내용 | 목적 |
|---|---|---|
| Service | Headless Service (clusterIP: None) | 파드별 고유 DNS 주소 제공 |
| Storage | SSD 기반 StorageClass + PVC Template | 파드별 영구 데이터 보존 |
| Replicas | 3개 (1 Primary + 2 Replica) | 고가용성 및 읽기 분산 |
이처럼 스테이트풀셋은 각 구성 요소가 톱니바퀴처럼 맞물려 돌아가야 합니다. 특히 저장소와 네트워크 설정이 어긋나면 데이터 정합성이 깨지거나 통신 불능 상태에 빠질 수 있으니 주의가 필요해요.
자주 하는 실수와 해결법
실무 운영 경험을 통해 많은 개발자가 공통적으로 저지르는 실수들을 정리했습니다. 미리 알고 있으면 큰 사고를 막을 수 있어요.
- ❌ 실수: 데이터베이스를 디플로이먼트로 배포함
➡️ 이유: 파드가 재시작될 때 데이터가 담긴 볼륨을 다시 찾지 못하고 새 볼륨을 붙여버림
➡️ ✅ 해결: 반드시 스테이트풀셋을 사용하고 `volumeClaimTemplates`를 활용하세요. - ❌ 실수: 헤드리스 서비스를 만들지 않음
➡️ 이유: 파드들이 서로를 고유한 이름으로 호출할 수 없어 복제(Replication) 구성이 불가능함
➡️ ✅ 해결: `clusterIP: None`으로 설정된 서비스를 반드시 생성하고 스테이트풀셋의 `serviceName` 필드와 연결하세요. - ❌ 실수: 기존 PVC를 `volumes` 섹션에 직접 입력함
➡️ 이유: 모든 파드가 하나의 볼륨만 공유하게 되어 데이터가 충돌하거나 권한 오류가 발생함
➡️ ✅ 해결: 파드별 독립 볼륨을 위해 `volumeClaimTemplates`를 사용하여 자동 생성 방식을 택하세요. - ❌ 실수: 파드 삭제 순서를 무시한 클러스터 축소
➡️ 이유: 중요한 Primary 파드가 먼저 삭제되어 데이터 쓰기가 중단될 수 있음
➡️ ✅ 해결: 스테이트풀셋의 순차 삭제 메커니즘을 이해하고, 클러스터 상태를 확인하며 진행하세요. - ❌ 실수: 스토리지 클래스(StorageClass) 설정 미비
➡️ 이유: 파드가 생성될 때 실제 물리 디스크가 자동으로 할당되지 않아 파드가 `Pending` 상태에 머무름
➡️ ✅ 해결: 클러스터에 적절한 스토리지 클래스가 설정되어 있는지, PVC가 해당 클래스를 사용하는지 확인하세요.
스테이트풀셋은 파드가 삭제되어도 PVC는 자동으로 삭제되지 않습니다. 이는 데이터 보호를 위한 설계이지만, 반대로 말하면 테스트를 마치고 클러스터를 정리할 때 수동으로 PVC를 지워주지 않으면 계속해서 스토리지 비용이 발생할 수 있다는 뜻입니다.
자주 묻는 질문
Q. 스테이트풀셋을 사용하면 데이터 유실 위험이 아예 없나요?
완벽한 안전은 없습니다. 스테이트풀셋은 파드와 디스크의 연결을 보장해 주지만, 물리적인 디스크 장애나 스토리지 클래스의 결함까지 막아주지는 못해요. 따라서 별도의 백업 전략을 반드시 병행해야 합니다.
Q. 왜 디플로이먼트보다 배포 속도가 느린가요?
스테이트풀셋은 파드를 하나씩 순차적으로 띄우고, 각 파드가 ‘준비 완료’ 상태가 될 때까지 기다리기 때문이에요. 이는 데이터베이스의 일관성을 위한 필수적인 대기 시간이라고 보시면 됩니다.
Q. Redis 같은 인메모리 DB도 스테이트풀셋이 필요한가요?
네, 필요합니다. Redis도 영속성을 위해 스냅샷을 저장하거나 AOF(Append Only File)를 사용한다면, 재시작 시 데이터를 복구하기 위해 스테이트풀셋을 사용하는 것이 훨씬 안정적입니다.
Q. 파드 이름을 중간에 바꿀 수 있나요?
아니요, 스테이트풀셋이 관리하는 파드의 이름은 고정되어 있습니다. 이름을 바꾸고 싶다면 스테이트풀셋 자체를 수정하거나 새로 만들어야 합니다.
Q. 규모가 커지면 스테이트풀셋만으로 충분할까요?
매우 큰 규모의 클러스터에서는 스테이트풀셋만으로는 운영 난이도가 급격히 올라갑니다. 이때는 앞서 언급한 오퍼레이터(Operator) 도입을 강력히 권장합니다.
안정적인 운영을 위한 마지막 체크리스트
지금까지 스테이트풀셋을 활용한 데이터베이스 운영 사례를 살펴보았습니다. 상태가 있는 애플리케이션을 쿠버네티스에서 다루는 것은 매우 까다롭지만, 스테이트풀셋이라는 강력한 도구를 제대로 활용한다면 충분히 안정적인 서비스를 구축할 수 있어요.
- 데이터베이스에는 반드시 스테이트풀셋을 사용하세요.
- 헤드리스 서비스를 통해 고유한 네트워크 주소를 확보하세요.
- volumeClaimTemplates를 사용하여 파드별 독립 볼륨을 할당하세요.
- 파드의 생성 및 삭제 순서가 클러스터 안정성에 미치는 영향을 이해하세요.
- 운영 효율을 위해 장기적으로는 오퍼레이터 도입을 검토하세요.
- 정리 시에는 사용하지 않는 PVC가 남아 비용을 발생시키지 않는지 확인하세요.
오늘 배운 내용을 바탕으로 지금 바로 실습 환경에서 작은 데이터베이스 클러스터를 직접 배포해 보세요. 이론으로만 보던 네트워크 주소와 볼륨 연결이 어떻게 실제로 작동하는지 눈으로 확인하는 과정이 가장 빠른 학습 방법입니다.
만약 실습 도중 설정이 꼬이거나 예상치 못한 에러 메시지가 나타난다면, 주저하지 말고 댓글로 질문을 남겨 주세요. 함께 고민하고 해결책을 찾아보겠습니다!
관련하여 더 깊이 있는 학습을 원하신다면 아래 글들도 함께 읽어보시길 추천드려요.
🔗 쿠버네티스 스테이트풀셋 기본 개념 글
🔗 클러스터 구축 입문 가이드