미니 프로젝트: Headless Service와 StatefulSet (MySQL 스타일 동작)

발행: (2026년 1월 16일 오전 08:23 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

🎯 목표

끝까지 보면 분명히 알 수 있습니다:

  • ClusterIP가 pod ID를 숨기는 이유
  • Headless Service가 pod ID를 노출하는 이유
  • StatefulSet + Headless Servicepod당 안정적인 DNS를 제공하는 방법 (데이터베이스에 필요한 마법)

🧠 정신 모델 (염두에 두세요)

SetupDNS 결과
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️⃣ 데이터베이스가 필요한 이유

RoleDNS name
Primary DBmysql-0.mysql-headless
Replica 1mysql-1.mysql-headless
Replica 2mysql-2.mysql-headless
  • Writesmysql-0.mysql-headless (안정적인 프라이머리)
  • Reads → 레플리카들
  • Replication → 안정적인 대상

불가능 Deployment + ClusterIP와 함께 사용할 경우 서비스가 파드 아이덴티티를 숨기기 때문입니다.

7️⃣ 시각적 직관 (무슨 일이 일어나고 있나요)

Headless Service + StatefulSetClusterIP Service
헤드리스 다이어그램ClusterIP 다이어그램

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)

기능ClusterIPHeadless
가상 IP 보유
파드 정체성 숨김
DNS 반환1 IP다수의 파드 IP
파드 전용 DNS
웹 애플리케이션에 적합
데이터베이스에 적합✅ (StatefulSet와 함께)

🧪 왜 dns-test pod를 사용했는가

노트북에서 “DNS를 볼” 수 없습니다.
pod를 생성해서 다음을 실행합니다:

nslookup 

이것이 SRE가 실제 프로덕션 이슈를 디버깅하는 방법입니다.

🎯 한 줄 인터뷰 답변 (중요)

“ClusterIP 서비스는 파드의 정체성을 숨기고 트래픽을 로드밸런싱하므로 데이터베이스에 적합하지 않습니다.
Headless 서비스는 DNS를 통해 파드 IP를 노출하고, StatefulSet과 결합될 때 상태 저장 워크로드에 필요한 안정적인 파드별 DNS를 제공합니다.”

이미지

이미지 1

이미지 2

Back to Blog

관련 글

더 보기 »

StatefulSet 프로젝트

전제 조건 StatefulSet은 다음 구성 요소가 필요합니다: - Headless Service – 각 pod에 대해 안정적인 DNS를 제공합니다. - StatefulSet manifest – pod들을 정의합니다.

Helm이란?

Helm이란 Wordpress와 같은 애플리케이션으로, 프런트엔드에 Wordpress 컨테이너가 필요하고 백엔드에 MySQL 데이터베이스가 필요합니다. 이러한 구성 요소를 수동으로 Deployme...