본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 29. · 10 Views
StatefulSet으로 상태 유지 완벽 가이드
Kubernetes에서 데이터베이스, 메시지 큐 같은 상태를 가진 애플리케이션을 안정적으로 운영하는 방법을 알아봅니다. StatefulSet의 핵심 개념부터 실무 활용까지 차근차근 설명합니다.
목차
1. StatefulSet vs Deployment
김개발 씨는 회사에서 MongoDB 클러스터를 Kubernetes에 올리라는 미션을 받았습니다. 평소처럼 Deployment를 작성하려던 그때, 선배 박시니어 씨가 지나가며 한마디 던졌습니다.
"데이터베이스는 StatefulSet으로 해야지, Deployment로 하면 큰일 나."
StatefulSet은 상태를 가진 애플리케이션을 위한 Kubernetes 워크로드입니다. Deployment가 언제든 교체 가능한 일회용 컵이라면, StatefulSet은 주인이 정해진 개인 머그컵과 같습니다.
각 Pod가 고유한 정체성을 유지하기 때문에 데이터베이스처럼 상태가 중요한 애플리케이션에 적합합니다.
다음 코드를 살펴봅시다.
# Deployment - Pod 이름이 랜덤하게 생성됨
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
---
# StatefulSet - Pod 이름이 순서대로 생성됨 (web-0, web-1, web-2)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
김개발 씨는 입사 6개월 차 DevOps 엔지니어입니다. 그동안 웹 서버, API 서버 같은 것들은 Deployment로 잘 배포해왔습니다.
그런데 오늘은 좀 다릅니다. MongoDB 레플리카셋을 Kubernetes에 올려야 하는데, 선배가 Deployment를 쓰면 안 된다고 합니다.
"왜 안 되는 거죠? Deployment도 여러 개 Pod를 잘 띄워주잖아요?" 박시니어 씨가 화이트보드 앞으로 김개발 씨를 데려갑니다.
"자, 여기 카페가 있다고 생각해봐. Deployment는 마치 일회용 컵 같은 거야.
손님이 오면 아무 컵이나 집어서 커피를 담아주지. 컵이 깨지면?
그냥 버리고 새 컵 쓰면 돼." 김개발 씨가 고개를 끄덕입니다. "근데 StatefulSet은 달라.
이건 단골손님 전용 머그컵이야. 김철수 씨 컵, 박영희 씨 컵이 따로 있는 거지.
각 컵에는 주인 이름이 새겨져 있고, 그 사람만 쓸 수 있어." Deployment로 Pod를 3개 만들면 이름이 nginx-deployment-7b9f5c8d4-xk2mn처럼 랜덤한 해시값이 붙습니다. Pod가 죽고 다시 생성되면 완전히 다른 이름을 받습니다.
웹 서버처럼 상태가 없는 애플리케이션은 이래도 상관없습니다. 어차피 모든 Pod가 똑같은 일을 하니까요.
하지만 데이터베이스는 다릅니다. MongoDB 레플리카셋을 예로 들면, Primary 노드와 Secondary 노드가 서로를 알아야 합니다.
"너는 mongo-0이고, 나는 mongo-1이야. 우리 둘이 데이터를 동기화하자." 이런 식으로요.
StatefulSet은 바로 이 문제를 해결합니다. Pod 이름이 web-0, web-1, web-2처럼 순서대로 붙습니다.
Pod가 죽어도 같은 이름으로 다시 태어납니다. 마치 불사조처럼요.
위 코드를 비교해보면 구조는 거의 비슷합니다. 하지만 결정적인 차이가 있습니다.
StatefulSet에는 serviceName 필드가 있습니다. 이 필드가 있어야 각 Pod가 고유한 네트워크 정체성을 가질 수 있습니다.
실무에서 StatefulSet이 필요한 대표적인 경우는 이렇습니다. 첫째, 데이터베이스입니다.
MySQL, PostgreSQL, MongoDB 등 데이터를 저장하는 모든 것들이요. 둘째, 메시지 큐입니다.
Kafka, RabbitMQ처럼 메시지를 순서대로 처리해야 하는 시스템이죠. 셋째, 분산 캐시입니다.
Redis 클러스터처럼 여러 노드가 협력해야 하는 경우입니다. 박시니어 씨의 설명을 들은 김개발 씨는 이제 이해했습니다.
"아, 그래서 데이터베이스는 StatefulSet이어야 하는 거군요. Pod마다 고유한 이름이 있어야 서로를 찾을 수 있으니까요!"
실전 팁
💡 - 상태가 없는 애플리케이션은 Deployment를, 상태가 있는 애플리케이션은 StatefulSet을 사용하세요
- StatefulSet의 serviceName은 반드시 Headless Service와 연결해야 합니다
2. 안정적인 네트워크 ID
김개발 씨가 StatefulSet으로 MongoDB를 배포했습니다. 그런데 Pod들이 서로 통신을 못 하는 것 같습니다.
"분명 Pod는 잘 떠있는데, 왜 레플리카셋 구성이 안 될까요?" 박시니어 씨가 웃으며 말했습니다. "각 Pod의 DNS 이름을 확인해봤어?"
StatefulSet의 각 Pod는 안정적인 네트워크 ID를 가집니다. Pod 이름이 예측 가능하고, 전용 DNS 이름까지 부여받습니다.
형식은 $(Pod이름).$(서비스이름).$(네임스페이스).svc.cluster.local입니다. 덕분에 다른 Pod나 서비스가 특정 Pod를 정확히 찾아갈 수 있습니다.
다음 코드를 살펴봅시다.
# StatefulSet Pod의 DNS 이름 예시
# Pod 이름: mongo-0, mongo-1, mongo-2
# Service 이름: mongo
# Namespace: default
# 각 Pod의 FQDN (Fully Qualified Domain Name)
mongo-0.mongo.default.svc.cluster.local
mongo-1.mongo.default.svc.cluster.local
mongo-2.mongo.default.svc.cluster.local
# 같은 네임스페이스에서는 짧게 사용 가능
mongo-0.mongo
mongo-1.mongo
mongo-2.mongo
# MongoDB 레플리카셋 초기화 예시
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo-0.mongo:27017" },
{ _id: 1, host: "mongo-1.mongo:27017" },
{ _id: 2, host: "mongo-2.mongo:27017" }
]
})
김개발 씨는 StatefulSet으로 MongoDB Pod 3개를 성공적으로 띄웠습니다. mongo-0, mongo-1, mongo-2가 모두 Running 상태입니다.
이제 레플리카셋을 구성해야 하는데, 문제가 생겼습니다. Pod들이 서로를 찾지 못하는 것 같습니다.
"IP 주소로 연결하면 되지 않나요?" 김개발 씨가 물었습니다. 박시니어 씨가 고개를 저었습니다.
"Pod IP는 언제든 바뀔 수 있어. Pod가 죽었다가 다시 뜨면 새 IP를 받거든.
그러면 레플리카셋 설정이 다 깨져버려." 이것은 마치 친구 집 주소를 외워뒀는데, 친구가 이사를 가버린 상황과 같습니다. 매번 이사할 때마다 새 주소를 알아내야 한다면 정말 불편하겠죠.
하지만 친구가 고정 전화번호를 가지고 있다면요? 이사를 가도 같은 번호로 연락할 수 있습니다.
StatefulSet의 네트워크 ID가 바로 이 고정 전화번호 역할을 합니다. 각 Pod는 자신만의 DNS 이름을 받습니다.
mongo-0은 항상 mongo-0.mongo라는 이름으로 찾을 수 있습니다. Pod가 죽었다가 다시 생겨도, 다른 노드로 이동해도, 이 이름은 변하지 않습니다.
DNS 이름의 전체 형식은 $(Pod이름).$(서비스이름).$(네임스페이스).svc.cluster.local입니다. 꽤 길지만, 같은 네임스페이스 안에서는 mongo-0.mongo처럼 짧게 써도 됩니다.
Kubernetes DNS가 알아서 찾아주니까요. 위 코드에서 MongoDB 레플리카셋 초기화 명령을 보세요.
각 멤버의 host에 IP 주소가 아닌 DNS 이름을 사용합니다. mongo-0.mongo:27017처럼요.
이렇게 하면 Pod가 재시작되어도 레플리카셋 설정이 깨지지 않습니다. 이 안정적인 네트워크 ID 덕분에 가능한 것들이 많습니다.
첫째, 클러스터 멤버십 관리입니다. 분산 시스템의 각 노드가 서로를 안정적으로 찾을 수 있습니다.
둘째, 클라이언트 연결입니다. 애플리케이션이 특정 데이터베이스 노드에 직접 연결할 수 있습니다.
셋째, 리더 선출입니다. 분산 합의 알고리즘에서 노드 식별이 필요할 때 유용합니다.
주의할 점도 있습니다. 이 DNS 이름이 작동하려면 Headless Service가 반드시 필요합니다.
일반 Service는 로드밸런서처럼 동작해서 요청을 아무 Pod에나 보내버립니다. 하지만 Headless Service는 각 Pod의 DNS를 직접 노출시켜줍니다.
김개발 씨가 환하게 웃었습니다. "아, 그래서 StatefulSet에 serviceName을 지정해야 했던 거군요.
그 서비스가 각 Pod의 DNS를 만들어주는 거였어요!"
실전 팁
💡 - Pod의 DNS 이름은 재시작 후에도 유지되므로 설정 파일에 안심하고 사용하세요
- 같은 네임스페이스에서는 짧은 DNS 이름(pod-name.service-name)을 사용할 수 있습니다
3. 순서 보장 배포
김개발 씨가 Kafka 클러스터를 StatefulSet으로 배포하고 있습니다. 그런데 이상합니다.
Pod가 동시에 막 생기는 게 아니라 하나씩 천천히 뜨고 있습니다. "혹시 뭔가 잘못된 건가요?" 박시니어 씨가 대답했습니다.
"아니, 그게 정상이야. StatefulSet은 원래 그렇게 동작해."
StatefulSet은 Pod를 순서대로 생성하고 삭제합니다. 생성할 때는 0번부터 차례로, 삭제할 때는 마지막 번호부터 역순으로 진행합니다.
앞 번호 Pod가 완전히 준비되어야 다음 Pod를 생성합니다. 이 순서 보장 덕분에 마스터-슬레이브 구조나 클러스터 초기화가 안전하게 이루어집니다.
다음 코드를 살펴봅시다.
# StatefulSet 생성 순서 확인
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
spec:
serviceName: "kafka"
replicas: 3
podManagementPolicy: OrderedReady # 기본값 - 순서 보장
selector:
matchLabels:
app: kafka
template:
metadata:
labels:
app: kafka
spec:
containers:
- name: kafka
image: bitnami/kafka:3.5
readinessProbe: # 이 프로브가 성공해야 다음 Pod 생성
tcpSocket:
port: 9092
initialDelaySeconds: 30
periodSeconds: 10
# 생성 순서: kafka-0 → (Ready 확인) → kafka-1 → (Ready 확인) → kafka-2
# 삭제 순서: kafka-2 → kafka-1 → kafka-0
# 병렬 생성이 필요한 경우 (순서가 중요하지 않을 때)
# podManagementPolicy: Parallel
김개발 씨는 Kafka 클러스터를 배포하면서 이상한 점을 발견했습니다. kubectl get pods -w 명령으로 지켜보니, kafka-0이 먼저 뜨고, 한참 있다가 kafka-1이 뜨고, 또 한참 있다가 kafka-2가 뜹니다.
Deployment로 배포할 때는 3개가 동시에 와르르 생겼는데 말이죠. "버그인가요?" 김개발 씨가 걱정스럽게 물었습니다.
박시니어 씨가 미소를 지었습니다. "아니, 그게 StatefulSet의 핵심 기능 중 하나야.
순서 보장이라고 해." 생각해보면 이 순서가 왜 중요한지 이해할 수 있습니다. 마치 회사에 신입사원이 들어오는 것과 비슷합니다.
첫 번째 신입이 와서 자리를 잡고, 업무를 파악하고, 시스템에 익숙해져야 합니다. 그래야 두 번째 신입이 왔을 때 "여기가 화장실이고, 저기가 회의실이야"라고 알려줄 수 있습니다.
모두가 동시에 입사하면 아무도 뭘 해야 할지 모르는 혼란이 생깁니다. 분산 시스템도 마찬가지입니다.
Kafka 클러스터에서 kafka-0이 먼저 뜨면 스스로를 클러스터의 첫 번째 브로커로 인식합니다. kafka-1이 뜰 때는 kafka-0에게 "나도 클러스터에 참여할게"라고 말할 수 있습니다.
모두가 동시에 뜨면 "누가 첫 번째지?" 하며 혼란에 빠질 수 있습니다. OrderedReady는 StatefulSet의 기본 podManagementPolicy입니다.
이 정책 아래에서 Kubernetes는 다음 규칙을 따릅니다. 첫째, Pod는 0, 1, 2 순서로 생성됩니다.
둘째, N번 Pod가 Ready 상태가 되어야 N+1번 Pod를 생성합니다. 셋째, 삭제할 때는 역순으로 2, 1, 0 순서로 진행합니다.
위 코드에서 readinessProbe를 주목하세요. 이 프로브가 성공해야 Pod가 Ready 상태가 됩니다.
kafka-0의 9092 포트가 열리고 readinessProbe가 성공해야만 kafka-1 생성이 시작됩니다. 이것이 순서 보장의 핵심입니다.
물론 순서가 중요하지 않은 경우도 있습니다. 예를 들어 캐시 서버처럼 각 Pod가 독립적으로 동작하는 경우요.
이럴 때는 podManagementPolicy: Parallel을 설정하면 됩니다. 그러면 Deployment처럼 모든 Pod가 동시에 생성됩니다.
스케일 다운할 때의 역순 삭제도 중요합니다. 보통 분산 시스템에서 마지막에 참여한 노드가 가장 적은 데이터를 가지고 있습니다.
따라서 마지막 노드부터 제거하는 것이 데이터 재분배 비용을 최소화합니다. 김개발 씨가 고개를 끄덕였습니다.
"그렇구나. 데이터베이스 클러스터가 안전하게 초기화되려면 이런 순서가 필요하군요.
모두가 동시에 뜨면 누가 Primary인지 결정하기 어려울 테니까요."
실전 팁
💡 - readinessProbe를 반드시 설정하세요. 없으면 Pod가 실제로 준비되지 않았어도 다음 Pod가 생성될 수 있습니다
- 순서가 중요하지 않은 워크로드는 Parallel 정책으로 배포 속도를 높일 수 있습니다
4. Headless Service 연동
김개발 씨가 StatefulSet을 배포했는데 Pod의 DNS 이름이 작동하지 않습니다. nslookup mongo-0.mongo를 해도 아무것도 안 나옵니다.
"서비스도 만들었는데 왜 안 되죠?" 박시니어 씨가 서비스 정의를 보더니 바로 문제를 찾았습니다. "여기, clusterIP: None이 빠졌네."
Headless Service는 clusterIP가 없는 특별한 서비스입니다. 일반 서비스는 하나의 가상 IP로 트래픽을 분산시키지만, Headless Service는 각 Pod의 IP를 직접 DNS로 노출합니다.
StatefulSet과 함께 사용하면 각 Pod가 고유한 DNS 이름을 갖게 되어 직접 접근이 가능해집니다.
다음 코드를 살펴봅시다.
# Headless Service 정의
apiVersion: v1
kind: Service
metadata:
name: mongo
labels:
app: mongo
spec:
ports:
- port: 27017
name: mongo
clusterIP: None # 핵심! 이것이 Headless Service를 만듦
selector:
app: mongo
---
# StatefulSet과 연동
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
spec:
serviceName: "mongo" # 위 Headless Service 이름과 일치해야 함
replicas: 3
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:6.0
ports:
- containerPort: 27017
# DNS 조회 결과 비교
# 일반 Service: nslookup mongo → 10.96.0.100 (Service의 ClusterIP)
# Headless Service: nslookup mongo → 10.244.1.5, 10.244.2.6, 10.244.3.7 (각 Pod IP)
김개발 씨는 MongoDB StatefulSet을 배포하고 서비스도 만들었습니다. 그런데 mongo-0.mongo라는 DNS 이름이 작동하지 않습니다.
분명 문서에서 본 대로 했는데 말이죠. 박시니어 씨가 서비스 정의를 살펴봅니다.
"아, 여기가 문제네. 일반 Service를 만들었구나.
StatefulSet에는 Headless Service가 필요해." "Headless요? 머리가 없다는 건가요?" 김개발 씨가 의아한 표정을 지었습니다.
박시니어 씨가 설명을 시작합니다. "일반 Service를 생각해봐.
마치 콜센터 대표번호 같은 거야. 고객이 1588-XXXX로 전화하면 콜센터가 빈 상담원에게 연결해주지.
어떤 상담원이 받을지 고객은 모르고, 알 필요도 없어." 이것이 일반 Service의 동작 방식입니다. Service에는 하나의 ClusterIP가 할당됩니다.
클라이언트가 이 IP로 요청하면, kube-proxy가 여러 Pod 중 하나로 트래픽을 분산시킵니다. 로드밸런서처럼요.
"그런데 Headless Service는 달라. 이건 마치 직통 전화번호를 공개하는 거야.
김철수 상담원은 02-1234-5678, 박영희 상담원은 02-1234-5679. 고객이 특정 상담원에게 직접 전화할 수 있지." Headless Service는 clusterIP: None으로 설정합니다.
그러면 Service에 가상 IP가 할당되지 않습니다. 대신 DNS 쿼리를 하면 모든 Pod의 IP가 직접 반환됩니다.
그리고 StatefulSet과 함께 사용하면, 각 Pod별로 고유한 DNS 레코드가 생성됩니다. 위 코드를 보면 Service에 clusterIP: None이 설정되어 있습니다.
이것 하나로 일반 Service가 Headless Service로 바뀝니다. 그리고 StatefulSet의 serviceName: "mongo"가 이 서비스를 가리킵니다.
DNS 조회 결과의 차이를 이해하는 것이 중요합니다. 일반 Service에 nslookup을 하면 Service의 ClusterIP 하나만 반환됩니다.
하지만 Headless Service는 각 Pod의 IP가 모두 반환됩니다. 그리고 mongo-0.mongo처럼 특정 Pod를 지정하면, 그 Pod의 IP만 반환됩니다.
흔한 실수 중 하나는 StatefulSet의 serviceName과 실제 Service 이름을 다르게 설정하는 것입니다. 이 둘은 반드시 일치해야 합니다.
그래야 Kubernetes가 올바른 DNS 레코드를 생성할 수 있습니다. 또 하나 주의할 점이 있습니다.
Headless Service는 로드밸런싱을 하지 않습니다. 클라이언트가 서비스 이름으로 접근하면 모든 Pod IP를 받지만, 어떤 Pod에 연결할지는 클라이언트가 결정해야 합니다.
따라서 클라이언트 측 로드밸런싱이 필요한 경우가 많습니다. 김개발 씨가 서비스를 수정하고 다시 테스트합니다.
"오! 이제 mongo-0.mongo가 잘 됩니다.
clusterIP: None 하나가 이렇게 중요한 거였군요."
실전 팁
💡 - StatefulSet의 serviceName과 Headless Service의 metadata.name이 반드시 일치해야 합니다
- Headless Service에서도 selector는 필요합니다. 어떤 Pod의 DNS를 노출할지 결정하기 때문입니다
5. PVC 템플릿 사용
김개발 씨가 MongoDB StatefulSet을 재시작했더니 데이터가 모두 사라졌습니다. "어제까지 있던 데이터가 다 날아갔어요!" 박시니어 씨가 물었습니다.
"혹시 volumeClaimTemplates를 설정 안 했어?" 김개발 씨의 얼굴이 새하얘졌습니다.
volumeClaimTemplates는 StatefulSet의 핵심 기능으로, 각 Pod마다 고유한 PersistentVolumeClaim을 자동 생성합니다. Pod가 삭제되어도 PVC는 남아있어 데이터가 보존됩니다.
새 Pod가 생성되면 같은 이름의 PVC에 자동으로 연결되어 이전 데이터를 그대로 사용할 수 있습니다.
다음 코드를 살펴봅시다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
spec:
serviceName: "mongo"
replicas: 3
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:6.0
volumeMounts:
- name: data
mountPath: /data/db # MongoDB 데이터 저장 경로
# 핵심: 각 Pod마다 PVC를 자동 생성
volumeClaimTemplates:
- metadata:
name: data # volumeMounts의 name과 일치해야 함
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "standard"
resources:
requests:
storage: 10Gi
# 생성되는 PVC 이름 패턴: {volumeClaimTemplate.name}-{StatefulSet.name}-{ordinal}
# data-mongo-0, data-mongo-1, data-mongo-2
김개발 씨에게 최악의 아침이 찾아왔습니다. 어제 테스트 데이터를 열심히 넣어뒀는데, 아침에 출근하니 클러스터가 재시작되어 있었습니다.
그리고 데이터가 모두 사라졌습니다. "컨테이너가 재시작되면 데이터가 사라지는 건 알고 있었는데...
StatefulSet이라서 괜찮을 줄 알았어요." 박시니어 씨가 고개를 저었습니다. "StatefulSet은 Pod의 정체성을 유지해주지, 데이터를 자동으로 보관해주진 않아.
데이터를 보존하려면 PVC가 필요해." 이것을 이해하려면 컨테이너의 본질을 알아야 합니다. 컨테이너는 마치 호텔 방과 같습니다.
체크아웃하면 다음 손님을 위해 방이 깨끗이 청소됩니다. 이전 손님의 물건은 모두 치워지죠.
컨테이너도 마찬가지입니다. 컨테이너가 종료되면 내부 데이터는 사라집니다.
그러면 데이터를 어떻게 보존할까요? 호텔 금고를 생각해보세요.
귀중품을 금고에 넣어두면 체크아웃해도 안전합니다. 다음에 다시 왔을 때 금고를 열면 물건이 그대로 있죠.
PersistentVolume이 바로 이 금고 역할을 합니다. StatefulSet의 volumeClaimTemplates는 각 Pod마다 개인 금고를 자동으로 할당해줍니다.
mongo-0은 data-mongo-0이라는 PVC를, mongo-1은 data-mongo-1이라는 PVC를 받습니다. 이 PVC는 Pod와 별개로 존재하므로, Pod가 죽어도 PVC는 남아있습니다.
위 코드를 보면 volumeClaimTemplates 섹션이 있습니다. 여기서 PVC의 스펙을 정의합니다.
name: data는 volumeMounts와 연결됩니다. storage: 10Gi는 각 Pod에 10GB 저장소를 할당하라는 의미입니다.
PVC 이름 생성 규칙도 중요합니다. 형식은 **{템플릿이름}-{StatefulSet이름}-{순번}**입니다.
그래서 data-mongo-0, data-mongo-1, data-mongo-2가 생성됩니다. 이 규칙 덕분에 Pod가 재생성될 때 같은 PVC를 찾아서 연결할 수 있습니다.
주의할 점이 있습니다. StatefulSet을 삭제해도 PVC는 자동으로 삭제되지 않습니다.
이것은 데이터 보호를 위한 의도적인 설계입니다. 실수로 StatefulSet을 지워도 데이터는 안전하게 남아있습니다.
물론 더 이상 필요 없다면 PVC를 수동으로 삭제해야 합니다. 스케일 다운할 때도 마찬가지입니다.
replicas를 3에서 2로 줄이면 mongo-2 Pod는 삭제되지만, data-mongo-2 PVC는 남아있습니다. 나중에 다시 스케일 업하면 새로운 mongo-2가 기존 PVC에 연결되어 데이터를 그대로 사용합니다.
김개발 씨가 volumeClaimTemplates를 추가하고 다시 배포했습니다. "이제 재시작해도 데이터가 안전하겠네요.
비싼 수업료였지만 확실히 배웠습니다."
실전 팁
💡 - StatefulSet을 삭제할 때 PVC도 함께 정리하려면 kubectl delete pvc -l app=mongo처럼 레이블로 삭제하세요
- storageClassName을 지정하지 않으면 클러스터의 기본 StorageClass가 사용됩니다
6. StatefulSet 업데이트 전략
김개발 씨가 MongoDB 이미지를 업데이트해야 합니다. "Deployment는 그냥 이미지 바꾸면 롤링 업데이트되는데, StatefulSet도 그런가요?" 박시니어 씨가 대답했습니다.
"비슷하지만 좀 달라. StatefulSet은 역순으로 업데이트하고, partition 기능도 있어서 더 세밀하게 제어할 수 있지."
StatefulSet의 기본 업데이트 전략은 RollingUpdate입니다. Deployment와 달리 가장 큰 번호의 Pod부터 역순으로 업데이트합니다.
partition 값을 설정하면 특정 번호 이상의 Pod만 업데이트하여 카나리 배포가 가능합니다. 이를 통해 일부 Pod에서 먼저 테스트한 후 전체 배포할 수 있습니다.
다음 코드를 살펴봅시다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
spec:
serviceName: "mongo"
replicas: 5
# 업데이트 전략 설정
updateStrategy:
type: RollingUpdate # 기본값
rollingUpdate:
partition: 3 # ordinal >= 3인 Pod만 업데이트 (mongo-3, mongo-4)
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:7.0 # 새 버전으로 변경
# partition: 3 설정 시 업데이트 대상
# mongo-4: 업데이트됨 (ordinal 4 >= partition 3)
# mongo-3: 업데이트됨 (ordinal 3 >= partition 3)
# mongo-2: 업데이트 안 됨 (ordinal 2 < partition 3)
# mongo-1: 업데이트 안 됨
# mongo-0: 업데이트 안 됨
# 전체 업데이트하려면 partition을 0으로 변경
# kubectl patch statefulset mongo -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
프로덕션 MongoDB 클러스터를 운영하던 김개발 씨에게 새로운 과제가 떨어졌습니다. MongoDB 버전을 6.0에서 7.0으로 업그레이드해야 합니다.
서비스 중단 없이 말이죠. "Deployment에서는 이미지만 바꾸면 알아서 롤링 업데이트되잖아요.
StatefulSet도 마찬가지 아닌가요?" 박시니어 씨가 화이트보드에 그림을 그리기 시작합니다. "비슷하지만 중요한 차이가 있어.
첫째, StatefulSet은 역순으로 업데이트해. 마지막 Pod부터 시작하는 거지." 이것은 데이터베이스 클러스터의 특성을 고려한 설계입니다.
보통 Primary나 Master 노드가 0번입니다. 가장 중요한 노드죠.
따라서 덜 중요한 마지막 노드부터 업데이트하고, 문제가 없으면 점점 앞으로 나아갑니다. "둘째, partition이라는 강력한 기능이 있어.
이걸로 카나리 배포를 할 수 있지." 카나리 배포는 광산에서 유래한 용어입니다. 옛날 광부들은 카나리아 새를 데리고 갱도에 들어갔습니다.
유독 가스가 있으면 카나리아가 먼저 쓰러지거든요. 그걸 보고 광부들이 대피할 수 있었습니다.
소프트웨어에서도 마찬가지입니다. 전체를 한 번에 업데이트하는 대신, 일부만 먼저 업데이트합니다.
문제가 생기면 그 일부만 영향받고, 빨리 발견해서 롤백할 수 있습니다. 위 코드에서 partition: 3을 보세요.
이렇게 설정하면 ordinal이 3 이상인 Pod만 업데이트됩니다. 5개의 Pod 중에서 mongo-3과 mongo-4만 새 버전으로 바뀌는 거죠.
mongo-0, mongo-1, mongo-2는 그대로입니다. 새 버전이 잘 동작하는지 확인합니다.
로그를 살펴보고, 메트릭을 점검하고, 일정 시간 모니터링합니다. 문제가 없다면 partition을 0으로 낮춥니다.
그러면 나머지 Pod도 차례로 업데이트됩니다. 만약 새 버전에서 문제가 발견되면요?
partition을 그대로 두고 이미지를 원래 버전으로 되돌립니다. 그러면 mongo-3, mongo-4만 롤백됩니다.
전체 클러스터에 영향을 주지 않고 문제를 해결할 수 있습니다. OnDelete라는 또 다른 전략도 있습니다.
이 전략에서는 Pod를 수동으로 삭제해야만 업데이트됩니다. 극도로 보수적인 환경에서 사용합니다.
운영자가 직접 하나씩 확인하며 업데이트하고 싶을 때요. 김개발 씨가 고개를 끄덕였습니다.
"partition으로 일부만 먼저 테스트하고, 안전하면 전체로 확대하는 거군요. 프로덕션 데이터베이스에는 이 정도 신중함이 필요하겠네요."
실전 팁
💡 - 카나리 배포 시 partition 값을 replicas-1로 시작해서 점차 줄여나가세요
- OnDelete 전략은 완전한 수동 제어가 필요한 미션 크리티컬 시스템에 적합합니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Istio 보안 완벽 가이드
마이크로서비스 환경에서 필수적인 Istio 보안 기능을 실무 중심으로 설명합니다. mTLS부터 인증, 인가까지 단계별로 학습하여 안전한 서비스 메시를 구축할 수 있습니다.
Istio 트래픽 관리 완벽 가이드
Istio의 트래픽 관리 기능을 마스터하는 완벽 가이드입니다. VirtualService와 DestinationRule을 활용한 라우팅부터 트래픽 분할, 헤더 기반 라우팅까지 실무에 필요한 모든 내용을 다룹니다.
Istio 설치와 구성 완벽 가이드
Kubernetes 환경에서 Istio 서비스 메시를 설치하고 구성하는 방법을 초급 개발자도 쉽게 이해할 수 있도록 실무 스토리와 비유로 풀어낸 가이드입니다. istioctl 설치부터 사이드카 주입까지 단계별로 학습합니다.
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.