This is an automated email from the ASF dual-hosted git repository.

lhotari pushed a commit to branch 
lh-support-webservice-bindaddresses-and-advertised-listeners
in repository https://gitbox.apache.org/repos/asf/pulsar.git

commit 8d1a4d034b0bcc382d20838867460612fdf386ac
Author: Lari Hotari <[email protected]>
AuthorDate: Thu May 21 11:12:22 2026 +0300

    [improve][broker] Clarify listener-related config docs
    
    Tighten and align the docs for the eight listener-related properties across
    `ServiceConfiguration.java`, `conf/broker.conf`, and `conf/standalone.conf` 
so
    the configuration model is explicit:
    
    - `*Port` properties (`brokerServicePort` / `brokerServicePortTls` /
      `webServicePort` / `webServicePortTls`) now say that each port number is 
used
      both for the local socket binding (with `bindAddress`) and for the 
advertised
      URL (with `advertisedAddress`), so no entry in `advertisedListeners` or
      `bindAddresses` is required for the internal listener.
    - `advertisedAddress` notes that it supplies the hostname for the internal
      listener's advertised URLs (e.g. `pulsar://<advertisedAddress>:<port>`).
    - `advertisedListeners` is reframed so its primary purpose comes first
      (declaring additional, typically external, listeners). The auto-configured
      internal listener and the discouraged-override caveat come after the 
format,
      and the legacy fallback (`internalListenerName=` blank ⇒ first listener
      parsed here becomes the internal one) is called out as its own short 
paragraph.
    - `internalListenerName` opens with the purpose + default, then the "when 
set
      (the default)" branch covering auto-configuration and the avoid-routing-
      cluster-traffic-through-an-external-LB warning, and finally the legacy
      fallback sentence.
    - "lookup redirects, replication, admin forwarding" → "lookup redirects, 
admin
      forwarding to owner or leader broker" — geo-replication may legitimately 
use
      an external listener, so it doesn't belong in the internal-listener list.
    - Implementation details (Jetty connectors, `AddListenerAttributeFilter`)
      removed from user-facing docs.
    
    All six shared property comment blocks (`brokerServicePort`, 
`webServicePort`,
    `bindAddress`, `bindAddresses`, `advertisedAddress`, `internalListenerName`)
    are now byte-identical between `broker.conf` and `standalone.conf`.
---
 conf/broker.conf                                   | 79 +++++++++++-------
 conf/standalone.conf                               | 57 ++++++++-----
 .../apache/pulsar/broker/ServiceConfiguration.java | 95 ++++++++++++++--------
 3 files changed, 145 insertions(+), 86 deletions(-)

diff --git a/conf/broker.conf b/conf/broker.conf
index 3d5ad632b42..0038cb22571 100644
--- a/conf/broker.conf
+++ b/conf/broker.conf
@@ -38,16 +38,22 @@ configurationMetadataSyncEventTopic=
 # The metadata store URL for the configuration data. If empty, we fall back to 
use metadataStoreUrl
 configurationMetadataStoreUrl=
 
-# Broker data port
+# Port for the Pulsar binary protocol of the internal listener.
+# The same port number is used both for the local socket binding (with 
bindAddress) and for the
+# advertised URL (with advertisedAddress), so no entry in advertisedListeners 
or bindAddresses
+# is required for the internal listener.
 brokerServicePort=6650
 
-# Broker data port for TLS - By default TLS is disabled
+# Port for the Pulsar binary protocol TLS endpoint of the internal listener.
+# Used both for the local socket binding and the advertised URL. By default 
TLS is disabled.
 brokerServicePortTls=
 
-# Port to use to server HTTP request
+# Port for the HTTP admin/REST endpoint of the internal listener.
+# Used both for the local socket binding and the advertised URL.
 webServicePort=8080
 
-# Port to use to server HTTPS request - By default TLS is disabled
+# Port for the HTTPS admin/REST endpoint of the internal listener.
+# Used both for the local socket binding and the advertised URL. By default 
TLS is disabled.
 webServicePortTls=
 
 # Specify the tls protocols the broker's web service will use to negotiate 
during TLS handshake
@@ -62,46 +68,61 @@ webServiceTlsProtocols=
 # webServiceTlsCiphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
 webServiceTlsCiphers=
 
-# Hostname or IP address the service binds on, default is 0.0.0.0.
+# Local network interface IP for the internal listener's port bindings.
+# Use a specific local IP to bind to a single interface, or 0.0.0.0 to bind on 
all interfaces.
 bindAddress=0.0.0.0
 
-# Bind addresses for the broker.
-# Comma-separated list of <listener_name>:<scheme>://<ip>:<port> entries.
+# Per-listener socket bindings.
+# The internal listener's bindings are derived automatically from bindAddress 
plus the port
+# properties (brokerServicePort / brokerServicePortTls / webServicePort / 
webServicePortTls)
+# and do not need to be repeated here.
+# PIP-95 smart listener selection routes a connection to the listener whose 
port it arrived on.
+# For the Pulsar binary protocol (pulsar / pulsar+ssl) this is optional — 
clients can also pass
+# an explicit listenerName. For the HTTP/HTTPS Admin API (http / https) it is 
the only routing
+# mechanism, so every HTTP/HTTPS advertised listener reachable from outside 
the cluster needs a
+# dedicated entry here on a unique port. This lets a layer-4 TCP load balancer 
serve the Admin
+# API directly per listener, without an HTTP reverse proxy.
+# Format: comma-separated <listener_name>:<scheme>://<ip>:<port> entries.
 # Supported schemes: pulsar, pulsar+ssl, http, https.
 # The <ip> part selects which local network interface the port binds to: use a 
specific local
 # IP, or 0.0.0.0 to bind on all interfaces. A local hostname is also accepted 
but not
 # recommended.
-# At runtime, the legacy brokerServicePort, brokerServicePortTls, 
webServicePort, and
-# webServicePortTls properties are migrated into the bind address list (using 
bindAddress,
-# default 0.0.0.0, as the IP) under the listener named by 
internalListenerName, and merged with
-# the entries declared here.
-# Each ip:port may be bound by exactly one (listener, scheme) pair — a TCP 
socket cannot serve
-# two protocol schemes at once. An entry here that exactly matches a migrated 
legacy binding
-# (same listener:scheme://ip:port) is tolerated; assigning the same ip:port to 
a different
-# listener or to a different scheme fails validation.
-# Binding the same listener to multiple ports of the same scheme is allowed 
but rarely useful.
+# Each ip:port may be bound by exactly one (listener, scheme) pair. An entry 
that exactly
+# matches the auto-derived internal-listener binding is tolerated; assigning 
the same ip:port
+# to a different listener or scheme fails validation.
 bindAddresses=
 
-# Hostname or IP address the service advertises to the outside world.
-# If not set, the value of InetAddress.getLocalHost().getCanonicalHostName() 
is used.
+# Hostname or IP advertised to clients for the internal listener.
+# Combined with the *Port properties it forms the internal listener's 
advertised URLs
+# (e.g. pulsar://<advertisedAddress>:<brokerServicePort>).
+# If not set, defaults to InetAddress.getLocalHost().getCanonicalHostName().
 advertisedAddress=
 
-# Advertised listeners for the broker.
-# Comma-separated list of <listener_name>:<scheme>://<host>:<port> entries.
+# Declares additional advertised listeners — typically external listeners that 
complement the
+# internal one.
+# Format: comma-separated <listener_name>:<scheme>://<host>:<port> entries.
 # Supported schemes: pulsar, pulsar+ssl, http, https.
 # A listener name may be repeated to declare multiple schemes for the same 
listener.
-# URLs declared here for the listener named by internalListenerName take 
precedence; any URL
-# slots left undeclared are filled in from brokerServicePort, 
brokerServicePortTls,
-# webServicePort, and webServicePortTls, so existing deployments keep working 
after upgrade.
+# The internal listener is auto-configured from advertisedAddress plus the 
*Port properties
+# (brokerServicePort / brokerServicePortTls / webServicePort / 
webServicePortTls) so it does
+# not need an entry here. URLs declared here under internalListenerName do 
override that
+# auto-configured listener, but this is not recommended because it can route 
cluster-internal
+# traffic through an external endpoint (for example, an external load 
balancer).
+# Legacy fallback: when internalListenerName is left blank, the first listener 
parsed from this
+# property is used as the internal listener (so in that case its entry here is 
required).
 # advertisedListeners=
 
 # Name of the listener used for cluster-internal broker-to-broker 
communication (lookup
-# redirects, replication, admin forwarding).
-# The internal listener must advertise an address reachable from other brokers 
in the same
-# cluster. It can also serve external traffic when the same DNS names and IPs 
are routable from
-# outside the cluster; this is typically not the case with Kubernetes, where 
the broker pod IP
-# is only reachable in-cluster — configure a separate non-internal listener 
for external clients
-# in that situation.
+# redirects, admin forwarding to owner or leader broker). Defaults to 
"internal".
+# When set (the default), the internal listener is auto-configured from 
advertisedAddress plus
+# the *Port properties. The internal listener must advertise addresses 
reachable from other
+# brokers in the same cluster; avoid overriding its URLs in 
advertisedListeners to point at an
+# external load balancer because that would route cluster-internal traffic 
outside the cluster.
+# For external clients, declare a separate non-internal listener in 
advertisedListeners
+# instead. The default name "internal" also keeps the port-derived internal 
listener distinct
+# from any user-declared listener names.
+# Setting this to an empty string restores the legacy fallback: the first 
listener parsed from
+# advertisedListeners is used as the internal listener.
 internalListenerName=internal
 
 # Enable or disable the HAProxy protocol.
diff --git a/conf/standalone.conf b/conf/standalone.conf
index 8d4c07e7f01..69011aeb77e 100644
--- a/conf/standalone.conf
+++ b/conf/standalone.conf
@@ -32,42 +32,57 @@ metadataStoreConfigPath=
 # The metadata store URL for the configuration data. If empty, we fall back to 
use metadataStoreUrl
 configurationMetadataStoreUrl=
 
+# Port for the Pulsar binary protocol of the internal listener.
+# The same port number is used both for the local socket binding (with 
bindAddress) and for the
+# advertised URL (with advertisedAddress), so no entry in advertisedListeners 
or bindAddresses
+# is required for the internal listener.
 brokerServicePort=6650
 
-# Port to use to server HTTP request
+# Port for the HTTP admin/REST endpoint of the internal listener.
+# Used both for the local socket binding and the advertised URL.
 webServicePort=8080
 
-# Hostname or IP address the service binds on, default is 0.0.0.0.
+# Local network interface IP for the internal listener's port bindings.
+# Use a specific local IP to bind to a single interface, or 0.0.0.0 to bind on 
all interfaces.
 bindAddress=0.0.0.0
 
-# Bind addresses for the broker.
-# Comma-separated list of <listener_name>:<scheme>://<ip>:<port> entries.
+# Per-listener socket bindings.
+# The internal listener's bindings are derived automatically from bindAddress 
plus the port
+# properties (brokerServicePort / brokerServicePortTls / webServicePort / 
webServicePortTls)
+# and do not need to be repeated here.
+# PIP-95 smart listener selection routes a connection to the listener whose 
port it arrived on.
+# For the Pulsar binary protocol (pulsar / pulsar+ssl) this is optional — 
clients can also pass
+# an explicit listenerName. For the HTTP/HTTPS Admin API (http / https) it is 
the only routing
+# mechanism, so every HTTP/HTTPS advertised listener reachable from outside 
the cluster needs a
+# dedicated entry here on a unique port. This lets a layer-4 TCP load balancer 
serve the Admin
+# API directly per listener, without an HTTP reverse proxy.
+# Format: comma-separated <listener_name>:<scheme>://<ip>:<port> entries.
 # Supported schemes: pulsar, pulsar+ssl, http, https.
 # The <ip> part selects which local network interface the port binds to: use a 
specific local
 # IP, or 0.0.0.0 to bind on all interfaces. A local hostname is also accepted 
but not
 # recommended.
-# At runtime, the legacy brokerServicePort, brokerServicePortTls, 
webServicePort, and
-# webServicePortTls properties are migrated into the bind address list (using 
bindAddress,
-# default 0.0.0.0, as the IP) under the listener named by 
internalListenerName, and merged with
-# the entries declared here.
-# Each ip:port may be bound by exactly one (listener, scheme) pair — a TCP 
socket cannot serve
-# two protocol schemes at once. An entry here that exactly matches a migrated 
legacy binding
-# (same listener:scheme://ip:port) is tolerated; assigning the same ip:port to 
a different
-# listener or to a different scheme fails validation.
-# Binding the same listener to multiple ports of the same scheme is allowed 
but rarely useful.
+# Each ip:port may be bound by exactly one (listener, scheme) pair. An entry 
that exactly
+# matches the auto-derived internal-listener binding is tolerated; assigning 
the same ip:port
+# to a different listener or scheme fails validation.
 bindAddresses=
 
-# Hostname or IP address the service advertises to the outside world.
-# If not set, the value of InetAddress.getLocalHost().getCanonicalHostName() 
is used.
+# Hostname or IP advertised to clients for the internal listener.
+# Combined with the *Port properties it forms the internal listener's 
advertised URLs
+# (e.g. pulsar://<advertisedAddress>:<brokerServicePort>).
+# If not set, defaults to InetAddress.getLocalHost().getCanonicalHostName().
 advertisedAddress=
 
 # Name of the listener used for cluster-internal broker-to-broker 
communication (lookup
-# redirects, replication, admin forwarding).
-# The internal listener must advertise an address reachable from other brokers 
in the same
-# cluster. It can also serve external traffic when the same DNS names and IPs 
are routable from
-# outside the cluster; this is typically not the case with Kubernetes, where 
the broker pod IP
-# is only reachable in-cluster — configure a separate non-internal listener 
for external clients
-# in that situation.
+# redirects, admin forwarding to owner or leader broker). Defaults to 
"internal".
+# When set (the default), the internal listener is auto-configured from 
advertisedAddress plus
+# the *Port properties. The internal listener must advertise addresses 
reachable from other
+# brokers in the same cluster; avoid overriding its URLs in 
advertisedListeners to point at an
+# external load balancer because that would route cluster-internal traffic 
outside the cluster.
+# For external clients, declare a separate non-internal listener in 
advertisedListeners
+# instead. The default name "internal" also keeps the port-derived internal 
listener distinct
+# from any user-declared listener names.
+# Setting this to an empty string restores the legacy fallback: the first 
listener parsed from
+# advertisedListeners is used as the internal listener.
 internalListenerName=internal
 
 # Enable or disable the HAProxy protocol.
diff --git 
a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java
 
b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java
index 9ffdbf57880..3829f6d062c 100644
--- 
a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java
+++ 
b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/ServiceConfiguration.java
@@ -175,26 +175,32 @@ public class ServiceConfiguration implements 
PulsarConfiguration {
 
     @FieldContext(
         category = CATEGORY_SERVER,
-        doc = "The port for serving binary protobuf requests."
-            + " If set, defines a server binding for 
bindAddress:brokerServicePort."
-            + " The Default value is 6650."
+        doc = "Port for the Pulsar binary protocol of the internal listener."
+            + " The same port number is used both for the local socket binding 
(with `bindAddress`)"
+            + " and for the advertised URL (with `advertisedAddress`), so no 
entry in"
+            + " `advertisedListeners` or `bindAddresses` is required for the 
internal listener."
+            + " Default is 6650."
     )
 
     private Optional<Integer> brokerServicePort = Optional.of(6650);
     @FieldContext(
         category = CATEGORY_SERVER,
-        doc = "The port for serving TLS-secured binary protobuf requests."
-            + " If set, defines a server binding for 
bindAddress:brokerServicePortTls."
+        doc = "Port for the Pulsar binary protocol TLS endpoint of the 
internal listener."
+            + " Used both for the local socket binding and the advertised URL."
+            + " By default TLS is disabled."
     )
     private Optional<Integer> brokerServicePortTls = Optional.empty();
     @FieldContext(
         category = CATEGORY_SERVER,
-        doc = "The port for serving http requests"
+        doc = "Port for the HTTP admin/REST endpoint of the internal listener."
+            + " Used both for the local socket binding and the advertised URL."
     )
     private Optional<Integer> webServicePort = Optional.of(8080);
     @FieldContext(
         category = CATEGORY_SERVER,
-        doc = "The port for serving https requests"
+        doc = "Port for the HTTPS admin/REST endpoint of the internal 
listener."
+            + " Used both for the local socket binding and the advertised URL."
+            + " By default TLS is disabled."
     )
     private Optional<Integer> webServicePortTls = Optional.empty();
 
@@ -220,57 +226,74 @@ public class ServiceConfiguration implements 
PulsarConfiguration {
 
     @FieldContext(
         category = CATEGORY_SERVER,
-        doc = "Hostname or IP address the service binds on"
+        doc = "Local network interface IP for the internal listener's port 
bindings."
+            + " Use a specific local IP to bind to a single interface, or 
`0.0.0.0` to bind on all"
+            + " interfaces. Default is `0.0.0.0`."
     )
     private String bindAddress = "0.0.0.0";
 
     @FieldContext(
         category = CATEGORY_SERVER,
-        doc = "Hostname or IP address the service advertises to the outside 
world."
-            + " If not set, the value of 
`InetAddress.getLocalHost().getCanonicalHostName()` is used."
+        doc = "Hostname or IP advertised to clients for the internal listener."
+            + " Combined with the *Port properties it forms the internal 
listener's advertised URLs"
+            + " (e.g. `pulsar://<advertisedAddress>:<brokerServicePort>`)."
+            + " If not set, defaults to 
`InetAddress.getLocalHost().getCanonicalHostName()`."
     )
     private String advertisedAddress;
 
     @FieldContext(category = CATEGORY_SERVER,
-            doc = "Advertised listeners for the broker.\n"
-                    + " Comma-separated list of 
`<listener_name>:<scheme>://<host>:<port>` entries."
+            doc = "Declares additional advertised listeners — typically 
external listeners that"
+                    + " complement the internal one.\n"
+                    + " Format: comma-separated 
`<listener_name>:<scheme>://<host>:<port>` entries."
                     + " Supported schemes: `pulsar`, `pulsar+ssl`, `http`, 
`https`."
                     + " A listener name may be repeated to declare multiple 
schemes for the same listener.\n"
-                    + " URLs declared here for the listener named by 
`internalListenerName` take precedence;"
-                    + " any URL slots left undeclared are filled in from 
`brokerServicePort`,"
-                    + " `brokerServicePortTls`, `webServicePort`, and 
`webServicePortTls`, so existing"
-                    + " deployments keep working after upgrade.")
+                    + " The internal listener is auto-configured from 
`advertisedAddress` plus the *Port"
+                    + " properties (`brokerServicePort` / 
`brokerServicePortTls` / `webServicePort` /"
+                    + " `webServicePortTls`) so it does not need an entry 
here. URLs declared here under"
+                    + " `internalListenerName` do override that 
auto-configured listener, but this is"
+                    + " not recommended because it can route cluster-internal 
traffic through an external"
+                    + " endpoint (for example, an external load balancer).\n"
+                    + " Legacy fallback: when `internalListenerName` is left 
blank, the first listener"
+                    + " parsed from this property is used as the internal 
listener (so in that case its"
+                    + " entry here is required).")
     private String advertisedListeners;
 
     @FieldContext(category = CATEGORY_SERVER,
             doc = "Name of the listener used for cluster-internal 
broker-to-broker communication"
-                    + " (lookup redirects, replication, admin forwarding).\n"
-                    + " The internal listener must advertise an address 
reachable from other brokers in"
-                    + " the same cluster. It can also serve external traffic 
when the same DNS names and"
-                    + " IPs are routable from outside the cluster; this is 
typically not the case with"
-                    + " Kubernetes, where the broker pod IP is only reachable 
in-cluster — configure a"
-                    + " separate non-internal listener for external clients in 
that situation.\n"
-                    + " Defaults to `internal`.")
+                    + " (lookup redirects, admin forwarding to owner or leader 
broker). Defaults to"
+                    + " `internal`.\n"
+                    + " When set (the default), the internal listener is 
auto-configured from"
+                    + " `advertisedAddress` plus the *Port properties. The 
internal listener must"
+                    + " advertise addresses reachable from other brokers in 
the same cluster; avoid"
+                    + " overriding its URLs in `advertisedListeners` to point 
at an external load"
+                    + " balancer because that would route cluster-internal 
traffic outside the cluster."
+                    + " For external clients, declare a separate non-internal 
listener in"
+                    + " `advertisedListeners` instead. The default name 
`internal` also keeps the"
+                    + " port-derived internal listener distinct from any 
user-declared listener names.\n"
+                    + " Setting this to an empty string restores the legacy 
fallback: the first listener"
+                    + " parsed from `advertisedListeners` is used as the 
internal listener.")
     private String internalListenerName = DEFAULT_INTERNAL_LISTENER_NAME;
 
     @FieldContext(category = CATEGORY_SERVER,
-            doc = "Bind addresses for the broker.\n"
-                    + " Comma-separated list of 
`<listener_name>:<scheme>://<ip>:<port>` entries."
+            doc = "Per-listener socket bindings.\n"
+                    + " The internal listener's bindings are derived 
automatically from `bindAddress`"
+                    + " plus the port properties (`brokerServicePort` / 
`brokerServicePortTls` /"
+                    + " `webServicePort` / `webServicePortTls`) and do not 
need to be repeated here.\n"
+                    + " PIP-95 smart listener selection routes a connection to 
the listener whose port"
+                    + " it arrived on. For the Pulsar binary protocol 
(`pulsar` / `pulsar+ssl`) this is"
+                    + " optional — clients can also pass an explicit 
`listenerName`. For the HTTP/HTTPS"
+                    + " Admin API (`http` / `https`) it is the only routing 
mechanism, so every"
+                    + " HTTP/HTTPS advertised listener reachable from outside 
the cluster needs a"
+                    + " dedicated entry here on a unique port. This lets a 
layer-4 TCP load balancer"
+                    + " serve the Admin API directly per listener, without an 
HTTP reverse proxy.\n"
+                    + " Format: comma-separated 
`<listener_name>:<scheme>://<ip>:<port>` entries."
                     + " Supported schemes: `pulsar`, `pulsar+ssl`, `http`, 
`https`."
                     + " The `<ip>` part selects which local network interface 
the port binds to:"
                     + " use a specific local IP, or `0.0.0.0` to bind on all 
interfaces."
                     + " A local hostname is also accepted but not 
recommended.\n"
-                    + " At runtime, the legacy `brokerServicePort`, 
`brokerServicePortTls`,"
-                    + " `webServicePort`, and `webServicePortTls` properties 
are migrated into the bind"
-                    + " address list (using `bindAddress`, default `0.0.0.0`, 
as the IP) under the listener"
-                    + " named by `internalListenerName`, and merged with the 
entries declared here.\n"
-                    + " Each `ip:port` may be bound by exactly one (listener, 
scheme) pair — a TCP socket"
-                    + " cannot serve two protocol schemes at once. An entry 
here that exactly matches a"
-                    + " migrated legacy binding (same 
`listener:scheme://ip:port`) is tolerated; assigning"
-                    + " the same `ip:port` to a different listener or to a 
different scheme fails"
-                    + " validation.\n"
-                    + " Binding the same listener to multiple ports of the 
same scheme is allowed but rarely"
-                    + " useful.")
+                    + " Each `ip:port` may be bound by exactly one (listener, 
scheme) pair. An entry"
+                    + " that exactly matches the auto-derived 
internal-listener binding is tolerated;"
+                    + " assigning the same `ip:port` to a different listener 
or scheme fails validation.")
     private String bindAddresses;
 
     @FieldContext(category = CATEGORY_SERVER,

Reply via email to