본문 바로가기
DevOps/Kubernates

[k8s] Kubernetes 서비스 타입, Headless Service?

by BenKangKang 2025. 7. 11.

들어가며

Kubernetes에서 Pod는 임시적인 특성을 가지고 있어 언제든지 생성되거나 삭제될 수 있습니다. 하지만 애플리케이션을 실제 운영 환경에서 사용하려면 안정적인 네트워크 엔드포인트가 필요합니다. 이때 Kubernetes Service가 핵심 역할을 담당합니다.

Service는 Pod 집합에 대한 네트워크 접근을 추상화하고 안정적인 엔드포인트를 제공합니다. 하지만 다양한 사용 사례에 따라 여러 종류의 Service 타입이 존재합니다. 이번 포스트에서는 각 Service 타입의 특징과 언제 사용해야 하는지 자세히 알아보겠습니다.

1. ClusterIP (기본 타입)

특징

  • Kubernetes 클러스터 내부에서만 접근 가능한 가상 IP 제공
  • 기본 Service 타입으로 별도 지정하지 않으면 자동으로 ClusterIP가 설정됨
  • 클러스터 내부 통신용으로 사용

사용 사례

  • 마이크로서비스 간 내부 통신
  • 데이터베이스 서비스와 애플리케이션 간 연결
  • 외부 노출이 필요 없는 백엔드 서비스

예시 YAML

apiVersion: v1
kind: Service
metadata:
  name: my-backend-service
spec:
  type: ClusterIP  # 생략 가능 (기본값)
  selector:
    app: backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

접근 방법

# 클러스터 내부에서만 접근 가능
curl http://my-backend-service.default.svc.cluster.local

2. NodePort

특징

  • 모든 노드의 특정 포트를 통해 외부에서 접근 가능
  • 기본 포트 범위: 30000-32767
  • 각 노드의 IP:NodePort로 서비스 접근 가능

사용 사례

  • 개발 환경에서 간단한 외부 접근
  • 로드 밸런서 없이 외부 노출이 필요한 경우
  • 특정 노드를 통한 직접 접근이 필요한 경우

예시 YAML

apiVersion: v1
kind: Service
metadata:
  name: my-nodeport-service
spec:
  type: NodePort
  selector:
    app: webapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
      nodePort: 30080  # 생략 시 자동 할당

접근 방법

# 모든 노드의 IP를 통해 접근 가능
curl http://NODE_IP:30080

3. LoadBalancer

특징

  • 클라우드 제공업체의 로드 밸런서를 자동으로 프로비저닝
  • 외부 IP를 통해 서비스에 접근 가능
  • 클라우드 환경에서만 완전히 지원 (AWS, GCP, Azure 등)

사용 사례

  • 프로덕션 환경에서 외부 사용자 접근
  • 고가용성이 필요한 웹 애플리케이션
  • SSL 종료가 필요한 서비스

예시 YAML

apiVersion: v1
kind: Service
metadata:
  name: my-loadbalancer-service
spec:
  type: LoadBalancer
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  loadBalancerSourceRanges:
    - "10.0.0.0/8"  # 접근 허용 IP 범위 제한

접근 방법

# 외부 IP를 통해 접근
curl http://EXTERNAL_IP

4. ExternalName

특징

  • 외부 서비스를 클러스터 내부에서 참조할 수 있게 해주는 DNS 별칭
  • 실제 Pod를 가리키지 않음
  • CNAME 레코드를 통해 외부 서비스로 리다이렉션

사용 사례

  • 외부 데이터베이스 서비스 참조
  • 서드파티 API 엔드포인트 추상화
  • 레거시 시스템과의 통합

예시 YAML

apiVersion: v1
kind: Service
metadata:
  name: external-database
spec:
  type: ExternalName
  externalName: database.example.com
  ports:
    - protocol: TCP
      port: 5432

접근 방법

# 클러스터 내부에서 외부 서비스 이름으로 접근
mysql -h external-database.default.svc.cluster.local -u user -p

5. Headless Service (헤드리스 서비스)

특징

  • clusterIP: None으로 설정하여 가상 IP를 할당받지 않음
  • DNS 쿼리 시 개별 Pod IP들을 직접 반환
  • 로드 밸런싱을 하지 않고 클라이언트가 직접 Pod 선택

왜 헤드리스 서비스를 사용하는가?

문제 상황: MySQL 마스터-슬레이브 구조

일반 서비스를 사용하면:
[애플리케이션] → [Service] → [랜덤하게 Pod 선택]
                             ↓
                    [Master Pod] 또는 [Slave Pod]

문제: 쓰기 작업이 Slave Pod로 갈 수 있음 → 데이터 불일치!

해결책: 헤드리스 서비스 사용

헤드리스 서비스를 사용하면:
[애플리케이션] → DNS 쿼리 → 모든 Pod IP 반환
                ↓
            직접 선택
                ↓
    [Master Pod] (쓰기용) / [Slave Pod] (읽기용)

구체적인 예시

1. 데이터베이스 클러스터

apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
    - port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  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: "password"

애플리케이션에서 사용하는 방법

# Python 예시
import socket

def get_mysql_pods():
    # DNS 쿼리로 모든 Pod IP 반환
    pod_ips = socket.getaddrinfo('mysql-headless.default.svc.cluster.local', 3306)
    return [ip[4][0] for ip in pod_ips]

def connect_to_database():
    pod_ips = get_mysql_pods()
    
    # 첫 번째 Pod는 마스터 (쓰기용)
    master_ip = pod_ips[0]
    
    # 나머지 Pod들은 슬레이브 (읽기용)
    slave_ips = pod_ips[1:]
    
    # 쓰기 작업
    write_connection = mysql.connect(host=master_ip, user='root', password='password')
    
    # 읽기 작업 (로드 밸런싱)
    read_connection = mysql.connect(host=random.choice(slave_ips), user='root', password='password')

2. Kafka 클러스터

apiVersion: v1
kind: Service
metadata:
  name: kafka-headless
spec:
  clusterIP: None
  selector:
    app: kafka
  ports:
    - port: 9092
// Java 예시 - Kafka Producer
Properties props = new Properties();
// 헤드리스 서비스로 모든 브로커 발견
props.put("bootstrap.servers", "kafka-0.kafka-headless:9092,kafka-1.kafka-headless:9092,kafka-2.kafka-headless:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

Producer<String, String> producer = new KafkaProducer<>(props);

일반 서비스와 비교

일반 서비스 (ClusterIP)

# DNS 쿼리 결과
$ nslookup mysql-service.default.svc.cluster.local
Address: 10.96.0.100  # 서비스 IP (가상 IP)

# 문제: 항상 랜덤하게 Pod 선택됨

헤드리스 서비스

# DNS 쿼리 결과
$ nslookup mysql-headless.default.svc.cluster.local
Address: 10.244.1.10  # mysql-0 Pod IP
Address: 10.244.1.11  # mysql-1 Pod IP  
Address: 10.244.1.12  # mysql-2 Pod IP

# 장점: 애플리케이션이 직접 Pod 선택 가능

사용 사례

  • 데이터베이스 클러스터: 마스터/슬레이브 구분이 필요한 경우
  • 분산 시스템: Kafka, Elasticsearch, Redis Cluster 등
  • 게임 서버: 각 방(Pod)에 직접 연결이 필요한 경우
  • 커스텀 로드 밸런싱: 애플리케이션 레벨에서 로드 밸런싱 제어

실제 사용 시나리오

1. 3-Tier 웹 애플리케이션 구조

SSR 프론트엔드 (Next.js, Nuxt.js 등)

# Frontend (LoadBalancer) - 서버에서 실행
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: LoadBalancer
  selector:
    tier: frontend
  ports:
    - port: 80
      targetPort: 3000

---
# Backend API (ClusterIP) - 프론트엔드에서 직접 접근 가능
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP
  selector:
    tier: backend
  ports:
    - port: 8080
      targetPort: 8080

---
# Database (ClusterIP)
apiVersion: v1
kind: Service
metadata:
  name: database-service
spec:
  type: ClusterIP
  selector:
    tier: database
  ports:
    - port: 5432
      targetPort: 5432

CSR 프론트엔드 (React SPA, Vue.js SPA 등)

# Frontend (LoadBalancer) - 정적 파일 서빙
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: LoadBalancer
  selector:
    tier: frontend
  ports:
    - port: 80
      targetPort: 80

---
# Backend API (LoadBalancer) - 브라우저에서 직접 접근 필요
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: LoadBalancer
  selector:
    tier: backend
  ports:
    - port: 80
      targetPort: 8080

---
# Database (ClusterIP)
apiVersion: v1
kind: Service
metadata:
  name: database-service
spec:
  type: ClusterIP
  selector:
    tier: database
  ports:
    - port: 5432
      targetPort: 5432

2. 마이크로서비스 아키텍처

# API Gateway (LoadBalancer)
apiVersion: v1
kind: Service
metadata:
  name: api-gateway
spec:
  type: LoadBalancer
  selector:
    app: gateway
  ports:
    - port: 80
      targetPort: 8080

---
# User Service (ClusterIP)
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  type: ClusterIP
  selector:
    app: user-service
  ports:
    - port: 8080
      targetPort: 8080

---
# Order Service (ClusterIP)
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  type: ClusterIP
  selector:
    app: order-service
  ports:
    - port: 8080
      targetPort: 8080

주의사항 및 팁

1. 보안 고려사항

  • NodePort 사용 시 방화벽 설정 확인
  • LoadBalancer 사용 시 sourceRanges로 접근 제한
  • 민감한 서비스는 ClusterIP 사용 권장

2. 성능 최적화

  • 불필요한 외부 노출 방지
  • 적절한 Service 타입 선택으로 네트워크 홉 최소화
  • sessionAffinity 설정 고려

3. 디버깅 팁

# Service 상태 확인
kubectl get svc

# Service 상세 정보 확인
kubectl describe svc my-service

# 엔드포인트 확인
kubectl get endpoints my-service

# DNS 해상도 테스트
nslookup my-service.default.svc.cluster.local

결론

Kubernetes Service의 다섯 가지 타입은 각각 고유한 용도와 특성을 가지고 있습니다:

  • ClusterIP: 클러스터 내부 통신용
  • NodePort: 간단한 외부 접근용
  • LoadBalancer: 프로덕션 환경의 외부 접근용
  • ExternalName: 외부 서비스 참조용
  • Headless Service: StatefulSet과 함께 사용하여 개별 Pod 접근용

적절한 Service 타입을 선택하는 것은 애플리케이션의 보안, 성능, 관리 편의성에 직접적인 영향을 미칩니다. 각 서비스의 특성을 정확히 이해하고 사용 사례에 맞는 타입을 선택하여 안정적이고 효율적인 Kubernetes 애플리케이션을 구축하시기 바랍니다.


참고 자료

댓글