Skip to main content

Kubernetes Headless Service

· 14 min read
Jinwoong Kim

Cloud Native 환경에서의 서비스 디스커버리

서비스 디스커버리는 클라우드 네이티브 환경에서 애플리케이션의 구성 요소들이 서로를 인식하고 통신할 수 있도록 하는 핵심 기능이다.

  1. 동적 환경 지원: 클라우드 환경에서는 애플리케이션의 구성 요소가 동적으로 생성되거나 삭제될 수 있다. 서비스 디스커버리는 이러한 변화에 즉각적으로 대응하여, 새로운 인스턴스가 추가되거나 기존 인스턴스가 제거될 때에도 애플리케이션이 원활하게 작동할 수 있도록 한다.

  2. 부하 분산: 서비스 디스커버리를 통해 클라이언트는 여러 서비스 인스턴스 중에서 적절한 인스턴스를 선택할 수 있다. 이를 통해 트래픽이 고르게 분산되어 시스템의 성능과 안정성을 높일 수 있다.

  3. 장애 복구: 서비스 디스커버리는 장애가 발생한 인스턴스를 자동으로 감지하고, 클라이언트가 다른 정상 인스턴스에 연결할 수 있도록 해서 가용성을 높인다.

  4. 유지보수 용이성: 서비스 디스커버리를 통해 애플리케이션의 구성 요소가 서로를 쉽게 찾을 수 있으므로, 시스템의 유지보수가 용이해진다. 개발자는 서비스의 위치나 상태를 신경 쓰지 않고, 애플리케이션의 비즈니스 로직에 집중할 수 있다.

  5. 확장성: 서비스 디스커버리는 새로운 서비스 인스턴스를 추가할 때, 기존의 클라이언트가 이를 자동으로 인식할 수 있도록 하여, 시스템의 확장성을 지원한다. 이는 비즈니스 요구에 따라 유연하게 리소스를 조정할 수 있게 한다.

Headless Service

Kubernetes Service는 클러스터 내외의 파드(pod) 간 통신을 위한 중요한 요소이다. 일반적인 Service는 클러스터 IP와 로드밸런서, 노드포드 등을 통해 파드 내, 외부의 트래픽을 관리하지만, 모든 서비스가 이런 방식으로 동작할 필요는 없다. 특히, 상태 기반 애플리케이션이나 각 노드가 고유성을 유지해야 하는 분산 시스템에서는 Headless Service가 필수적이다.

Headless Service는 서비스 디스커버리의 중요한 역할을 수행한다. 클러스터 IP를 생성하지 않기 때문에 각 파드는 고유한 DNS 레코드를 통해 서로를 발견하고 직접 통신할 수 있다. 이는 상태 기반 애플리케이션이 각 파드의 상태를 유지하면서도 서로 간의 통신을 원활하게 할 수 있도록 지원한다. 따라서, Headless Service는 동적 환경에서 서비스 디스커버리를 가능하게 하여, 애플리케이션의 유연성과 확장성을 높이는 데 기여한다.

이번 포스팅에서는 Kubernetes의 Headless Service를 설명하고, 이를 StatefulSet과 함께 사용하는 실제 사례를 통해 활용 방법을 정리하고자 한다. 아울러, 최근 실무에서 많이 사용되는 도구들에서 활용할 수 있는 간단한 코드로 제공하여, Kubernetes에서 stateful한 애플리케이션을 어떻게 효과적으로 운영할 수 있는지에 대해 다룬다.

Service의 역할

Kubernetes의 Service는 클러스터 내의 파드 간 통신을 가능하게 하고, 외부 트래픽이 클러스터 내부에 있는 애플리케이션에 접근할 수 있도록 하는 핵심 컴포넌트이다. 일반적으로 Service는 로드밸런서를 사용하여 파드 간의 트래픽을 분산하지만, Headless Service는 이와는 다르게 동작한다.

Headless Service의 주요 특징

Headless Service는 클러스터 IP를 생성하지 않으며, Kubernetes DNS 시스템을 사용하여 파드 간의 직접적인 통신을 가능하게 한다. 이는 상태를 가진 애플리케이션, 즉 각 파드가 고유한 ID와 상태를 유지해야 하는 경우에 매우 유용하다. 주로 데이터베이스, 분산 시스템, 상태 기반 애플리케이션에서 주로 사용된다. 레플리카셋을 통해 생성된 스테이트리스 파드는 동일한 것으로 간주되며, 요청이 어느 쪽에 도착하는지는 중요하지 않다, 따라서 일반 서비스 형태로 로드 밸런싱이 이루어진다. 그러나 스테이트풀 파드는 서로 다르며, 특정 파드의 좌표로 특정 파드에 도달해야 하는 경우가 많다.

apiVersion: v1
kind: Service
metadata:
name: redis
spec:
clusterIP: None
selector:
app: redis
ports:
- name: http
port: 8080
  • Cluster IP 없음: Headless Service는 클러스터 IP를 할당하지 않으며, 각 파드가 직접 DNS 레코드를 통해 접근된다. 이는 kube-proxy가 서비스를 처리하지 않고 클러스터 IP 할당이나 로드 밸런싱을 원하지 않는다는 것을 의미한다.
  • DNS 기반 서비스 발견: 파드들은 DNS 쿼리를 통해 서로를 발견하고, 파드의 개별 IP를 직접 조회하여 통신할 수 있다.
  • StatefulSet과의 통합: Headless Service는 StatefulSet과 자주 결합되어 사용되며, 상태를 가진 파드가 고유한 네트워크 ID를 가질 수 있도록 지원한다.
  • Selector 사용: Headless Service는 selector(선택자)를 사용하여 특정 파드에 대한 접근을 가능하게 한다. 이러한 서비스는 API 서버에 엔드포인트 레코드를 생성하고, 서비스에 연결된 파드를 가리키는 A 레코드(주소)를 반환하는 DNS 항목을 생성한다. 간단히 말해, 각 파드는 클라이언트가 예측 가능한 방식으로 직접 접근할 수 있는 DNS 항목을 갖게 된다. 예를 들어, 우리의 redis 서비스가 default 네임스페이스에 속한다면, StatefulSet의 파드 이름은 redis-0, redis-1 이런식으로 제공되게 되는데 redis-0 파드에 접근하기 위해서는 FQDN인 redis-0.redis.default.svc.cluster.local 처럼 파드의 이름을 서비스 이름 앞에 붙여서 사용한다.

Headless Service 사용 사례

최근의 Cloud Native 애플리케이션에서는 분산 데이터베이스, 메시지 브로커, 컨테이너화된 스토리지 시스템 등의 구축이 활발히 이루어지고 있다. 이러한 시스템은 고유한 네트워크 식별자와 상태 유지가 필수적이다. Headless Service는 이러한 요구사항을 만족시키는 가장 이상적인 Kubernetes 서비스 타입이다.

DevOps와 SRE 환경에서의 서비스는 확장성과 고가용성을 기반으로 해야 한다. Headless Service는 이러한 시스템 구축에 중요한 역할을 한다. 특히 분산 데이터베이스, 메시지 브로커, 컨테이너화된 스토리지 등 다양한 클라우드 네이티브 애플리케이션에서 파드 간의 직접 통신을 가능하게 함으로써, 더 나은 관리성과 성능을 제공한다.

분산 데이터베이스에서의 활용

최근 많은 엔터프라이즈 애플리케이션이 Cassandra, CockroachDB와 같은 분산 데이터베이스 시스템을 사용하고 있다. 이들 시스템은 고가용성을 유지하면서도 여러 노드에 데이터를 분산 저장하고 동기화해야 하므로, Headless Service와 StatefulSet을 함께 사용하여 이러한 요구를 충족할 수 있다.

Redis와 Headless Service

Redis는 인메모리 데이터 구조 저장소로, 고속 데이터 접근이 필요한 애플리케이션에서 자주 사용된다. Redis 클러스터는 각 노드가 고유한 네트워크 식별자와 상태를 유지해야 하며, 이를 위해 Headless Service가 효과적으로 사용된다.

Redis 클러스터 구성 예시

다음은 Redis 클러스터를 Kubernetes에서 구성하는 Headless Service와 StatefulSet의 예시이다.

apiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
spec:
clusterIP: None
selector:
app: redis
ports:
- port: 6379
name: redis

이 예시는 Redis를 위한 Headless Service 정의이다. clusterIP: None으로 설정함으로써 각 Redis 노드가 고유한 IP 주소를 통해 DNS에 등록된다.

StatefulSet과의 결합

Redis 클러스터의 StatefulSet 정의는 각 노드가 고유한 네트워크 ID를 유지할 수 있도록 한다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: "redis" ## 스테이트풀셋에 사용할 헤드리스 서비스 이름 지정
replicas: 2
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:latest
ports:
- containerPort: 6379
name: redis

이 예시는 각 Redis 노드가 고유한 DNS 레코드를 통해 서로 통신할 수 있도록 StatefulSet과 Headless Service를 통합한 것이다. 이를 통해 각 노드는 독립적으로 상태를 유지하면서도 클러스터 환경에서 동작할 수 있다. 자세히 보면 .spec.serviceName에 StatefulSet에 사용할 Headless Service 이름을 지정한 것을 확인할 수 있다.

해당 리소스를 생성하면 0부터 순서대로 인덱스를 확인할 수 있다.

$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-0 1/1 Running 0 35m 10.244.0.14 kind-control-plane <none> <none>
redis-1 1/1 Running 0 35m 10.244.0.15 kind-control-plane <none> <none>

임시로 netshoot 파드를 실행해서 Headless Service의 FQDN 이름으로 DNS질의를 해보면 ClusterIP가 아닌 각 파드의 IP로 응답하는 것을 알 수 있다.

$ kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot -- sh
~ # host redis-0.redis.default
redis-0.redis.default.svc.cluster.local has address 10.244.0.14
~ # host redis-1.redis.default
redis-1.redis.default.svc.cluster.local has address 10.244.0.15

정리

Kubernetes의 Headless Service는 상태 기반 애플리케이션에서 각 파드 간의 직접적인 통신을 가능하게 하여, DevOps 및 SRE 환경에서 분산 시스템 구축에 중요한 도구로 사용된다. 이 포스트에서는 Headless Service의 개념과 함께, Redis를 예시로 실제 분산 시스템에서의 활용 사례를 간단하게 살펴봤다.