쿠버네티스 StatefulSet, Headless Service, ClusterIP 실습 가이드
이 가이드는 쿠버네티스에서 상태 유지가 필요한 애플리케이션(Stateful Application)을 배포하는 데 사용되는 StatefulSet과, 이를 지원하는 Headless Service의 개념을 실습 중심으로 설명합니다. 또한, 일반적인 ClusterIP 서비스와의 차이점을 코드를 통해 비교하여 개념을 명확히 이해할 수 있도록 돕습니다.
1. 왜 StatefulSet이 필요한가?
Deployment는 주로 상태가 없는(Stateless) 애플리케이션에 사용됩니다. 파드(Pod)가 다시 시작되면 내부 데이터가 사라지고, 파드의 이름이나 네트워크 주소(IP)도 예측 불가능하게 변경됩니다. 하지만 데이터베이스나 분산 시스템처럼 각 노드가 고유한 상태와 식별자를 가져야 하는 애플리케이션에는 적합하지 않습니다.
StatefulSet은 이러한 문제를 해결하기 위해 등장했으며, 다음과 같은 특징을 가집니다.
- 안정적이고 고유한 네트워크 식별자: 파드는
(StatefulSet 이름)-(순번)형식의 예측 가능한 호스트 이름을 갖습니다. (예:web-0,web-1) - 안정적이고 지속적인 스토리지: 각 파드는 고유한
PersistentVolumeClaim(PVC)을 통해 자신만의 영구 스토리지를 가집니다. 파드가 재시작되어도 동일한 스토리지에 다시 연결됩니다. - 순차적인 배포 및 스케일링: 파드는 정해진 순서(0, 1, 2…)에 따라 순차적으로 생성되고, 역순(…, 2, 1, 0)으로 삭제됩니다.
2. 실습 환경
이 실습은 로컬 쿠버네티스 환경인 minikube를 기준으로 설명합니다.
# minikube 시작
minikube start3. Headless Service: 파드를 위한 고유 주소 생성
StatefulSet의 안정적인 네트워크 식별자를 구현하려면 Headless Service가 필수적입니다. 일반적인 서비스(ClusterIP)와 달리, Headless Service는 가상의 단일 IP 주소를 갖지 않습니다. 대신, 서비스에 속한 각 파드의 실제 IP 주소를 가리키는 DNS 레코드를 생성합니다.
이를 통해 클라이언트는 서비스 이름 대신 개별 파드의 고유한 주소(web-0.nginx, web-1.nginx)로 직접 통신할 수 있습니다.
Headless Service YAML 작성
headless-service.yaml 파일을 생성합니다.
apiVersion: v1
kind: Service
metadata:
name: nginx # StatefulSet이 이 서비스를 참조할 이름
spec:
ports:
- port: 80
name: web
clusterIP: None # 이 부분이 Headless Service의 핵심입니다.
selector:
app: nginx # StatefulSet의 파드 라벨과 일치해야 합니다.clusterIP: None: 이 설정을 통해 쿠버네티스는 이 서비스에 가상 IP를 할당하지 않습니다.
서비스 생성 및 확인
# Headless Service 생성
kubectl apply -f headless-service.yaml
# 서비스 목록 확인
kubectl get svc nginx실행 결과:
CLUSTER-IP가 일반적인 IP 주소가 아닌 None으로 표시되는 것을 확인할 수 있습니다.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 5s
4. StatefulSet: 상태를 갖는 파드 배포
이제 위에서 만든 Headless Service를 사용하는 StatefulSet을 배포해 보겠습니다. 이 예제에서는 간단한 Nginx 웹서버를 사용합니다.
StatefulSet YAML 작성
statefulset.yaml 파일을 생성합니다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx" # 사용할 Headless Service 이름
replicas: 2 # 파드를 2개 생성
selector:
matchLabels:
app: nginx # spec.template.metadata.labels와 일치해야 함
template:
metadata:
labels:
app: nginx # Headless Service의 selector와 일치해야 함
spec:
containers:
- name: nginx
image: nginx:1.19.0
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html # 볼륨을 마운트할 경로
volumeClaimTemplates: # 각 파드를 위한 PVC를 자동으로 생성하는 템플릿
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi # 각 파드마다 1GB의 스토리지를 요청serviceName: "nginx": 이StatefulSet이nginx라는 이름의Headless Service에 속한다는 것을 명시합니다.volumeClaimTemplates: 이 부분이StatefulSet의 핵심 기능 중 하나입니다.replicas수만큼PersistentVolumeClaim(PVC)을 자동으로 생성합니다.web-0파드는www-web-0이라는 PVC를,web-1파드는www-web-1이라는 PVC를 갖게 됩니다.
StatefulSet 생성 및 확인
# StatefulSet 생성
kubectl apply -f statefulset.yaml
# 파드 생성 과정 확인 (순차적으로 생성되는 것을 볼 수 있음)
kubectl get pods -w실행 결과:
web-0이 먼저 생성되고 Running 상태가 된 후에 web-1이 생성되는 것을 확인할 수 있습니다.
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 3s
web-0 1/1 Running 0 5s
web-1 0/1 ContainerCreating 0 5s
web-1 1/1 Running 0 7s
5. 네트워크 식별자 및 데이터 영속성 검증
고유 호스트 이름 확인
각 파드는 예측 가능한 고유한 호스트 이름을 갖습니다. kubectl exec를 통해 확인해 봅시다.
# web-0 파드의 호스트 이름 확인
kubectl exec web-0 -- hostname
# 출력: web-0
# web-1 파드의 호스트 이름 확인
kubectl exec web-1 -- hostname
# 출력: web-1DNS를 통한 파드 조회 (Headless Service의 역할)
Headless Service는 각 파드에 대해 (파드이름).(서비스이름).(네임스페이스).svc.cluster.local 형식의 DNS A 레코드를 생성합니다. busybox 같은 유틸리티 파드를 실행하여 nslookup으로 이를 확인할 수 있습니다.
# nslookup을 위한 임시 파드 실행
kubectl run -it --rm --image=busybox:1.28 dns-test -- /bin/sh
# 임시 파드 셸에서 아래 명령어 실행
# (서비스 이름으로 조회 시, 각 파드의 IP 목록이 반환됨)
nslookup nginx
# (개별 파드 주소로 조회 시, 해당 파드의 IP가 반환됨)
nslookup web-0.nginx
nslookup web-1.nginx데이터 영속성 테스트
StatefulSet의 가장 중요한 기능인 데이터 영속성을 테스트해 보겠습니다. web-0 파드에 파일을 생성한 후, 파드를 삭제했다가 다시 생성되었을 때 파일이 남아있는지 확인합니다.
-
web-0파드에 데이터 쓰기kubectl exec web-0 -- /bin/sh -c 'echo "Hello from web-0" > /usr/share/nginx/html/index.html' -
web-0파드 삭제StatefulSet컨트롤러가 파드가 삭제된 것을 감지하고 즉시 동일한 이름(web-0)과 스토리지(www-web-0)를 가진 새 파드를 생성합니다.kubectl delete pod web-0 -
새로 생성된
web-0파드에서 데이터 확인 잠시 후 새web-0파드가Running상태가 되면 아래 명령어를 실행합니다.kubectl exec web-0 -- /bin/sh -c 'cat /usr/share/nginx/html/index.html'
실행 결과:
“Hello from web-0” 메시지가 그대로 출력되는 것을 볼 수 있습니다. 이는 파드가 삭제되어도 PersistentVolume에 저장된 데이터는 보존됨을 의미합니다.
6. 비교: Deployment와 ClusterIP 서비스
StatefulSet과 비교하기 위해 일반적인 Deployment와 ClusterIP 서비스 조합을 살펴보겠습니다.
Deployment 및 ClusterIP 서비스 YAML
deployment-service.yaml 파일을 생성합니다.
apiVersion: v1
kind: Service
metadata:
name: my-nginx-svc
spec:
selector:
app: my-nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
# clusterIP를 명시하지 않으면 기본값인 'ClusterIP' 타입으로 생성됨
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: my-nginx
template:
metadata:
labels:
app: my-nginx
spec:
containers:
- name: nginx
image: nginx:1.19.0
ports:
- containerPort: 80생성 및 확인
kubectl apply -f deployment-service.yaml
# 서비스 확인
kubectl get svc my-nginx-svc
# 출력: CLUSTER-IP에 가상 IP가 할당됨
# 파드 확인
kubectl get pods -l app=my-nginx
# 출력: 파드 이름이 랜덤한 해시값으로 생성됨 (예: my-nginx-deployment-6b...-abcde)ClusterIP 서비스는 단일 가상 IP를 통해 들어온 요청을 파드들에게 로드 밸런싱합니다. 개별 파드에 직접 접근할 수 있는 안정적인 주소를 제공하지 않으며, 파드 자체도 고유한 식별자나 영구 스토리지를 갖지 않습니다.
7. 정리
-
StatefulSet + Headless Service:
- 언제: 데이터베이스, 메시지 큐, 분산 파일 시스템 등 각 노드가 고유한 상태와 식별자를 가져야 할 때 사용합니다.
- 동작: 예측 가능한 파드 이름(
web-0), 고유한 DNS 주소(web-0.nginx), 그리고 파드와 1:1로 매핑되는 영구 스토리지(www-web-0)를 제공합니다. - 네트워크:
Headless Service는 파드에 직접 접근할 수 있는 DNS 레코드를 제공합니다.
-
Deployment + ClusterIP Service:
- 언제: 웹서버, API 게이트웨이 등 상태가 없고 쉽게 복제하여 수평 확장이 가능한(Stateless) 애플리케이션에 사용합니다.
- 동작: 파드는 언제든지 교체될 수 있는 소모품으로 취급됩니다. 파드 이름과 IP는 동적으로 할당됩니다.
- 네트워크:
ClusterIP서비스는 단일 진입점(가상 IP)을 통해 요청을 여러 파드에 분산(로드 밸런싱)합니다.