다중 고지 리스너
다중 고지 리스너 (Multiple Advertised Listeners)
운영 환경에서 Pulsar 클러스터는 다른 네트워크에 있는 클라이언트들을 동시에 서비스해야 하는 경우가 많아요. 보통은 Pulsar Proxy를 두고 외부 연결을 처리하지만, 상황에 따라 Proxy 없이 클라이언트가 브로커에 직접 연결하고 싶을 수도 있죠. 다중 고지 리스너는 브로커가 클라이언트마다 다른 주소를 고지(advertise)할 수 있게 해서 이런 직접 연결을 가능하게 해줘요. 이 글에서는 동작 원리와 설정 방법을 함께 살펴볼게요.
출처: 문서
본문
운영 환경에서 Pulsar 클러스터는 종종 다른 네트워크의 클라이언트를 서비스해야 해요. 외부 연결을 처리하는 전통적인 방법은 Pulsar Proxy를 배포하는 것이지만, 어떤 경우에는 Pulsar Proxy를 사용하지 않고 클라이언트와 브로커를 직접 연결하고 싶을 수도 있어요. 다중 고지 리스너(multiple advertised listeners)는 브로커가 다른 클라이언트에게 다른 주소를 고지하도록 허용해 이 직접 연결을 가능하게 해줘요.
조회(Lookup) 메커니즘
클라이언트가 토픽에 대해 조회를 수행하면:
- 클라이언트가 서비스 URL을 사용해 브로커에 연결해요.
- 브로커가 조회 요청을 처리하고 어떤 브로커가 토픽을 소유하는지 판단해요.
- 브로커가 클라이언트가 어떤 리스너를 통해 연결했는지에 따라 적절한 고지 주소를 반환해요.
- 클라이언트가 반환된 주소를 사용해 토픽 소유자에게 직접 연결해요.
bindAddresses를 올바르게 구성하면, 브로커는 클라이언트가 연결한 포트를 기반으로 어떤 고지 주소를 반환할지 자동으로 결정해요.
서비스 URL에는 사용 가능한 브로커에 연결해 주는 로드 밸런서가 있어야 해요.
사용 사례: Pulsar Proxy 없이 클라이언트-브로커 직접 연결
클라이언트가 브로커에 직접 연결해야 할 때(Proxy를 통하지 않고), 다중 고지 리스너가 필수적이에요. 이 접근 방식은 특히 다음 상황에서 유용해요:
- 프록시 계층을 제거해 네트워크 홉을 줄이기
- 일부 시나리오에서 배포 아키텍처 단순화
- 특정 워크로드의 잠재적 성능 향상
- 직접 브로커 연결을 선호하는 환경
네트워크 아키텍처 고려 사항
참고: Pulsar 배포는 네트워크 경계 보안(network perimeter security)을 전제로 해요. 이런 유형의 배포는 신뢰할 수 있는 네트워크의 신뢰할 수 있는 클라이언트에서만 사용하고, 네트워크 보안이 올바르게 처리되도록 해야 해요.
여러 네트워크가 있는 일반적인 배포에서는:
- 내부 네트워크: 사설 네트워크 안에서 브로커는 표준 포트의 사설 주소(예: 10.x.x.x, 172.16.x.x 또는 192.168.x.x)로 접근할 수 있어요.
- 외부 네트워크: 사설 네트워크 밖의 클라이언트를 위해 NAT(네트워크 주소 변환)가 외부 주소/포트를 내부 브로커 주소/포트로 매핑해요.
올바른 구성이 없으면 외부 클라이언트는 토픽 조회 중에 내부 브로커 주소를 받게 되어 연결에 성공할 수 없어요.
고지 리스너 구성
Pulsar는 이 문제를 해결하기 위해 세 가지 핵심 구성 옵션을 제공해요:
- advertisedListeners: 브로커가 클라이언트에 고지하는 여러 주소를 지정해요.
- bindAddresses: 각 고지 리스너를 특정 로컬 바인딩 주소와 포트에 매핑해요.
- internalListenerName: 브로커가 내부 통신에 사용하는 리스너를 지정해요.
구성 세부 사항
- advertisedListeners:
<리스너_이름>:<프로토콜>://<호스트>:<포트>,...형식이에요. 예시:advertisedListeners=internal:pulsar://192.168.1.11:6650,internal:pulsar+ssl://192.168.1.11:6651,external:pulsar://external-broker-1.example.com:6650,external:pulsar+ssl://external-broker-1.example.com:6651 - bindAddresses: 각 리스너 이름을 로컬 바인딩 주소와 포트에 매핑해요. 예시:
bindAddresses=internal:pulsar://0.0.0.0:6650,internal:pulsar+ssl://0.0.0.0:6651,external:pulsar://0.0.0.0:16650,external:pulsar+ssl://0.0.0.0:16651 - internalListenerName: 내부 통신에 사용되는 리스너를 지정해요. 예시:
internalListenerName=internal
구성 예시
여기에 다중 고지 리스너로 브로커를 구성하는 완전한 예시가 있어요:
# Define advertised listeners for internal and external clientsadvertisedListeners=internal:pulsar://192.168.1.11:6650,internal:pulsar+ssl://192.168.1.11:6651,external:pulsar://external-broker-1.example.com:6650,external:pulsar+ssl://external-broker-1.example.com:6651# Define bind addresses for each listenerbindAddresses=internal:pulsar://0.0.0.0:6650,internal:pulsar+ssl://0.0.0.0:6651,external:pulsar://0.0.0.0:16650,external:pulsar+ssl://0.0.0.0:16651# Specify the internal listener nameinternalListenerName=internal
네트워크 구성
이를 동작시키려면:
- DNS 이름을 사용한다면 각 브로커 인스턴스마다 고유한 이름을 등록해요 (예: external-broker-1.example.com, external-broker-2.example.com 등).
- 각 브로커 인스턴스에 대해 네트워크 게이트웨이 또는 방화벽이 프록시 또는 NAT를 처리하도록 구성해서, 외부 IP와 포트가 내부 IP와 외부 리스너 포트로 프록시되게 해요.
- 외부 리스너 포트의 정상 브로커에 프록시해 주는 로드 밸런서를 추가해요.
클라이언트 구성
브로커가 올바르게 구성되면 클라이언트는 리스너 이름을 지정하지 않고 연결할 수 있어요:
이 예시에서 "private-brokers.internal"은 사용 가능한 브로커의 내부 로드 밸런서이고, "external-brokers.example.com"은 사용 가능한 브로커의 외부 로드 밸런서로, 각 브로커의 외부 리스너 바인드 포트에 연결돼요.
// Internal client using standard protocol
PulsarClient internalClient = PulsarClient.builder()
.serviceUrl("pulsar://private-brokers.internal:6650")
.build();
// Internal client using SSL
PulsarClient internalSecureClient = PulsarClient.builder()
.serviceUrl("pulsar+ssl://private-brokers.internal:6651")
.build();
// External client with SSL
PulsarClient externalClient = PulsarClient.builder()
.serviceUrl("pulsar+ssl://external-brokers.example.com:6651")
.build();
참고: 예전 Pulsar 문서에서는 클라이언트 구성에
listenerName파라미터를 사용하라고 권장했지만,bindAddresses가 올바르게 구성된 경우에는 이 접근 방식이 더 이상 필요하지 않아요. Pulsar 조회 메커니즘이 바인딩 포트를 기반으로 적절한 고지 주소를 반환해요.
Kubernetes 배포 권장 사항
Kubernetes 배포에서는 일반적으로 Pulsar Proxy를 권장하지만, 직접 브로커 접근이 필요한 경우 다중 고지 리스너를 사용할 수 있어요.
Apache Pulsar Helm 차트는 현재 이 유형의 배포를 지원하지 않아요. 이 지침은 Kubernetes 배포에서 advertisedListeners 기능을 사용하기 위한 일반적인 안내로 제공돼요.
Kubernetes 배포에서 이 문제를 처리하는 방법은 여러 가지가 있어요. 일부 서비스 메시(service mesh) 구성에서도 고지 리스너가 필요해요.
보안 공지: Pulsar의 설계는 클러스터가 네트워크 경계 보안 뒤에 있다고 가정해요. 브로커 클라이언트와 관리 API는 공개 인터넷에 노출하기에 안전하지 않아요. 가능하면 사설 네트워크의 내부 로드 밸런서를 사용하세요. 외부 노출이 불가피하다면 Service의
loadBalancerSourceRanges필드(또는 동등한 방화벽 규칙)로 권한 있는 IP 범위에만 접근을 제한하고, TLS와 인증이 완전히 구성되었는지 확인하세요. Pulsar의 설계는 배포가 네트워크 경계 보안 조치로 보호된다고 가정해요.
완전한 배포에는 두 종류의 서비스가 필요해요:
- 클러스터 전체 서비스 URL. 모든 정상 브로커 팟을 앞에 두는 LoadBalancer 서비스(예:
external-brokers.example.com)예요. 서비스의 외부 포트는 표준 Pulsar TLS 포트6651일 수 있으며, LoadBalancer가 이를 브로커의 외부 리스너 바인드 포트(예:16651)로 매핑해요. 클라이언트는 이 주소를serviceUrl로 사용해 토픽을 조회하고, 조회 응답은 각 클라이언트를 토픽을 소유한 특정 브로커로 리다이렉트하며, 해당 브로커는 아래 설명된 브로커 팟별 서비스를 통해 접근돼요. - 브로커 팟별 서비스. 각 브로커 팟을 대상으로 하는 하나의 서비스로, 해당 팟만을 대상으로 하고 그 팟의 외부 리스너 바인드 포트를 노출해요. 이 팟별 서비스의 외부 FQDN/IP와 포트는 브로커가
advertisedListeners의external리스너에 대해 고지하는 주소와 정확히 일치해야 해요. 그렇지 않으면 조회 응답이 클라이언트가 도달할 수 없는 주소를 반환해요. 아래에 두 가지 옵션을 설명하니 TLS와 비용 요구 사항에 따라 하나를 선택하세요.
옵션 1: 브로커 팟별 NodePort 서비스
각 브로커 팟에 대해 하나의 NodePort 서비스를 만들어 그 팟의 외부 리스너 바인딩 포트에 매핑해요. 클라이언트는 <노드-ip-또는-호스트명>:<노드-포트>로 브로커에 도달해요.
- 비용: 추가 클라우드 인프라가 필요 없어요. 트래픽이 기존 클러스터 노드의 포트를 통해 들어와요.
- 네트워크 도달성: 클라이언트가 사설 네트워크(VPC, 온프레미스 네트워크, VPN 등)를 통해 브로커 노드에 도달하는 경우 이 옵션이 적합해요. Pulsar는 공개 인터넷 노출을 지원하지 않는 구성이므로, NodePort로는 방화벽 규칙에 특별한 주의가 필요해요. 선택한 포트가 모든 노드에서 열리므로, 노드 수준 방화벽, 클라우드 보안 그룹, 네트워크 정책을 검토해 그 포트가 권한 있는 클라이언트 네트워크에서만 도달 가능하도록 해야 해요.
- TLS 문제: 브로커가 제시하는 인증서는 클라이언트가 실제로 연결하는 호스트명 또는 IP와 일치해야 해요. 자체 관리 클러스터에서는 노드 IP가 불안정한 경우가 많아서, 노드를 가리키는 안정적인 DNS를 프로비저닝(또는 인증서에 IP SAN 포함)하지 않으면 호스트명 검증이 실패해요. 브로커 전체에 단일 와일드카드 인증서를 재사용하려면 모든 클라이언트 접점이 그 와일드카드 도메인 아래에서 해석되어야 하는데, 클라이언트가 노드 IP에 직접 연결할 때는 어색해요.
옵션 2: 브로커 팟별 LoadBalancer 서비스
각 브로커 팟에 대해 하나의 LoadBalancer 서비스를 만들어 그 팟의 외부 리스너 바인딩 포트에 매핑해요.
- 비용: 각 LoadBalancer는 반복 비용이 발생하는 별도의 클라우드 리소스예요. N개의 브로커가 있으면 N개의 로드 밸런서 비용을 내야 해요.
- TLS가 더 쉬움: LoadBalancer마다 안정적인 DNS 이름(예: pulsar-broker-0.example.com)을 제어하고 그 이름에 대한 인증서를 발급할 수 있어요. 클라이언트가 호스트명으로 연결하므로 특별한 구성 없이 표준 호스트명 검증이 동작해요.
특정 브로커 팟 대상 지정 (두 옵션 모두에 적용)
어떤 서비스 유형을 선택하든, 각 팟별 서비스는 선택자(selector)에 statefulset.kubernetes.io/pod-name 레이블(예: statefulset.kubernetes.io/pod-name: pulsar-broker-0)을 포함해 정확히 하나의 브로커 팟을 선택해야 해요. 이 레이블은 StatefulSet 컨트롤러가 모든 팟에 자동으로 적용하며, 특정 브로커의 외부 리스너 포트로 가는 트래픽이 서비스의 외부 주소·포트와 일치하는 advertisedListeners 구성을 가진 브로커 팟에 도달하도록 보장해요. Kubernetes 1.32 이상에서는 안정적인 apps.kubernetes.io/pod-index 레이블(예: apps.kubernetes.io/pod-index: "0")을 선택자에서 대신 사용할 수 있어요.
브로커 팟별 LoadBalancer의 브로커 구성 예시
# Advertised listeners — "internal" uses the StatefulSet headless service DNS name,
# "external" uses the stable DNS of this broker's LoadBalancer
advertisedListeners=internal:pulsar://pulsar-broker-0.pulsar-broker-headless.pulsar.svc.cluster.local:6650,internal:pulsar+ssl://pulsar-broker-0.pulsar-broker-headless.pulsar.svc.cluster.local:6651,external:pulsar://pulsar-broker-0.example.com:6650,external:pulsar+ssl://pulsar-broker-0.example.com:6651
# Bind addresses — "external" binds use a different local port range than "internal"
bindAddresses=internal:pulsar://0.0.0.0:6650,internal:pulsar+ssl://0.0.0.0:6651,external:pulsar://0.0.0.0:16650,external:pulsar+ssl://0.0.0.0:16651
# Specify the internal listener name
internalListenerName=internal
위 예시에서:
- pulsar-broker-0.pulsar-broker-headless.pulsar.svc.cluster.local은 StatefulSet의 headless 서비스가 제공하는 팟별 FQDN이에요 (형식:
<pod-name>.<headless-service-name>.<namespace>.svc.cluster.local). 팟 재시작에도 안정적이고 현재 팟 IP로 다시 해석되므로, 팟 IP 대신 내부 리스너에 사용해야 해요. 팟 자체의 FQDN은 팟마다 동적으로 설정돼요 (보통metadata.labels['statefulset.kubernetes.io/pod-name']과 알려진 headless 서비스 및 네임스페이스를 통해). - pulsar-broker-0.example.com은 이 브로커 팟을 대상으로 하는 LoadBalancer 서비스에 할당된 안정적인 DNS 이름이에요 (statefulset.kubernetes.io/pod-name: pulsar-broker-0 선택자 사용). 각 브로커 팟은 팟마다 동적으로 설정되는 고유한 DNS 이름을 가져요.
- 브로커별 LoadBalancer는 외부 포트 6650과 6651을 브로커의 외부 리스너 바인드 포트 16650과 16651로 매핑해요. 외부적으로 표준 Pulsar 포트를 사용하면 클러스터 전체 서비스 URL과 팟별 LoadBalancer 모두에 동일한 DNS 이름과 인증서가 동작해요.
더 알아보기 (Learn more)
- Pulsar Proxy 관리 — Proxy를 사용하는 전통적인 외부 연결 방식도 비교해 보세요.
- 클러스터 수준 페일오버 — 클러스터 장애 대응을 함께 알아봐요.
- Kubernetes 배포 — Helm 차트와 배포 가이드를 확인해요.