[Kubernetes] 19. 서비스와 네트워크 (5편) - Headless, ExternalName, None-Selector

JIWON·2025년 7월 20일

Kubernetes

목록 보기
19/32
post-thumbnail

특수 목적의 서비스 패턴 (Headless, ExternalName, None-selector)

앞서 다룬 ClusterIP, NodePort, LoadBalancer가 트래픽 부하 분산(Load Balancing)과 안정적인 접속을 위한 것이었다면, 이번에 다룰 서비스들은 DNS 해석(Resolution)과 엔드포인트의 유연한 관리에 특화된 서비스들이다.


1. Headless Service

1) 개념 및 특징

일반적인 서비스는 ClusterIP 라는 가상 IP(VIP)를 할당받아, 이 IP로 들어오는 요청을 여러 파드에 분산한다. 반면 Headless Service는 이름 그대로 머리(Head), 즉 서비스를 대표하는 가상 IP(ClusterIP)가 없는 서비스이다.

  • 동작 방식: 로드밸런싱을 위한 단일 IP를 제공하는 대신, DNS 쿼리 시 해당 서비스에 연결된 모든 파드(Pod)의 IP 주소 목록을 직접 반환한다.

  • 용도:

    • StatefulSet: 파드의 고유한 네트워크 식별자(Stable Network Identity)가 필요할 때 필수적으로 사용된다.

    • Client-side Load Balancing: gRPC 등에서 클라이언트가 직접 파드들의 IP 목록을 받아 스스로 로드밸런싱 로직을 수행할 때 사용한다.

2) Headless Service 생성

서비스 매니페스트(spec 필드)가 아래 두 가지 조건을 만족해야 한다.

  1. type: ClusterIP (생략 시 기본값)

  2. clusterIP: None (핵심 설정)

3) 실습: StatefulSet과 함께 사용하기

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

② DNS 동작 확인 (CoreDNS)

서비스를 생성한 후 dig 명령어로 DNS 조회를 해보면 동작 차이를 명확히 알 수 있다.

  • 서비스 이름으로 조회 (DNS Round Robin): 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
  • 파드 이름으로 직접 조회 (Stable Identity): StatefulSet과 Headless Service가 연결되면, 각 파드는 [파드명].[서비스명].[네임스페이스].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

4) (심화) 일반 파드에 고정 DNS 이름 부여

StatefulSet이 아닌 일반 DeploymentPod에서도 hostnamesubdomain 필드를 활용하면 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 로 해석 가능

DNS 동작 확인

파드는 [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 주소가 정상적으로 반환된다.


2. ExternalName Service

1) 개념 및 특징

ExternalName 서비스는 파드로 트래픽을 프록시하거나 포워딩하는 일반적인 서비스와 달리, DNS 레벨에서 CNAME(별칭)을 반환하는 서비스이다.

  • 역할: 쿠버네티스 내부 DNS에 외부 도메인을 위한 '별명'을 만들어준다.

  • 동작 원리:

    1. 파드가 ExternalName 서비스(sample-externalname.default.svc.cluster.local)에 DNS 조회를 요청한다.

    2. 쿠버네티스 내부 DNS는 CNAME 레코드로 설정된 외부 도메인(external.example.com)을 반환한다.

    3. 클라이언트는 이 CNAME 정보를 받아 실제 외부 도메인에 대한 IP 주소를 다시 질의하여 통신을 시작한다.

2) 왜 사용하는가? (Loose Coupling)

이 서비스의 핵심 가치는 애플리케이션과 외부 환경의 결합도를 낮추는 것(Decoupling)이다.

  • 문제 상황: 애플리케이션 코드에 db.prod.aws.com 같은 외부 DB 주소를 하드코딩하면, 나중에 DB 주소가 바뀔 때마다 코드를 수정하고 재배포해야 한다.

  • 해결책: 애플리케이션은 내부 서비스명(my-db)만 바라보게 한다. 실제 연결 주소는 ExternalName 서비스 설정(YAML)에서 관리한다. 나중에 연결 대상을 바꾸더라도 애플리케이션 코드는 건드릴 필요가 없다.

3) 실습: 외부 도메인 연결

spec.typeExternalName 으로 설정하고 externalName 필드에 실제 목적지 도메인을 적는다.

다음과 같이 typeExternalName 으로 지정하고, externalName 필드에 연결할 외부 도메인을 명시한다.

# sample-externalname.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-externalname
  namespace: default
spec:
  type: ExternalName
  externalName: external.example.com # 실제 연결할 외부 도메인

DNS 조회 확인

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이 반환되는 것을 확인할 수 있다.


3. None-selector Service (수동 엔드포인트)

1) 개념 및 특징

일반적인 서비스는 selector를 이용해 파드를 자동으로 찾아 연결한다. 하지만 None-selector 서비스는 selector를 정의하지 않고, 관리자가 직접 Endpoints(IP 주소 목록)를 생성하여 연결하는 방식이다.

  • 특징: ExternalName과 달리 ClusterIP(가상 IP)를 할당받는다. 즉, 내부에서는 일반 서비스처럼 IP로 접근하지만, 그 뒷단이 파드가 아닌 외부 IP(또는 다른 네임스페이스의 파드)일 수 있다.

  • 용도:

    • 클러스터 외부의 레거시 시스템(DB, 메인프레임 등)을 내부 서비스처럼 연결할 때.

    • DNS 이름이 없는(IP만 있는) 외부 시스템을 연결할 때.

    • 블루/그린 배포 테스트 시 수동으로 트래픽 대상을 제어하고 싶을 때.

2) ExternalName 서비스와의 차이점

둘 다 "외부 리소스를 내부 서비스처럼 쓴다"는 목적은 같지만 동작 방식이 다르다.

특징ExternalName ServiceNone-selector Service
동작 레벨DNS (CNAME)Proxy / IP forwarding
ClusterIP없음 (None)있음 (할당됨)
연결 대상도메인 이름 (DNS)IP 주소 (직접 지정)
포트 매핑불가능 (DNS만 반환)가능 (포트 변환 가능)

3) 실습: 수동 엔드포인트 연결

① 서비스 생성 (Selector 없음)

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

② Endpoints 생성 (수동 연결)

서비스와 동일한 이름(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)한다.

0개의 댓글