Pod 이름이 매번 바뀌는데, 데이터베이스는 어떻게 클러스터를 이루나

MySQL 3대로 Galera 클러스터를 세우려 한다. 각 노드는 "내가 노드 1번이고, 노드 2번은 저기, 노드 3번은 저기"라고 알아야 한다 — 서로를 안정적인 이름으로 부를 수 있어야 복제 토폴로지가 잡힌다. 그런데 04장에서 본 대로 일반 Pod는 죽을 때마다 이름이 바뀐다. "mysql-1"이 죽고 새 Pod가 "mysql-x7y9"로 살아나면, 다른 노드는 "내가 알던 노드가 사라졌네?"라고 혼란에 빠진다.

이 글이 푸는 것은: Kubernetes가 상태 있는(stateful) 워크로드를 위해 StatefulSet으로 "안정된 정체(stable identity)"를 어떻게 보장하는가다. 핵심은 숫자다 — 0, 1, 2라는 순서가 있는 이름이, Pod가 죽었다 살아나도 변하지 않는다는 것.

StatefulSet이 보장하는 세 가지 안정성

Deployment가 만드는 Pod는 이름이 무작위 접미사(mysql-a1b2)를 단다. StatefulSet이 만드는 Pod는 순차 번호를 단다: mysql-0, mysql-1, mysql-2. 그리고 이 이름이 안정적이다. StatefulSet이 보장하는 것 세 가지:

  1. 안정된 네트워크 정체성: Pod 이름(mysql-0)과 그에 대응하는 DNS 이름(mysql-0.mysql.default.svc.cluster.local)이 죽었다 살아나도 같다.
  2. 안정된 스토리지: 각 Pod는 자기 전용 영구 볼륨(PV)을 갖고, 재생성돼도 같은 PV에 다시 연결된다.
  3. 순차성: 0번이 떠야 1번이 뜨고(생성), 역순으로 종료된다.
flowchart TD
    SS["StatefulSet<br/>replicas: 3"] --> P0["mysql-0<br/>→ PV-A (전용)"]
    SS --> P1["mysql-1<br/>→ PV-B (전용)"]
    SS --> P2["mysql-2<br/>→ PV-C (전용)"]
    HS["headless Service: mysql"] -. DNS .-> P0
    HS -. DNS .-> P1
    HS -. DNS .-> P2

이름이 번호라는 게 왜 중요한가? 상태ful 앱(MySQL Galera, ZooKeeper, Elasticsearch)은 보통 토폴로지를 설정으로 갖는다. "노드 0번은 이 역할, 1번은 저 역할"처럼. 이름이 안정적이면 이 설정이 깨지지 않는다.

headless Service — Pod 하나를 이름으로 찌르는 법

일반 Service(ClusterIP)는 여러 Pod를 묶어 하나의 가상 IP로 부른다. 로드밸런싱이 목적이다. 그런데 상태ful 앱은 "아무 Pod"가 아니라 *"정확히 mysql-0"*를 찔러야 한다 — 리더를 찌르거나, 특정 노드에만 쓰기를 보내거나.

이걸 가능하게 하는 게 headless Service(clusterIP: None)다. headless Service는 가상 IP를 주지 않는 대신, DNS가 각 Pod의 IP를 직접 반환하게 한다.

# Kubernetes 1.36
apiVersion: v1
kind: Service
metadata:
  name: mysql           # headless
spec:
  clusterIP: None       # 핵심: 가상 IP 없음
  selector:
    app: mysql
  ports: [{port: 3306}]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql    # 이 headless Service와 짝
  replicas: 3
  selector:
    matchLabels: {app: mysql}
  template:
    metadata:
      labels: {app: mysql}
    spec:
      containers:
      - name: mysql
        image: mysql:8.4

이제 DNS가 두 종류의 질의를 응답한다:

  • mysql.default.svc.cluster.local → Pod 3개의 IP 전부(클라이언트가 고름).
  • mysql-0.mysql.default.svc.cluster.local정확히 mysql-0의 IP.
# Kubernetes 1.36 — 특정 Pod를 이름으로 찾기
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \
  nslookup mysql-0.mysql.default.svc.cluster.local

확인할 것: mysql-0이라는 안정된 이름이 그 Pod의 IP를 반환한다. Pod가 재생성돼 IP가 바뀌어도 이름은 같은 Pod를 가리킨다.

headless Service의 이 DNS 동작은 StatefulSet이 serviceName으로 그 Service를 가리킬 때만 완전히 성립한다. 잊지 말 것.

volumeClaimTemplates — 각 Pod마다 전용 영구 볼륨

StatefulSet의 또 다른 핵심: volumeClaimTemplates. 각 Pod마다 자기 전용 PVC/PV를 자동으로 만들어 준다.

spec:
  serviceName: mysql
  replicas: 3
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests: {storage: 10Gi}
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.4
        volumeMounts:
        - {name: data, mountPath: /var/lib/mysql}

이렇게 하면 Pod마다 data-mysql-0, data-mysql-1, data-mysql-2라는 PVC가 생기고, 각 Pod는 자기 번호에 맞는 PVC에 연결된다. 핵심 보장: mysql-0이 죽어서 새 mysql-0이 떠도, 그것은 다시 data-mysql-0에 연결된다 — 이전 데이터가 그대로 남아 있다.

이것이 Deployment와 결정적으로 다른 점이다. Deployment는 Pod가 죽으면 새 Pod가 아무 노드에 볼륨으로 뜰 수 있다. 상태ful 앱엔 치명적이다 — "내 데이터"가 사라지거나 섞이니까. StatefulSet은 어느 데이터가 어느 Pod에를 영속적으로 묶는다.

순차적 생성과 종료 — 왜 순서가 보장되는가

StatefulSet은 Pod를 0번부터 순서대로 만든다. mysql-0이 ready(준비됨)돼야 mysql-1을 만들고, 그 다음 mysql-2. 종료는 역순 — 2번부터 죽는다. 이 순서 보장이 왜 필요한가?

상태ful 클러스터엔 보통 초기화 순서가 있다. 예를 들어 "첫 번째 노드가 데이터를 부트스트랩하고, 그다음 노드들이 첫 번째에 합류한다"는 패턴(ZooKeeper, etcd). 순서가 없으면 누가 부트스트랩할지 모호해진다.

# Kubernetes 1.36 — StatefulSet 생성 순서 관찰
kubectl get pods -l app=mysql -w

확인할 것: mysql-0이 먼저 Running+Ready가 되고, 그 후 mysql-1이 생긴다. 동시에 안 생김.

NAME      READY   STATUS              RESTARTS   AGE
mysql-0   0/1     ContainerCreating   0          1s
mysql-0   1/1     Running             0          3s
mysql-1   0/1     Pending             0          0s
mysql-1   0/1     ContainerCreating   0          1s
mysql-1   1/1     Running             0          4s
...

순서를 끄는 옵션도 있다 — podManagementPolicy: Parallel. 이땐 동시 생성된다. 단 정체성 보장(이름/PV)은 여전히 순차 번호로. 병렬은 보통 동질적 replica(예: 캐시 노드)에만.

StatefulSet과 RollingUpdate — 한 번에 하나씩, 역순으로

StatefulSet도 롤아웃을 한다. 이미지를 바꾸면 역순으로(2번→1번→0번) 한 번에 하나씩 교체한다. 각 Pod가 ready+lready가 돼야 다음으로 넘어간다. Deployment의 maxSurge 같은 "초과 생성"은 없다 — 한 번에 정확히 하나만.

이 점진성이 느리게 느껴질 수 있다(replicas 100이면 한 번에 하나씩). 그래서 큰 StatefulSet엔 OnDelete 전략(컨트롤러가 자동 안 바꾸고, Pod가 수동 삭제될 때만 새 버전으로)을 쓰기도 한다. 단 관리 부담이 커진다.

직접 확인하기

# Kubernetes 1.36 — StatefulSet + headless Service 한 번에 만들기
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata: {name: web}
spec: {clusterIP: None, selector: {app: web}, ports: [{port: 80}]}
---
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: web}
spec:
  serviceName: web
  replicas: 3
  selector: {matchLabels: {app: web}}
  template:
    metadata: {labels: {app: web}}
    spec:
      containers: [{name: nginx, image: nginx:1.27}]
EOF
kubectl get pods,pvc -l app=web

확인할 것: Pod 이름이 web-0,1,2(순차), PVC가 data-web-0,1,2로 각 Pod마다 하나씩.

# 특정 Pod DNS 해석
kubectl run t --image=busybox:1.36 --rm -it --restart=Never -- nslookup web-0.web
Name:    web-0.web.default.svc.cluster.local
Address: 10.244.x.x
# Pod 하나를 지우면 — 같은 이름으로 같은 PVC로 재생성
kubectl delete pod web-1
kubectl get pod web-1

확인할 것: web-1같은 이름으로 다시 생긴다. PVC data-web-1은 그대로.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"StatefulSet은 단순히 디스크가 있는 Deployment다" 아니다. 안정된 이름·DNS·순서·고정 PV 바인딩이 핵심. 디스크는 그중 하나
"Pod 이름이 안정적이라 IP도 안정적이다" IP는 바뀐다(재생성 시). 안정적인 건 이름과 그에 대한 DNS 매핑
"headless Service는 로드밸런싱을 한다" 안 한다. 각 Pod IP를 DNS로 노출. 클라이언트가 고름
"PVC는 Pod마다 자동 생긴다" StatefulSet의 volumeClaimTemplates로만. Deployment는 안 됨
"StatefulSet은 항상 순차 기동한다" 기본은 그렇지만 podManagementPolicy: Parallel이면 동시
"StatefulSet이면 무조건 상태ful 앱에 맞다" 상태ful 앱이 StatefulSet 기능(이름/순서)을 필요로 할 때만. 단순 캐시는 Deployment도 충분

요약 — 이 글의 결론

  • StatefulSet의 본질은 "안정된 정체성"이다. 이름(mysql-0)·DNS·전용 PV가 Pod가 죽었다 살아나도 변하지 않는다 — 상태ful 클러스터의 토폴로지 설정이 깨지지 않는 토대.
  • headless Service(clusterIP: None)가 각 Pod를 이름으로 직접 찌르게 한다. "아무 Pod"가 아니라 "정확히 mysql-0"로.
  • volumeClaimTemplates가 Pod마다 전용 PVC/PV를 묶어 준다. 같은 번호의 Pod는 항상 같은 PV — 데이터 영속성의 핵심.
  • 순차 생성/역순 종료가 상태ful 클러스터의 부트스트랩 순서를 보장한다. podManagementPolicy: Parallel로 끌 수도 있다.
  • Deployment와 결정적 차이: Deployment는 Pod 정체성이 무작위·휘발성. StatefulSet은 번호가 붙고 영속적. "상태"가 필요하면 StatefulSet, 무상태면 Deployment가 정답.
  • 롤아웃은 역순, 한 번에 하나. Deployment의 maxSurge 같은 초과 생성이 없다. 그래서 큰 StatefulSet은 느리다 — OnDelete로 수동 제어하기도.

생각해 볼 문제

  1. mysql-0의 IP가 바뀌어도 DNS 이름이 같은 Pod를 가리키는 메커니즘은? (headless Service와 kube-DNS의 관계)
  2. StatefulSet에서 replicas를 3→1로 줄이면 어떤 Pod가 먼저 죽나? 그때 그 Pod의 PVC는 어떻게 되나?
  3. headless Service 없이 StatefulSet을 만들면 DNS 이름이 동작하는가? serviceName의 역할은?
  4. Deployment에 PVC를 달면 왜 상태ful 앱에 부적합한가? 두 가지 이상 말하라.
  5. StatefulSet의 OrderedReady가 느린 이유를, replicas 100 클러스터 관점에서 설명하라.
  6. 일시적 네트워크 단절로 mysql-2가 잠시 안 보였다가 돌아왔다. StatefulSet은 새 Pod를 만들까, 기존 것을 둘까?

참고

'Tech Artifacts > Kubernetes' 카테고리의 다른 글

Kubernetes - 08. scheduling  (0) 2026.07.13
Kubernetes - 07. daemonset job cronjob  (0) 2026.07.13
Kubernetes - 05. deployment  (0) 2026.07.13
Kubernetes - 04. pod  (0) 2026.07.13
Kubernetes - 03. api machinery  (0) 2026.07.13