미니 프로젝트: Headless Service와 StatefulSet (MySQL 스타일 동작)
Source: Dev.to
🎯 목표
끝까지 보면 분명히 알 수 있습니다:
- ClusterIP가 pod ID를 숨기는 이유
- Headless Service가 pod ID를 노출하는 이유
- StatefulSet + Headless Service가 pod당 안정적인 DNS를 제공하는 방법 (데이터베이스에 필요한 마법)
🧠 정신 모델 (염두에 두세요)
| Setup | DNS 결과 |
|---|---|
| Deployment + ClusterIP | 하나의 가상 IP |
| Deployment + Headless | 여러 파드 IP |
| StatefulSet + Headless | 안정적인 파드 DNS 이름 (마법) |
🧩 프로젝트 구조
headless-demo/
├── mysql-headless.yaml
├── mysql-statefulset.yaml
└── dns-test.yaml
1️⃣ Headless Service (NO ClusterIP)
File: mysql-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None # 👈 THIS MAKES IT HEADLESS
selector:
app: mysql
ports:
- port: 3306
중요:
- 가상 IP가 생성되지 않습니다.
- DNS가 파드 IP들을 반환합니다.
2️⃣ StatefulSet (stable pod identity)
File: mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless # 👈 REQUIRED
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: root
ports:
- containerPort: 3306
StatefulSet이 보장하는 것
mysql-0
mysql-1
mysql-2
- 이름은 절대 변경되지 않음
- 정체성은 안정적
3️⃣ DNS 테스트 포드 (동작 관찰용)
파일: dns-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: dns-test
spec:
containers:
- name: dns
image: busybox:1.28
command: ["sleep", "3600"]
4️⃣ 모든 것을 적용하기 (순서가 중요합니다)
kubectl apply -f mysql-headless.yaml
kubectl apply -f mysql-statefulset.yaml
kubectl apply -f dns-test.yaml
파드가 준비될 때까지 기다립니다:
kubectl get pods
다음과 같이 표시됩니다:
mysql-0 Running
mysql-1 Running
mysql-2 Running
dns-test Running
5️⃣ 🔥 가장 중요한 부분 — DNS 동작
테스트 파드 진입
kubectl exec -it dns-test -- sh
headless 서비스의 DNS 조회
nslookup mysql-headless
결과 – 파드당 하나씩 여러 IP
Address: 10.244.0.12
Address: 10.244.0.13
Address: 10.244.0.14
이것은 DNS 라운드‑로빈입니다.
개별 파드의 DNS 조회 (핵심)
nslookup mysql-0.mysql-headless
nslookup mysql-1.mysql-headless
nslookup mysql-2.mysql-headless
각 명령은 특정 파드 IP로 해석됩니다 – 일반 ClusterIP 서비스가 절대 할 수 없는 일입니다.
6️⃣ 데이터베이스가 필요한 이유
| Role | DNS name |
|---|---|
| Primary DB | mysql-0.mysql-headless |
| Replica 1 | mysql-1.mysql-headless |
| Replica 2 | mysql-2.mysql-headless |
- Writes →
mysql-0.mysql-headless(안정적인 프라이머리) - Reads → 레플리카들
- Replication → 안정적인 대상
불가능 Deployment + ClusterIP와 함께 사용할 경우 서비스가 파드 아이덴티티를 숨기기 때문입니다.
7️⃣ 시각적 직관 (무슨 일이 일어나고 있나요)
| Headless Service + StatefulSet | ClusterIP Service |
|---|---|
![]() | ![]() |
8️⃣ One‑line interview answer (remember this)
“헤드리스 서비스는 가상 IP를 제거하고 DNS를 통해 파드 ID를 노출합니다. StatefulSet과 결합하면 데이터베이스 및 리더‑팔로워 아키텍처에 필요한 파드당 안정적인 DNS 이름을 제공합니다.”
🔥 동일한 앱, 두 서비스
🟦 사례 1 — ClusterIP 서비스 (기본 동작)
YAML (일반 서비스)
apiVersion: v1
kind: Service
metadata:
name: mysql-clusterip
spec:
selector:
app: mysql
ports:
- port: 3306
Kubernetes가 수행하는 작업
- 하나의 가상 IP 생성
- 모든 pod IP 숨김
kube-proxy가 트래픽을 로드밸런싱
클러스터 내부 DNS 동작
nslookup mysql-clusterip
Name: mysql-clusterip
Address: 10.96.120.15 mysql-0
+--> mysql-1
+--> mysql-2
Kubernetes가 각 요청을 어느 pod에 전달할지 결정합니다.
❌ 데이터베이스가 깨지는 이유
쓰기 작업이 어떤 복제본(예: mysql-2)에 도착하면, 기본(primary)에서는 이를 확인하지 못해 일관성과 복제가 깨집니다. 안정적인 pod DNS를 가진 헤드리스 서비스는 이 문제를 피할 수 있습니다.
❌ 문제: 로그인 실패
- Login request → mysql-0 (no data)
- ❌ login fails
왜?
쓰기와 읽기 트래픽이 다른 파드에 도달합니다. 파드 정체성이 숨겨져 있어 트래픽을 항상 mysql-0으로 강제할 수 없습니다.
ClusterIP는 무상태 친화적이지만 상태 저장에 적대적입니다.
🟨 CASE 2 — Headless Service (NO CLUSTER IP)
우리는 Service 정의에서 한 줄만 변경합니다.
YAML (headless service)
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None # 👈 THIS IS EVERYTHING
selector:
app: mysql
ports:
- port: 3306
🔍 DNS behavior (key difference)
포드 내부에서:
nslookup mysql-headless
Result
Address: 10.244.0.10 (mysql-0)
Address: 10.244.0.11 (mysql-1)
Address: 10.244.0.12 (mysql-2)
- ⚠️ 가상 IP가 없습니다.
- ⚠️ DNS가 포드 IP를 직접 반환합니다 – DNS 라운드‑로빈이며, kube‑proxy 로드 밸런싱이 아닙니다.
🧠 Still not enough alone (important)
If you stop here and use just mysql-headless, your app may still hit different pods because DNS answers rotate.
Headless alone is not the full solution.
🟩 STATEFULSET을 도입하세요 (빠진 조각)
StatefulSet은 안정적인 pod 이름을 보장합니다:
mysql-0
mysql-1
mysql-2
Kubernetes는 각 pod에 대해 DNS 레코드를 자동으로 생성합니다:
mysql-0.mysql-headless
mysql-1.mysql-headless
mysql-2.mysql-headless
🔥 바로 이해되는 순간
쓰기 트래픽
mysql-0.mysql-headless
읽기 트래픽
mysql-1.mysql-headless
mysql-2.mysql-headless
추측이 없습니다. 무작위도 없습니다.
📊 SIDE‑BY‑SIDE SUMMARY (MEMORIZE THIS)
| 기능 | ClusterIP | Headless |
|---|---|---|
| 가상 IP 보유 | ✅ | ❌ |
| 파드 정체성 숨김 | ✅ | ❌ |
| DNS 반환 | 1 IP | 다수의 파드 IP |
| 파드 전용 DNS | ❌ | ✅ |
| 웹 애플리케이션에 적합 | ✅ | ❌ |
| 데이터베이스에 적합 | ❌ | ✅ (StatefulSet와 함께) |
🧪 왜 dns-test pod를 사용했는가
노트북에서 “DNS를 볼” 수 없습니다.
pod를 생성해서 다음을 실행합니다:
nslookup
이것이 SRE가 실제 프로덕션 이슈를 디버깅하는 방법입니다.
🎯 한 줄 인터뷰 답변 (중요)
“ClusterIP 서비스는 파드의 정체성을 숨기고 트래픽을 로드밸런싱하므로 데이터베이스에 적합하지 않습니다.
Headless 서비스는 DNS를 통해 파드 IP를 노출하고, StatefulSet과 결합될 때 상태 저장 워크로드에 필요한 안정적인 파드별 DNS를 제공합니다.”



