
앞서 다룬 ClusterIP, NodePort, LoadBalancer가 트래픽 부하 분산(Load Balancing)과 안정적인 접속을 위한 것이었다면, 이번에 다룰 서비스들은 DNS 해석(Resolution)과 엔드포인트의 유연한 관리에 특화된 서비스들이다.
일반적인 서비스는 ClusterIP 라는 가상 IP(VIP)를 할당받아, 이 IP로 들어오는 요청을 여러 파드에 분산한다. 반면 Headless Service는 이름 그대로 머리(Head), 즉 서비스를 대표하는 가상 IP(ClusterIP)가 없는 서비스이다.
동작 방식: 로드밸런싱을 위한 단일 IP를 제공하는 대신, DNS 쿼리 시 해당 서비스에 연결된 모든 파드(Pod)의 IP 주소 목록을 직접 반환한다.
용도:
StatefulSet: 파드의 고유한 네트워크 식별자(Stable Network Identity)가 필요할 때 필수적으로 사용된다.
Client-side Load Balancing: gRPC 등에서 클라이언트가 직접 파드들의 IP 목록을 받아 스스로 로드밸런싱 로직을 수행할 때 사용한다.
서비스 매니페스트(spec 필드)가 아래 두 가지 조건을 만족해야 한다.
type: ClusterIP (생략 시 기본값)
clusterIP: None (핵심 설정)
StatefulSet은 파드마다 고유한 순서와 식별자(0, 1, 2...)를 가지며, 이를 네트워크상에서 식별하기 위해 Headless Service가 필요하다.
# sample-statefulset-headless.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sample-statefulset-headless
spec:
serviceName: "sample-headless" # 연결할 Headless Service 이름 명시
replicas: 3
selector:
matchLabels:
app: sample-app1
template:
metadata:
labels:
app: sample-app1
spec:
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
# sample-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-headless
spec:
type: ClusterIP
clusterIP: None # Headless Service로 정의
selector:
app: sample-app1 # StatefulSet의 파드를 선택
ports:
- name: http-port
protocol: TCP
port: 8080
targetPort: 80
서비스를 생성한 후 dig 명령어로 DNS 조회를 해보면 동작 차이를 명확히 알 수 있다.
ClusterIP 가 없으므로, 연결된 파드들의 IP 주소 목록(A 레코드)이 그대로 반환된다. 클라이언트는 이 중 하나를 선택하여 통신한다.# sample-pod 에 접속
jiwon@master:~/kubectl$ kubectl exec -it sample-pod -- /bin/bash
# sample-pod에서 실행
root@sample-pod:/# dig sample-headless.default.svc.cluster.local
;; ANSWER SECTION:
sample-headless.default.svc.cluster.local. 30 IN A 10.244.1.88
sample-headless.default.svc.cluster.local. 30 IN A 10.244.2.121
sample-headless.default.svc.cluster.local. 30 IN A 10.244.1.89
[파드명].[서비스명].[네임스페이스].svc.cluster.local 형태의 도메인이 생성되어 특정 파드(예: 0번 파드)를 콕 집어 통신할 수 있다. 이는 DB의 마스터-슬레이브 복제 구성 등에 매우 유용하다.# sample-statefulset-headless-0 파드의 IP를 직접 조회
root@sample-pod:/# dig sample-statefulset-headless-0.sample-headless.default.svc.cluster.local
;; ANSWER SECTION:
sample-statefulset-headless-0.sample-headless.default.svc.cluster.local. 30 IN A 10.244.1.88
StatefulSet이 아닌 일반 Deployment나 Pod에서도 hostname과 subdomain 필드를 활용하면 Headless Service를 통해 고유한 도메인을 가질 수 있다.
조건: 파드의 spec.subdomain과 서비스의 metadata.name이 일치해야 하며, 서비스는 Headless(clusterIP: None)여야 한다.
결과: [hostname].[subdomain].[namespace].svc.cluster.local 주소로 파드에 직접 접근 가능하다.
# sample-subdomain.yaml
apiVersion: v1
kind: Pod
metadata:
name: sample-subdomain
labels:
app: sample-app2
spec:
hostname: sample-hostname # 파드의 호스트명 설정
subdomain: sample-subdomain # 연결할 Headless Service 이름
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
---
apiVersion: v1
kind: Service
metadata:
name: sample-subdomain # 파드의 subdomain과 이름이 같아야 함
spec:
type: ClusterIP
clusterIP: None
selector:
app: sample-app2
ports:
- name: http
port: 80
[hostname].[subdomain].[namespace].svc.cluster.local 로 해석 가능파드는 [hostname].[subdomain].[namespace].svc.cluster.local 형태의 DNS 주소를 갖게 된다.
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-hostname.sample-subdomain.default.svc.cluster.local
;; ANSWER SECTION:
sample-hostname.sample-subdomain.default.svc.cluster.local. 30 IN A [파드의-IP-주소]
위와 같이 조회하면 sample-subdomain 파드의 IP 주소가 정상적으로 반환된다.
ExternalName 서비스는 파드로 트래픽을 프록시하거나 포워딩하는 일반적인 서비스와 달리, DNS 레벨에서 CNAME(별칭)을 반환하는 서비스이다.
역할: 쿠버네티스 내부 DNS에 외부 도메인을 위한 '별명'을 만들어준다.
동작 원리:
파드가 ExternalName 서비스(sample-externalname.default.svc.cluster.local)에 DNS 조회를 요청한다.
쿠버네티스 내부 DNS는 CNAME 레코드로 설정된 외부 도메인(external.example.com)을 반환한다.
클라이언트는 이 CNAME 정보를 받아 실제 외부 도메인에 대한 IP 주소를 다시 질의하여 통신을 시작한다.

이 서비스의 핵심 가치는 애플리케이션과 외부 환경의 결합도를 낮추는 것(Decoupling)이다.
문제 상황: 애플리케이션 코드에 db.prod.aws.com 같은 외부 DB 주소를 하드코딩하면, 나중에 DB 주소가 바뀔 때마다 코드를 수정하고 재배포해야 한다.
해결책: 애플리케이션은 내부 서비스명(my-db)만 바라보게 한다. 실제 연결 주소는 ExternalName 서비스 설정(YAML)에서 관리한다. 나중에 연결 대상을 바꾸더라도 애플리케이션 코드는 건드릴 필요가 없다.

spec.type을 ExternalName 으로 설정하고 externalName 필드에 실제 목적지 도메인을 적는다.
다음과 같이 type 을 ExternalName 으로 지정하고, externalName 필드에 연결할 외부 도메인을 명시한다.
# sample-externalname.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-externalname
namespace: default
spec:
type: ExternalName
externalName: external.example.com # 실제 연결할 외부 도메인
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod -- dig sample-externalname.default.svc.cluster.local CNAME
# 실행결과
;; ANSWER SECTION:
sample-externalname.default.svc.cluster.local. 30 IN CNAME external.example.com.
서비스를 조회했더니 external.example.com 이라는 CNAME이 반환되는 것을 확인할 수 있다.
일반적인 서비스는 selector를 이용해 파드를 자동으로 찾아 연결한다. 하지만 None-selector 서비스는 selector를 정의하지 않고, 관리자가 직접 Endpoints(IP 주소 목록)를 생성하여 연결하는 방식이다.
특징: ExternalName과 달리 ClusterIP(가상 IP)를 할당받는다. 즉, 내부에서는 일반 서비스처럼 IP로 접근하지만, 그 뒷단이 파드가 아닌 외부 IP(또는 다른 네임스페이스의 파드)일 수 있다.
용도:
클러스터 외부의 레거시 시스템(DB, 메인프레임 등)을 내부 서비스처럼 연결할 때.
DNS 이름이 없는(IP만 있는) 외부 시스템을 연결할 때.
블루/그린 배포 테스트 시 수동으로 트래픽 대상을 제어하고 싶을 때.
둘 다 "외부 리소스를 내부 서비스처럼 쓴다"는 목적은 같지만 동작 방식이 다르다.
| 특징 | ExternalName Service | None-selector Service |
|---|---|---|
| 동작 레벨 | DNS (CNAME) | Proxy / IP forwarding |
| ClusterIP | 없음 (None) | 있음 (할당됨) |
| 연결 대상 | 도메인 이름 (DNS) | IP 주소 (직접 지정) |
| 포트 매핑 | 불가능 (DNS만 반환) | 가능 (포트 변환 가능) |
selector 필드를 아예 생략한다.
# sample-none-selector.yaml
apiVersion: v1
kind: Service
metadata:
# Endpoints 리소스와 이름이 같아야 한다.
name: sample-none-selector
spec:
type: ClusterIP
# selector 필드를 정의하지 않는다.
ports:
- protocol: TCP
port: 8080
targetPort: 80
서비스와 동일한 이름(metadata.name)으로 Endpoints 리소스를 생성해야 자동으로 연결된다.
apiVersion: v1
kind: Endpoints
metadata:
# Service 리소스와 이름이 같아야 한다.
name: sample-none-selector
subsets:
- addresses:
- ip: 192.168.1.1 # 트래픽을 보낼 첫 번째 IP
- ip: 192.168.1.2 # 트래픽을 보낼 두 번째 IP
ports:
- protocol: TCP
port: 80
# 리소스 생성
kubectl apply -f sample-none-selector.yaml
# 서비스 상세 정보 확인
kubectl describe service sample-none-selector
Name: sample-none-selector
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.103.242.125
IPs: 10.103.242.125
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 192.168.1.1:80,192.168.1.2:80
Session Affinity: None
Events: <none>
이제 클러스터 내부에서 10.103.242.125:8080 (ClusterIP)으로 요청을 보내면, 쿠버네티스는 이를 192.168.1.10:80 등의 외부 IP로 전달(Proxy)한다.