본문 바로가기
1. 개발/1.8. ActiveMQ

쿠버네티스 'Headless Service'를 이용한 브로커 피어 발견 방법?

by 엉짱 2026. 4. 12.
반응형

쿠버네티스 'Headless Service'를 이용한 브로커 피어 발견 방법?

쿠버네티스(Kubernetes) 환경에서 메시지 브로커(ActiveMQ Artemis 등)를 클러스터링할 때, 인프라 엔지니어는 온프레미스 환경과는 전혀 다른 거대한 네트워킹 장벽에 부딪힙니다. 파드(Pod)의 IP는 시시각각 변하고, 기존에 의존하던 UDP 멀티캐스트(Multicast) 통신은 대부분의 CNI(Container Network Interface)와 퍼블릭 클라우드에서 보안 및 트래픽 폭주 방지를 이유로 완벽하게 차단되어 있기 때문입니다.

이 척박한 동적 환경에서 브로커들이 서로의 위치를 찾아내고(Peer Discovery) 견고한 풀 메쉬(Full Mesh) 클러스터를 엮어내기 위한 클라우드 네이티브의 해답이 바로 'Headless Service(헤드리스 서비스)'입니다. 본 가이드에서는 헤드리스 서비스의 아키텍처적 원리와 StatefulSet과의 결합을 통한 브로커 피어 탐색 기법을 상세히 해부합니다.


1. 일반 'ClusterIP' 서비스의 치명적 한계

쿠버네티스에서 헤드리스 서비스를 이해하려면, 먼저 일반적인 ClusterIP 서비스가 왜 브로커 클러스터링에 실패하는지 알아야 합니다.

일반적인 쿠버네티스 서비스는 L4 로드 밸런서(Load Balancer) 역할을 합니다. 클라이언트가 서비스 도메인을 호출하면, 쿠버네티스는 iptables나 IPVS를 통해 서비스 뒤에 묶여 있는 여러 파드들 중 하나로 트래픽을 랜덤하게(Round Robin) 전달합니다. 웹 서버(Stateless)에는 아주 훌륭한 방식입니다.

하지만 브로커 클러스터링 내부의 P2P(Peer-to-Peer) 통신에서는 이 방식이 최악의 독이 됩니다. 브로커 A가 브로커 B와 토폴로지 정보를 교환하고 상태를 동기화하려면, 불특정 다수가 아닌 'B의 정확한 고유 주소(엔드포인트)'를 알고 1:1로 직접 소통해야 합니다. 로드 밸런서를 거치게 되면 내가 방금 통신했던 브로커가 B인지 C인지 보장할 수 없으며, 결국 브로커 간의 상태 동기화 프로토콜이 완전히 붕괴됩니다.


2. Headless Service의 아키텍처적 마법

이러한 P2P 통신의 한계를 극복하기 위해 쿠버네티스는 clusterIP: None 이라는 특수한 속성을 가진 '헤드리스 서비스'를 제공합니다.

이름 그대로 '머리(진입점 VIP)'가 없는 이 서비스는 트래픽을 프록시하거나 로드 밸런싱하지 않습니다. 대신, 쿠버네티스 내부의 CoreDNS 시스템과 직접 결합하여 동작의 패러다임을 바꿉니다.

어떤 파드가 헤드리스 서비스의 도메인 이름(DNS)을 질의(Lookup)하면, 단일 가상 IP가 반환되는 것이 아니라 해당 서비스에 묶여 있는 모든 살아있는 파드들의 실제 IP 주소 목록(A Record 리스트)이 통째로 반환됩니다. 브로커는 이 IP 목록 전체를 건네받음으로써, 로드 밸런서를 거치지 않고 각각의 피어(Peer) 노드들에게 다이렉트로 TCP 커넥션을 뚫어낼 수 있는 강력한 탐색(Discovery)의 단서를 얻게 됩니다.


3. StatefulSet과의 결합: 고정 식별자의 획득

헤드리스 서비스의 진정한 위력은 브로커를 배포하는 컨트롤러인 StatefulSet(스테이트풀셋)과 결합할 때 비로소 완성됩니다.

메시지 브로커와 같은 유상태(Stateful) 애플리케이션은 파드가 재시작되더라도 자신의 정체성(Server ID)과 디스크 데이터를 유지해야 합니다. StatefulSet은 파드를 생성할 때 랜덤한 해시값이 아닌, broker-0, broker-1과 같이 예측 가능하고 절대 변하지 않는 고정된 인덱스 호스트 네임을 부여합니다.

여기에 헤드리스 서비스가 덧입혀지면, 쿠버네티스 네트워크 내부에는 완벽하게 고정된 불변의 DNS FQDN(Fully Qualified Domain Name)이 생성됩니다.

  • broker-0.broker-headless-svc.default.svc.cluster.local
  • broker-1.broker-headless-svc.default.svc.cluster.local

브로커 파드가 죽고 다른 워커 노드에서 완전히 새로운 IP를 할당받고 다시 살아나더라도, 위 도메인 주소만큼은 영원히 변하지 않고 항상 최신의 파드 IP를 가리키도록 CoreDNS가 자동으로 업데이트해 줍니다.


4. 메시지 브로커의 피어 발견(Peer Discovery) 적용법

이러한 쿠버네티스의 네트워킹 특성을 활용하면, 이전 가이드에서 다루었던 브로커의 '정적 탐색(Static Discovery)' 방식을 클라우드 환경에 완벽하게 이식할 수 있습니다.

브로커의 broker.xml 설정 파일에 하드코딩해야 했던 고정 IP 주소들을, 앞서 StatefulSet이 만들어낸 불변의 DNS 주소들로 모두 치환해 줍니다.

  1. broker-0 파드가 구동됩니다.
  2. 설정 파일에 초기 커넥터로 명시된 broker-1.broker-headless-svc... 도메인으로 DNS 질의를 날립니다.
  3. 브로커 1번 파드가 아직 안 떠있다면 연결에 실패하고 재시도하며 대기합니다. 브로커 1번 파드가 살아나는 순간, CoreDNS는 1번 파드의 새로운 IP를 반환합니다.
  4. broker-0은 즉각적으로 그 IP로 TCP 커넥션을 맺고 클러스터 토폴로지 맵을 교환하여 클러스터를 완성합니다.

이 방식은 멀티캐스트가 완벽하게 차단된 클라우드 환경에서도, ZooKeeper와 같은 외부의 거창한 탐색 솔루션 도입 없이 오직 쿠버네티스 네이티브 DNS 기능만으로 브로커들을 거미줄처럼 엮어내는 가장 우아하고 견고한 아키텍처입니다.


5. 인프라 아키텍트의 필수 고려사항 (Best Practices)

헤드리스 서비스 기반의 클러스터링을 프로덕션 환경에 배포할 때는 다음 두 가지 튜닝 포인트를 반드시 통제해야 합니다.

A. 자바(JVM) DNS 캐싱 타임(TTL)의 극단적 축소
ActiveMQ와 같은 자바 가상 머신(JVM) 기반의 애플리케이션은 DNS 질의 결과를 한 번 받으면 메모리에 오랫동안 쥐고(캐싱) 있으려는 습성이 있습니다. 쿠버네티스에서 파드가 재시작되어 IP가 바뀌었는데 JVM이 과거의 IP를 계속 캐싱하고 있다면 클러스터는 절대 복구되지 않습니다.
반드시 파드의 JVM 실행 옵션에 networkaddress.cache.ttl=5 와 같이 DNS 캐시 만료 시간을 매우 짧게(예: 5초 이하) 설정하여, 브로커가 항상 CoreDNS로부터 최신 파드 IP를 신선하게 받아오도록 강제해야 합니다.

B. JGroups 'DNS_PING' 활용 (오토스케일링 특화)
만약 브로커의 파드 개수가 3개, 5개, 10개로 수시로 변하는 오토스케일링(HPA) 환경이라면, 설정 파일에 broker-0, broker-1을 일일이 하드코딩해 두는 정적 탐색 방식은 한계가 있습니다.
이때는 브로커 내부의 JGroups 클러스터링 엔진을 활성화하고 DNS_PING 프로토콜을 사용하십시오. 설정 파일에 개별 파드 이름 대신 헤드리스 서비스의 최상위 도메인(broker-headless-svc.default.svc.cluster.local) 딱 하나만 던져주면 됩니다. JGroups 엔진이 주기적으로 이 최상위 도메인에 묶인 모든 하위 파드 IP 목록을 한 번에 긁어와 동적으로 멤버십을 갱신하므로, 컨테이너 스케일 아웃이 완벽하게 자동화됩니다.

결론적으로 쿠버네티스의 'Headless Service'는 유상태(Stateful) 애플리케이션이 클라우드의 역동성(동적 IP)과 타협하지 않고 자신만의 견고한 P2P 통신망을 구축할 수 있게 해주는 중추 신경망입니다. 이를 StatefulSet의 고정 식별자 기능과 정밀하게 결합하여, 어떠한 컨테이너 재시작 환경에서도 파괴되지 않는 영속적인 엔터프라이즈 메시징 백본을 완성하시기 바랍니다.

반응형