From 36b08b81fb40a89a4e642ab46c8c7975dcfb7ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 2 Jul 2025 17:22:57 +0200 Subject: [PATCH 1/6] Rewrite listener docs to be clearer --- .../listener-operator/pages/listener.adoc | 85 ++++++++++++++++--- .../listener-operator/pages/volume.adoc | 3 +- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/docs/modules/listener-operator/pages/listener.adoc b/docs/modules/listener-operator/pages/listener.adoc index 5d49edcb..8fad650f 100644 --- a/docs/modules/listener-operator/pages/listener.adoc +++ b/docs/modules/listener-operator/pages/listener.adoc @@ -1,31 +1,92 @@ = Listener :description: The Listener exposes Pods based on ListenerClass rules, provides address info via Ingress_addresses, supports PVC mounting, and enables sticky scheduling. -A Listener object exposes a set of Pods according to the rules of a xref:listenerclass.adoc[], but it also adds a couple of other features that are useful for the Stackable data platform at large. +:fn-kubernetes-service: footnote:[It is actually implemented using them, but don't rely on that.] -== ListenerClass +A Listener object represents a single exposed (possibly) load-balanced service that clients can connect to. +It can be thought of as the Stackable Data Platform equivalent of a Kubernetes https://kubernetes.io/docs/concepts/services-networking/service/[Service].{fn-kubernetes-service} -The exact rules of pod exposure are dictated by the specified xref:listenerclass.adoc[], which allow a single Listener definition to be reused in different clusters, regardless of the Kubernetes distribution or cloud provider. +The mechanism for the service is controlled by the xref:listenerclass.adoc[]. +This way, which a single Listener definition can be reused in different clusters, expressing the same _intent_ regardless of the Kubernetes distribution or cloud provider's limitations. + +Listeners only direct traffic to Pods that also mount them as a xref:volume.adoc[volume]. +The volume allows the operator to xref:#pinning[pin] the Pod to a specific Node, and provides an API for workloads to retrieve their external address. == Address API -A Listener writes back all addresses that it can be reached on to `Listener.status.ingress_addresses`, which can then be used to generate discovery information. -Contrary to Kubernetes' Service, this is done regardless of the type of service, and transparently also contains information about remapped ports. +NOTE: The CRD-based API is intended for external clients that need to retrieve the address. + The workload can retrieve _its own_ address(es) by using the xref:volume.adoc#downwards-api[downwards API]. -== Address volume projection +A Listener writes back all addresses that it can be reached on to `Listener.status.ingressAddresses`, which can then be used to connect to the service (generate discovery information). +Compared to Kubernetes' Services, this list is provided _regardless_ of the type of the backing Service. -Listener objects can be mounted into a Pod as a PersistentVolumeClaim (PVC), which contains information about how the Pod should request that external clients refer to it. +NOTE: All addresses may not be able to reach all backing Pods. Clients should connect to a _random_ address in the list, not just the first one. -For example, if the volume is mounted to `/stackable/listener`, the primary address can be read from `/stackable/listener/default-address/address`, and the public `http` port number can be read from `/stackable/listener/default-address/ports/http`. +Ports may be remapped from the Service definition. +Never assume that the exposed on an address will match your declared port. +Instead, read the port numbers from `.ports.\{portname\}`. +Otherwise, it will break when using NodePort services. == Per-replica listeners -A Listener PVC can also specify a xref:listenerclass.adoc[] rather than a Listener, in which case a Listener object is created automatically. -These PVCs can automatically be created for each replica using either StatefulSet's `volumeClaimTemplates` (for long-lived listeners that will be kept across replica restarts and upgrades) or Pod's `volumes[].ephemeral` (for temporary listeners that are deleted when their corresponding Pod is deleted). +A Listener volume can also specify a xref:listenerclass.adoc[] rather than a Listener, in which case a Listener object is created automatically for each volume. + +These volumes, in turn, can automatically be created for each replica using either: + +- StatefulSet's `volumeClaimTemplates` (for long-lived listeners that will be kept across replica restarts and upgrades), or +- Pod's `volumes[].ephemeral` (for temporary listeners that are deleted when their corresponding Pod is deleted) -== Sticky scheduling +== Pinning -When mounting a Listener PVC, it will be made "sticky" to that node if the xref:listenerclass.adoc[] uses a strategy that depends on the node that the workload is running on. +When mounting a Listener PVC, it will be "pinned" to that node if the xref:listenerclass.adoc[] uses a strategy that depends on the node that the workload is running on. Keep in mind that this will only work correctly when using long-lived PVCs (such as via StatefulSet's `volumeClaimTemplates`). Ephemeral PVCs will be "reset" for every pod that is created, even if they refer to a long-lived Listener object. + +[#reference] +== Reference + +[source,yaml] +---- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: Listener +metadata: + name: my-listener +spec: + className: external-unstable + ports: + - name: http + port: 9864 + protocol: TCP + extraPodSelectorLabels: + foo: bar + publishNotReadyAddresses: true +status: + ingressAddresses: + - address: 172.18.0.3 + addressType: IP + ports: + http: 32222 + nodePorts: + http: 32222 + serviceName: my-listener +---- + +`spec.className`:: The name of the xref:listenerclass.adoc[] to use. +`spec.ports`:: The ports exposed from the backing Pods. +`spec.ports.name`:: The name of the port. +`spec.ports.port`:: The number of the port. This must match the port number exposed by the container. +`spec.ports.protocol`:: The IP protocol (TCP/UDP/SCTP). Defaults to TCP. +`spec.extraPodSelectorLabels`:: Traffic will only be forwarded to Pods that apply these labels. + This field exists for exceptional cases, where Pods sometimes want to stop receiving traffic based on some dynamic condition. + Normal target selection should use xref:volume.adoc[Listener volumes] instead. + (Volumes are still required when using `extraPodSelectorLabels`.) +`spec.publishNotReadyAddresses`:: If false, traffic will only be directed to Pods that are Ready. If true, traffic will be directed to any running Pod. Defaults to true. +`status.ingressAddresses`:: A list of all addresses that the Listener can be reached on. See xref:#address-api[]. +`status.ingressAddresses.address`:: The hostname or IP address of this Listener. +`status.ingressAddresses.addressType`:: `IP` if `address` is an IP address, `Hostname` if it is a hostname. +`status.ingressAddresses.ports.\{portName\}`:: The _exposed_ port number for a given port name (as defined in `.spec.ports`). Note that this may be different than the port specified in `.spec.ports.port``. +`status.nodePorts.\{portName\}`:: For internal use only. + You probably want to use `.status.ingressAddresses` instead. + _If_ the ListenerClass is configured to use xref:listenerclass.adoc#servicetype-nodeport[NodePort] then this is the port number that each port is accessible on on its respective Node. +`status.serviceName`: The name of the Kubernetes Service object backing this Listener. diff --git a/docs/modules/listener-operator/pages/volume.adoc b/docs/modules/listener-operator/pages/volume.adoc index e86882f4..27648c06 100644 --- a/docs/modules/listener-operator/pages/volume.adoc +++ b/docs/modules/listener-operator/pages/volume.adoc @@ -12,7 +12,8 @@ When this address must be configured statically in clients (such as for HDFS Nam Mounting listeners into Pods as PersistentVolume allows the Listener Operator to pin these workloads to one node. Note that this only happens for xref:listenerclass.adoc[]es that actually benefit from pinning. -== Pod metadata injection +[#downwards-api] +== Downwards API Some services (such as Kafka) need to know their external address, so that they can advertize it to their own replica discovery mechanism. xref:listener.adoc[] volumes contain a file tree that exposes this information: From cd676dd680bcc510ae949d43a65508545df17086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 2 Jul 2025 17:25:53 +0200 Subject: [PATCH 2/6] s/PVC/volume --- docs/modules/listener-operator/pages/listener.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/listener-operator/pages/listener.adoc b/docs/modules/listener-operator/pages/listener.adoc index 8fad650f..41fe6c25 100644 --- a/docs/modules/listener-operator/pages/listener.adoc +++ b/docs/modules/listener-operator/pages/listener.adoc @@ -38,10 +38,10 @@ These volumes, in turn, can automatically be created for each replica using eith == Pinning -When mounting a Listener PVC, it will be "pinned" to that node if the xref:listenerclass.adoc[] uses a strategy that depends on the node that the workload is running on. +When mounting a Listener volume, it will be "pinned" to that node if the xref:listenerclass.adoc[] uses a strategy that depends on the node that the workload is running on. -Keep in mind that this will only work correctly when using long-lived PVCs (such as via StatefulSet's `volumeClaimTemplates`). -Ephemeral PVCs will be "reset" for every pod that is created, even if they refer to a long-lived Listener object. +Keep in mind that this will only work correctly when using long-lived volumes (such as via StatefulSet's `volumeClaimTemplates`). +Ephemeral volumes will be "reset" for every pod that is created, even if they refer to a long-lived Listener object. [#reference] == Reference From 583e4ad0c53c36e303434ee3118ea1d4f3050134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 2 Jul 2025 17:34:10 +0200 Subject: [PATCH 3/6] Fix some old naming in usage doc --- .../listener-operator/examples/listenerclass-public-gke.yaml | 2 +- .../listener-operator/examples/listenerclass-public-onprem.yaml | 2 +- docs/modules/listener-operator/pages/listenerclass.adoc | 1 + docs/modules/listener-operator/pages/usage.adoc | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/modules/listener-operator/examples/listenerclass-public-gke.yaml b/docs/modules/listener-operator/examples/listenerclass-public-gke.yaml index 7709281b..fd03383d 100644 --- a/docs/modules/listener-operator/examples/listenerclass-public-gke.yaml +++ b/docs/modules/listener-operator/examples/listenerclass-public-gke.yaml @@ -2,6 +2,6 @@ apiVersion: listeners.stackable.tech/v1alpha1 kind: ListenerClass metadata: - name: public + name: external-stable spec: serviceType: LoadBalancer diff --git a/docs/modules/listener-operator/examples/listenerclass-public-onprem.yaml b/docs/modules/listener-operator/examples/listenerclass-public-onprem.yaml index ed27bace..60cddbee 100644 --- a/docs/modules/listener-operator/examples/listenerclass-public-onprem.yaml +++ b/docs/modules/listener-operator/examples/listenerclass-public-onprem.yaml @@ -2,6 +2,6 @@ apiVersion: listeners.stackable.tech/v1alpha1 kind: ListenerClass metadata: - name: public + name: external-stable spec: serviceType: NodePort diff --git a/docs/modules/listener-operator/pages/listenerclass.adoc b/docs/modules/listener-operator/pages/listenerclass.adoc index ddd88a9f..24581e01 100644 --- a/docs/modules/listener-operator/pages/listenerclass.adoc +++ b/docs/modules/listener-operator/pages/listenerclass.adoc @@ -113,6 +113,7 @@ The Stackable Data Platform assumes the existence of a few predefined ListenerCl `external-unstable`:: Used for listeners that are accessible from outside the cluster, but which do not require a stable address. For example: individual Kafka brokers. `external-stable`:: Used for listeners that are accessible from outside the cluster, and do require a stable address. For example: Kafka bootstrap. +[#presets] === Presets To help users get started, the Stackable Listener Operator ships different ListenerClass _presets_ for different environments. diff --git a/docs/modules/listener-operator/pages/usage.adoc b/docs/modules/listener-operator/pages/usage.adoc index d9dd623c..81b40a0d 100644 --- a/docs/modules/listener-operator/pages/usage.adoc +++ b/docs/modules/listener-operator/pages/usage.adoc @@ -25,3 +25,5 @@ Or like this for on-premise environments: ---- include::example$listenerclass-public-onprem.yaml[] ---- + +These are normally installed by the appropriate xref:listenerclass.adoc#presets[preset]. From 1dcf5ca19c4167cc13e58de471e8d5511d119892 Mon Sep 17 00:00:00 2001 From: xeniape Date: Mon, 7 Jul 2025 13:27:22 +0200 Subject: [PATCH 4/6] small link and wording fixes --- docs/modules/listener-operator/pages/listener.adoc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/modules/listener-operator/pages/listener.adoc b/docs/modules/listener-operator/pages/listener.adoc index 41fe6c25..7228ea15 100644 --- a/docs/modules/listener-operator/pages/listener.adoc +++ b/docs/modules/listener-operator/pages/listener.adoc @@ -4,14 +4,15 @@ :fn-kubernetes-service: footnote:[It is actually implemented using them, but don't rely on that.] A Listener object represents a single exposed (possibly) load-balanced service that clients can connect to. -It can be thought of as the Stackable Data Platform equivalent of a Kubernetes https://kubernetes.io/docs/concepts/services-networking/service/[Service].{fn-kubernetes-service} +It can be thought of as the Stackable Data Platform equivalent of a Kubernetes https://kubernetes.io/docs/concepts/services-networking/service/[Service,window=_blank].{fn-kubernetes-service} The mechanism for the service is controlled by the xref:listenerclass.adoc[]. -This way, which a single Listener definition can be reused in different clusters, expressing the same _intent_ regardless of the Kubernetes distribution or cloud provider's limitations. +This way, a single Listener definition can be reused in different clusters, expressing the same _intent_ regardless of the Kubernetes distribution or cloud provider's limitations. Listeners only direct traffic to Pods that also mount them as a xref:volume.adoc[volume]. The volume allows the operator to xref:#pinning[pin] the Pod to a specific Node, and provides an API for workloads to retrieve their external address. +[#address-api] == Address API NOTE: The CRD-based API is intended for external clients that need to retrieve the address. @@ -23,7 +24,7 @@ Compared to Kubernetes' Services, this list is provided _regardless_ of the type NOTE: All addresses may not be able to reach all backing Pods. Clients should connect to a _random_ address in the list, not just the first one. Ports may be remapped from the Service definition. -Never assume that the exposed on an address will match your declared port. +Never assume that the exposed port on an address will match your declared port. Instead, read the port numbers from `.ports.\{portname\}`. Otherwise, it will break when using NodePort services. @@ -36,6 +37,7 @@ These volumes, in turn, can automatically be created for each replica using eith - StatefulSet's `volumeClaimTemplates` (for long-lived listeners that will be kept across replica restarts and upgrades), or - Pod's `volumes[].ephemeral` (for temporary listeners that are deleted when their corresponding Pod is deleted) +[#pinning] == Pinning When mounting a Listener volume, it will be "pinned" to that node if the xref:listenerclass.adoc[] uses a strategy that depends on the node that the workload is running on. From 594a7b372f29333e574259b1f21a7405c9b0ad95 Mon Sep 17 00:00:00 2001 From: xeniape Date: Wed, 9 Jul 2025 08:09:50 +0200 Subject: [PATCH 5/6] small fix --- docs/modules/listener-operator/pages/listener.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/listener-operator/pages/listener.adoc b/docs/modules/listener-operator/pages/listener.adoc index 7228ea15..91ead2d7 100644 --- a/docs/modules/listener-operator/pages/listener.adoc +++ b/docs/modules/listener-operator/pages/listener.adoc @@ -91,4 +91,4 @@ status: `status.nodePorts.\{portName\}`:: For internal use only. You probably want to use `.status.ingressAddresses` instead. _If_ the ListenerClass is configured to use xref:listenerclass.adoc#servicetype-nodeport[NodePort] then this is the port number that each port is accessible on on its respective Node. -`status.serviceName`: The name of the Kubernetes Service object backing this Listener. +`status.serviceName`:: The name of the Kubernetes Service object backing this Listener. From ce87d1334b4a332c63e827bb624ac94c9485c01a Mon Sep 17 00:00:00 2001 From: xeniape Date: Wed, 9 Jul 2025 10:43:28 +0200 Subject: [PATCH 6/6] add newline after each full stop --- .../modules/listener-operator/pages/listener.adoc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/modules/listener-operator/pages/listener.adoc b/docs/modules/listener-operator/pages/listener.adoc index 91ead2d7..d45e3715 100644 --- a/docs/modules/listener-operator/pages/listener.adoc +++ b/docs/modules/listener-operator/pages/listener.adoc @@ -77,17 +77,22 @@ status: `spec.className`:: The name of the xref:listenerclass.adoc[] to use. `spec.ports`:: The ports exposed from the backing Pods. `spec.ports.name`:: The name of the port. -`spec.ports.port`:: The number of the port. This must match the port number exposed by the container. -`spec.ports.protocol`:: The IP protocol (TCP/UDP/SCTP). Defaults to TCP. +`spec.ports.port`:: The number of the port. + This must match the port number exposed by the container. +`spec.ports.protocol`:: The IP protocol (TCP/UDP/SCTP). + Defaults to TCP. `spec.extraPodSelectorLabels`:: Traffic will only be forwarded to Pods that apply these labels. This field exists for exceptional cases, where Pods sometimes want to stop receiving traffic based on some dynamic condition. Normal target selection should use xref:volume.adoc[Listener volumes] instead. (Volumes are still required when using `extraPodSelectorLabels`.) -`spec.publishNotReadyAddresses`:: If false, traffic will only be directed to Pods that are Ready. If true, traffic will be directed to any running Pod. Defaults to true. -`status.ingressAddresses`:: A list of all addresses that the Listener can be reached on. See xref:#address-api[]. +`spec.publishNotReadyAddresses`:: If false, traffic will only be directed to Pods that are Ready. + If true, traffic will be directed to any running Pod. Defaults to true. +`status.ingressAddresses`:: A list of all addresses that the Listener can be reached on. + See xref:#address-api[]. `status.ingressAddresses.address`:: The hostname or IP address of this Listener. `status.ingressAddresses.addressType`:: `IP` if `address` is an IP address, `Hostname` if it is a hostname. -`status.ingressAddresses.ports.\{portName\}`:: The _exposed_ port number for a given port name (as defined in `.spec.ports`). Note that this may be different than the port specified in `.spec.ports.port``. +`status.ingressAddresses.ports.\{portName\}`:: The _exposed_ port number for a given port name (as defined in `.spec.ports`). + Note that this may be different than the port specified in `.spec.ports.port``. `status.nodePorts.\{portName\}`:: For internal use only. You probably want to use `.status.ingressAddresses` instead. _If_ the ListenerClass is configured to use xref:listenerclass.adoc#servicetype-nodeport[NodePort] then this is the port number that each port is accessible on on its respective Node.