From 6a76dc2b37cf0d5c1cf7200c324d668d4366dfa7 Mon Sep 17 00:00:00 2001 From: Lorenzo Affetti Date: Fri, 18 Sep 2026 15:12:43 +0200 Subject: [PATCH] [helm] Add opt-in release-scoped resource names Every resource the chart creates had a fixed name (coordinator-server, tablet-server, fluss-conf-file and the matching headless Services), and every cluster used the ZooKeeper root path /fluss. Two releases could therefore not share a namespace, which is a problem wherever namespace creation is restricted and a team needs more than one Fluss cluster. Route every name through a helper and add releaseScopedResourceNames, which prefixes each resource with fluss.fullname and moves the ZooKeeper root path to /fluss/. The option is off by default. Renaming a StatefulSet makes Helm replace it and orphans its PersistentVolumeClaims, so an existing release must opt in deliberately. With the option off the rendered output is byte-for-byte identical to before. Also validate that generated names stay within the 63 character DNS label limit when the option is on, and document both the multi-cluster setup and the migration path for an existing release. Co-Authored-By: Claude Opus 5 --- helm/templates/_helpers.tpl | 89 ++++++ helm/templates/_validate.tpl | 19 ++ helm/templates/configmap.yaml | 2 +- helm/templates/pdb-coordinator.yaml | 2 +- helm/templates/pdb-tablet.yaml | 2 +- helm/templates/sts-coordinator.yaml | 8 +- helm/templates/sts-tablet.yaml | 8 +- helm/templates/svc-coordinator.yaml | 2 +- helm/templates/svc-metrics-coordinator.yaml | 2 +- helm/templates/svc-metrics-tablet.yaml | 2 +- helm/templates/svc-tablet.yaml | 2 +- helm/tests/naming_test.yaml | 262 ++++++++++++++++++ helm/values.yaml | 13 +- .../install-deploy/deploying-with-helm.md | 76 ++++- 14 files changed, 472 insertions(+), 17 deletions(-) create mode 100644 helm/tests/naming_test.yaml diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 3933debca68..7a3a12a5be9 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -131,3 +131,92 @@ imagePullSecrets: {{- end -}} {{- end -}} +{{/* +Component resource names. + +By default every resource uses a fixed name, so a namespace can hold only one +Fluss release. Setting releaseScopedResourceNames=true prefixes every resource +with "fluss.fullname", which lets several releases coexist in one namespace. + +Fixed names are the default so that an existing release keeps upgrading in +place: renaming a StatefulSet makes Helm replace it and orphans its +PersistentVolumeClaims. The default is expected to flip once the option has +been available for a release cycle. +*/}} + +{{/* +Name of the coordinator StatefulSet and PodDisruptionBudget. +*/}} +{{- define "fluss.coordinator.fullname" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- printf "%s-coordinator-server" (include "fluss.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- "coordinator-server" -}} +{{- end -}} +{{- end -}} + +{{/* +Name of the coordinator headless Service. +*/}} +{{- define "fluss.coordinator.serviceName" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- printf "%s-coordinator-server-hs" (include "fluss.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- "coordinator-server-hs" -}} +{{- end -}} +{{- end -}} + +{{/* +Name of the coordinator metrics headless Service. +*/}} +{{- define "fluss.coordinator.metricsServiceName" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- printf "%s-coordinator-server-metrics-hs" (include "fluss.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-coordinator-server-metrics-hs" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Name of the tablet StatefulSet and PodDisruptionBudget. +*/}} +{{- define "fluss.tablet.fullname" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- printf "%s-tablet-server" (include "fluss.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- "tablet-server" -}} +{{- end -}} +{{- end -}} + +{{/* +Name of the tablet headless Service. +*/}} +{{- define "fluss.tablet.serviceName" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- printf "%s-tablet-server-hs" (include "fluss.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- "tablet-server-hs" -}} +{{- end -}} +{{- end -}} + +{{/* +Name of the tablet metrics headless Service. +*/}} +{{- define "fluss.tablet.metricsServiceName" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- printf "%s-tablet-server-metrics-hs" (include "fluss.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-tablet-server-metrics-hs" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Name of the ConfigMap holding server.yaml. +*/}} +{{- define "fluss.configMapName" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- printf "%s-conf-file" (include "fluss.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- "fluss-conf-file" -}} +{{- end -}} +{{- end -}} diff --git a/helm/templates/_validate.tpl b/helm/templates/_validate.tpl index d79221c97f7..111621a334b 100644 --- a/helm/templates/_validate.tpl +++ b/helm/templates/_validate.tpl @@ -41,6 +41,7 @@ Usage: {{- $messages = append $messages (include "fluss.security.validateError" .) -}} {{- $messages = append $messages (include "fluss.metrics.validateError" .) -}} {{- $messages = append $messages (include "fluss.secrets.validateError" .) -}} +{{- $messages = append $messages (include "fluss.names.validateError" .) -}} {{- $messages = without $messages "" -}} {{- join "\n" $messages -}} @@ -73,3 +74,21 @@ Usage: {{- end -}} {{- end -}} + +{{/* +Validates that the generated resource names stay within the 63 character limit +Kubernetes imposes on DNS labels. Only applies with releaseScopedResourceNames, +where the longest generated name adds 30 characters to the release prefix. +Usage: + include "fluss.names.validateError" . +*/}} +{{- define "fluss.names.validateError" -}} +{{- if .Values.releaseScopedResourceNames -}} +{{- $prefix := include "fluss.fullname" . -}} +{{- $longestSuffix := 30 -}} +{{- $maxPrefix := sub 63 $longestSuffix -}} +{{- if gt (len $prefix) (int $maxPrefix) -}} +{{- printf "resource name prefix %q is %d characters, but generated names must stay within 63 characters. Use a release name of at most %d characters, or set fullnameOverride." $prefix (len $prefix) (int $maxPrefix) -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm/templates/configmap.yaml b/helm/templates/configmap.yaml index bfaf0a74c91..8863d87d45f 100644 --- a/helm/templates/configmap.yaml +++ b/helm/templates/configmap.yaml @@ -19,7 +19,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: fluss-conf-file + name: {{ include "fluss.configMapName" . }} labels: {{- include "fluss.labels" . | nindent 4 }} data: diff --git a/helm/templates/pdb-coordinator.yaml b/helm/templates/pdb-coordinator.yaml index 79e03f34512..9a5bcf9b5e1 100644 --- a/helm/templates/pdb-coordinator.yaml +++ b/helm/templates/pdb-coordinator.yaml @@ -20,7 +20,7 @@ apiVersion: policy/v1 kind: PodDisruptionBudget metadata: - name: coordinator-server + name: {{ include "fluss.coordinator.fullname" . }} labels: {{- include "fluss.labels" . | nindent 4 }} spec: diff --git a/helm/templates/pdb-tablet.yaml b/helm/templates/pdb-tablet.yaml index ce7641efdb8..80ebd310b90 100644 --- a/helm/templates/pdb-tablet.yaml +++ b/helm/templates/pdb-tablet.yaml @@ -20,7 +20,7 @@ apiVersion: policy/v1 kind: PodDisruptionBudget metadata: - name: tablet-server + name: {{ include "fluss.tablet.fullname" . }} labels: {{- include "fluss.labels" . | nindent 4 }} spec: diff --git a/helm/templates/sts-coordinator.yaml b/helm/templates/sts-coordinator.yaml index 583cfcd9cbd..7532183fa74 100644 --- a/helm/templates/sts-coordinator.yaml +++ b/helm/templates/sts-coordinator.yaml @@ -19,7 +19,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: - name: coordinator-server + name: {{ include "fluss.coordinator.fullname" . }} labels: {{- include "fluss.labels" . | nindent 4 }} {{- with .Values.coordinator.annotations }} @@ -27,7 +27,7 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} spec: - serviceName: coordinator-server-hs + serviceName: {{ include "fluss.coordinator.serviceName" . }} replicas: {{ .Values.coordinator.numberOfReplicas }} updateStrategy: type: RollingUpdate @@ -125,7 +125,7 @@ spec: cp /opt/conf/server.yaml $FLUSS_HOME/conf && \ BIND_LISTENERS="INTERNAL://${POD_IP}:{{ .Values.listeners.internal.port }}, CLIENT://${POD_IP}:{{ .Values.listeners.client.port }}" && \ - ADVERTISED_LISTENERS="CLIENT://${POD_NAME}.coordinator-server-hs.${POD_NAMESPACE}.svc.cluster.local:{{ .Values.listeners.client.port }}" && \ + ADVERTISED_LISTENERS="CLIENT://${POD_NAME}.{{ include "fluss.coordinator.serviceName" . }}.${POD_NAMESPACE}.svc.cluster.local:{{ .Values.listeners.client.port }}" && \ echo "" >> $FLUSS_HOME/conf/server.yaml && \ echo "bind.listeners: ${BIND_LISTENERS}" >> $FLUSS_HOME/conf/server.yaml && \ @@ -167,7 +167,7 @@ spec: volumes: - name: fluss-conf configMap: - name: fluss-conf-file + name: {{ include "fluss.configMapName" . }} {{- if not .Values.coordinator.storage.enabled }} - name: data emptyDir: {} diff --git a/helm/templates/sts-tablet.yaml b/helm/templates/sts-tablet.yaml index 5407b2a6a95..93ce7c47367 100644 --- a/helm/templates/sts-tablet.yaml +++ b/helm/templates/sts-tablet.yaml @@ -19,7 +19,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: - name: tablet-server + name: {{ include "fluss.tablet.fullname" . }} labels: {{- include "fluss.labels" . | nindent 4 }} {{- with .Values.tablet.annotations }} @@ -27,7 +27,7 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} spec: - serviceName: tablet-server-hs + serviceName: {{ include "fluss.tablet.serviceName" . }} replicas: {{ .Values.tablet.numberOfReplicas }} updateStrategy: type: RollingUpdate @@ -121,7 +121,7 @@ spec: cp /opt/conf/server.yaml $FLUSS_HOME/conf && \ BIND_LISTENERS="INTERNAL://${POD_IP}:{{ .Values.listeners.internal.port }}, CLIENT://${POD_IP}:{{ .Values.listeners.client.port }}" && \ - ADVERTISED_LISTENERS="CLIENT://${POD_NAME}.tablet-server-hs.${POD_NAMESPACE}.svc.cluster.local:{{ .Values.listeners.client.port }}" && \ + ADVERTISED_LISTENERS="CLIENT://${POD_NAME}.{{ include "fluss.tablet.serviceName" . }}.${POD_NAMESPACE}.svc.cluster.local:{{ .Values.listeners.client.port }}" && \ echo "" >> $FLUSS_HOME/conf/server.yaml && \ echo "tablet-server.id: ${FLUSS_SERVER_ID}" >> $FLUSS_HOME/conf/server.yaml && \ @@ -180,7 +180,7 @@ spec: volumes: - name: fluss-conf configMap: - name: fluss-conf-file + name: {{ include "fluss.configMapName" . }} {{- if not .Values.tablet.storage.enabled }} - name: data emptyDir: {} diff --git a/helm/templates/svc-coordinator.yaml b/helm/templates/svc-coordinator.yaml index c0e87fa37f4..87e1c38ec97 100644 --- a/helm/templates/svc-coordinator.yaml +++ b/helm/templates/svc-coordinator.yaml @@ -19,7 +19,7 @@ apiVersion: v1 kind: Service metadata: - name: coordinator-server-hs + name: {{ include "fluss.coordinator.serviceName" . }} labels: {{- include "fluss.labels" . | nindent 4 }} app.kubernetes.io/component: coordinator diff --git a/helm/templates/svc-metrics-coordinator.yaml b/helm/templates/svc-metrics-coordinator.yaml index 77bbd94bf4b..950aed46057 100644 --- a/helm/templates/svc-metrics-coordinator.yaml +++ b/helm/templates/svc-metrics-coordinator.yaml @@ -22,7 +22,7 @@ apiVersion: v1 kind: Service metadata: - name: {{ .Release.Name }}-coordinator-server-metrics-hs + name: {{ include "fluss.coordinator.metricsServiceName" . }} labels: {{- include "fluss.labels" . | nindent 4 }} app.kubernetes.io/component: metrics diff --git a/helm/templates/svc-metrics-tablet.yaml b/helm/templates/svc-metrics-tablet.yaml index 2ecb597129c..ed1cfb7a3f8 100644 --- a/helm/templates/svc-metrics-tablet.yaml +++ b/helm/templates/svc-metrics-tablet.yaml @@ -22,7 +22,7 @@ apiVersion: v1 kind: Service metadata: - name: {{ .Release.Name }}-tablet-server-metrics-hs + name: {{ include "fluss.tablet.metricsServiceName" . }} labels: {{- include "fluss.labels" . | nindent 4 }} app.kubernetes.io/component: metrics diff --git a/helm/templates/svc-tablet.yaml b/helm/templates/svc-tablet.yaml index df61b1be0b2..6cbed4bf258 100644 --- a/helm/templates/svc-tablet.yaml +++ b/helm/templates/svc-tablet.yaml @@ -19,7 +19,7 @@ apiVersion: v1 kind: Service metadata: - name: tablet-server-hs + name: {{ include "fluss.tablet.serviceName" . }} labels: {{- include "fluss.labels" . | nindent 4 }} app.kubernetes.io/component: tablet diff --git a/helm/tests/naming_test.yaml b/helm/tests/naming_test.yaml new file mode 100644 index 00000000000..b20294c8951 --- /dev/null +++ b/helm/tests/naming_test.yaml @@ -0,0 +1,262 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +suite: fixed-names-by-default +templates: + - templates/configmap.yaml + - templates/sts-coordinator.yaml + - templates/sts-tablet.yaml + - templates/svc-coordinator.yaml + - templates/svc-tablet.yaml + - templates/pdb-coordinator.yaml + - templates/pdb-tablet.yaml + - templates/svc-metrics-coordinator.yaml + - templates/svc-metrics-tablet.yaml +release: + name: alpha +set: + coordinator.podDisruptionBudget: + enabled: true + maxUnavailable: 1 + tablet.podDisruptionBudget: + enabled: true + maxUnavailable: 1 + metrics.reporters: prometheus + metrics.prometheus: + port: 9249 + +tests: + - it: should keep the fixed resource names when the option is off + asserts: + - equal: + path: metadata.name + value: fluss-conf-file + template: templates/configmap.yaml + - equal: + path: metadata.name + value: coordinator-server + template: templates/sts-coordinator.yaml + - equal: + path: metadata.name + value: tablet-server + template: templates/sts-tablet.yaml + - equal: + path: metadata.name + value: coordinator-server-hs + template: templates/svc-coordinator.yaml + - equal: + path: metadata.name + value: tablet-server-hs + template: templates/svc-tablet.yaml + - equal: + path: metadata.name + value: coordinator-server + template: templates/pdb-coordinator.yaml + - equal: + path: metadata.name + value: tablet-server + template: templates/pdb-tablet.yaml + - equal: + path: metadata.name + value: alpha-coordinator-server-metrics-hs + template: templates/svc-metrics-coordinator.yaml + - equal: + path: metadata.name + value: alpha-tablet-server-metrics-hs + template: templates/svc-metrics-tablet.yaml + + - it: should keep the fixed cross references when the option is off + asserts: + - equal: + path: spec.serviceName + value: coordinator-server-hs + template: templates/sts-coordinator.yaml + - equal: + path: spec.serviceName + value: tablet-server-hs + template: templates/sts-tablet.yaml + - contains: + path: spec.template.spec.volumes + content: + name: fluss-conf + configMap: + name: fluss-conf-file + template: templates/sts-tablet.yaml + - matchRegex: + path: spec.template.spec.containers[0].command[2] + pattern: 'ADVERTISED_LISTENERS="CLIENT://\$\{POD_NAME\}\.coordinator-server-hs\.' + template: templates/sts-coordinator.yaml + +--- + +suite: release-scoped-names +templates: + - templates/configmap.yaml + - templates/sts-coordinator.yaml + - templates/sts-tablet.yaml + - templates/svc-coordinator.yaml + - templates/svc-tablet.yaml + - templates/pdb-coordinator.yaml + - templates/pdb-tablet.yaml + - templates/svc-metrics-coordinator.yaml + - templates/svc-metrics-tablet.yaml +release: + name: alpha +set: + releaseScopedResourceNames: true + coordinator.podDisruptionBudget: + enabled: true + maxUnavailable: 1 + tablet.podDisruptionBudget: + enabled: true + maxUnavailable: 1 + metrics.reporters: prometheus + metrics.prometheus: + port: 9249 + +tests: + - it: should prefix every resource name with the release name + asserts: + - equal: + path: metadata.name + value: alpha-fluss-conf-file + template: templates/configmap.yaml + - equal: + path: metadata.name + value: alpha-fluss-coordinator-server + template: templates/sts-coordinator.yaml + - equal: + path: metadata.name + value: alpha-fluss-tablet-server + template: templates/sts-tablet.yaml + - equal: + path: metadata.name + value: alpha-fluss-coordinator-server-hs + template: templates/svc-coordinator.yaml + - equal: + path: metadata.name + value: alpha-fluss-tablet-server-hs + template: templates/svc-tablet.yaml + - equal: + path: metadata.name + value: alpha-fluss-coordinator-server + template: templates/pdb-coordinator.yaml + - equal: + path: metadata.name + value: alpha-fluss-tablet-server + template: templates/pdb-tablet.yaml + - equal: + path: metadata.name + value: alpha-fluss-coordinator-server-metrics-hs + template: templates/svc-metrics-coordinator.yaml + - equal: + path: metadata.name + value: alpha-fluss-tablet-server-metrics-hs + template: templates/svc-metrics-tablet.yaml + + - it: should point the StatefulSets at the release-scoped headless Services + asserts: + - equal: + path: spec.serviceName + value: alpha-fluss-coordinator-server-hs + template: templates/sts-coordinator.yaml + - equal: + path: spec.serviceName + value: alpha-fluss-tablet-server-hs + template: templates/sts-tablet.yaml + + - it: should mount the release-scoped ConfigMap + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: fluss-conf + configMap: + name: alpha-fluss-conf-file + template: templates/sts-coordinator.yaml + - contains: + path: spec.template.spec.volumes + content: + name: fluss-conf + configMap: + name: alpha-fluss-conf-file + template: templates/sts-tablet.yaml + + - it: should advertise the release-scoped Service FQDN + asserts: + - matchRegex: + path: spec.template.spec.containers[0].command[2] + pattern: 'ADVERTISED_LISTENERS="CLIENT://\$\{POD_NAME\}\.alpha-fluss-coordinator-server-hs\.' + template: templates/sts-coordinator.yaml + - matchRegex: + path: spec.template.spec.containers[0].command[2] + pattern: 'ADVERTISED_LISTENERS="CLIENT://\$\{POD_NAME\}\.alpha-fluss-tablet-server-hs\.' + template: templates/sts-tablet.yaml + +--- + +suite: zookeeper-root-path +templates: + - templates/configmap.yaml +release: + name: alpha + +tests: + - it: should keep the shared ZooKeeper root path by default + asserts: + - matchRegex: + path: data["server.yaml"] + pattern: 'zookeeper\.path\.root: /fluss\n' + + - it: should scope the ZooKeeper root path to the release when opted in + set: + releaseScopedResourceNames: true + asserts: + - matchRegex: + path: data["server.yaml"] + pattern: 'zookeeper\.path\.root: /fluss/alpha' + +--- + +suite: name-length-validation +templates: + - templates/NOTES.txt + +tests: + - it: should fail when the release name makes generated names too long + release: + name: a-very-long-fluss-release-name-that-will-not-fit + set: + releaseScopedResourceNames: true + asserts: + - failedTemplate: + errorPattern: "must stay within 63 characters" + + - it: should accept a release name that fits + release: + name: alpha + set: + releaseScopedResourceNames: true + asserts: + - notFailedTemplate: {} + + - it: should not apply the length check with fixed names + release: + name: a-very-long-fluss-release-name-that-will-not-fit + asserts: + - notFailedTemplate: {} diff --git a/helm/values.yaml b/helm/values.yaml index b45a7e8a83b..cdd8b61f7ab 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -27,11 +27,22 @@ image: pullPolicy: IfNotPresent pullSecrets: [] +# Prefixes every resource name with the release name, and scopes the ZooKeeper +# root path to it, so that several Fluss clusters can share one namespace. +# Off by default: renaming a StatefulSet makes Helm replace it and orphans its +# PersistentVolumeClaims, so an existing release must opt in deliberately. See +# "Running several Fluss clusters in one namespace" in the Fluss Helm +# deployment documentation. This default is expected to flip in a later +# release, after which the option is deprecated. +releaseScopedResourceNames: false + # Fluss server configuration options configurationOverrides: default.bucket.number: 3 default.replication.factor: 3 - zookeeper.path.root: /fluss + # Follows releaseScopedResourceNames: scoping the root to the release keeps + # two clusters sharing a ZooKeeper ensemble from overwriting each other. + zookeeper.path.root: /fluss{{ if .Values.releaseScopedResourceNames }}/{{ .Release.Name }}{{ end }} zookeeper.address: zk-zookeeper.{{ .Release.Namespace }}.svc.cluster.local:2181 remote.data.dir: /tmp/fluss/remote-data data.dir: /tmp/fluss/data diff --git a/website/docs/install-deploy/deploying-with-helm.md b/website/docs/install-deploy/deploying-with-helm.md index 4a483e14a74..4123d9be6a0 100644 --- a/website/docs/install-deploy/deploying-with-helm.md +++ b/website/docs/install-deploy/deploying-with-helm.md @@ -165,6 +165,7 @@ The following table lists the configurable parameters of the Fluss chart, and th |-----------|-------------|---------| | `nameOverride` | Override the name of the chart | `""` | | `fullnameOverride` | Override the full name of the resources | `""` | +| `releaseScopedResourceNames` | Prefix every resource name with the release name and scope the ZooKeeper root path to it, so that several Fluss clusters can share a namespace. See [Running several Fluss clusters in one namespace](#running-several-fluss-clusters-in-one-namespace). | `false` | ### Image Parameters @@ -377,7 +378,7 @@ The same pattern works with Sealed Secrets, HashiCorp Vault Agent Injector (prod |-----------|-------------|---------| | `configurationOverrides.default.bucket.number` | Default number of buckets for tables | `3` | | `configurationOverrides.default.replication.factor` | Default replication factor | `3` | -| `configurationOverrides.zookeeper.path.root` | ZooKeeper root path for Fluss | `/fluss` | +| `configurationOverrides.zookeeper.path.root` | ZooKeeper root path for Fluss. Becomes `/fluss/` when `releaseScopedResourceNames` is enabled | `/fluss` | | `configurationOverrides.zookeeper.address` | ZooKeeper ensemble address | `zk-zookeeper.{{ .Release.Namespace }}.svc.cluster.local:2181` | | `configurationOverrides.remote.data.dir` | Remote data directory for snapshots | `/tmp/fluss/remote-data` | | `configurationOverrides.data.dir` | Local data directory | `/tmp/fluss/data` | @@ -562,6 +563,38 @@ configurationOverrides: zookeeper.path.root: "/my-fluss-cluster" ``` +### Running several Fluss clusters in one namespace + +By default the chart gives each resource a fixed name (`coordinator-server`, +`tablet-server`, `fluss-conf-file`, and the matching headless Services) and +puts every cluster on the ZooKeeper root path `/fluss`. Two releases in the +same namespace would therefore collide on both. + +Set `releaseScopedResourceNames` to prefix every resource with the release +name and move the ZooKeeper root path to `/fluss/`: + +```bash +helm install orders ./helm --set releaseScopedResourceNames=true +helm install payments ./helm --set releaseScopedResourceNames=true +``` + +This gives you `orders-fluss-coordinator-server` alongside +`payments-fluss-coordinator-server`, on ZooKeeper roots `/fluss/orders` and +`/fluss/payments`. It is useful when namespace creation is restricted and one +team needs more than one cluster. + +Two things are still shared and need attention: + +- `configurationOverrides.remote.data.dir` points both releases at the same + path. Give each release its own directory. +- The release name becomes part of every generated name. Keep it to 33 + characters or fewer, or set `fullnameOverride` to something shorter. The + chart fails the render with an explicit message when the limit is exceeded. + +To enable the option on a cluster that already exists, read +[Enabling release-scoped names on an existing release](#enabling-release-scoped-names-on-an-existing-release) +first: it is not an in-place change. + ### Network Configuration The chart automatically configures listeners for internal cluster communication and external client access: @@ -837,6 +870,47 @@ helm upgrade fluss ./helm helm upgrade fluss ./helm -f values-new.yaml ``` +### Enabling release-scoped names on an existing release + +Turning on `releaseScopedResourceNames` renames every resource. A StatefulSet +cannot be renamed in place, so Helm deletes the old one and creates a new one, +which means: + +- Pods are replaced rather than rolled. +- With `storage.enabled: true`, the PersistentVolumeClaims created from + `volumeClaimTemplates` are named after the StatefulSet + (`data-tablet-server-0` becomes `data--fluss-tablet-server-0`). + Kubernetes does not delete the old claims, so they stay behind as orphans and + the new pods start with empty volumes. +- The ZooKeeper root path moves from `/fluss` to `/fluss/`, so the new + pods do not see the existing cluster metadata. + +To keep the existing cluster state while taking the new names, pin the old root +path in the same upgrade: + +```yaml +releaseScopedResourceNames: true +configurationOverrides: + zookeeper.path.root: /fluss +``` + +Then delete the orphaned claims once the new pods are healthy: + +```bash +kubectl get pvc -l app.kubernetes.io/name=fluss +kubectl delete pvc data-coordinator-server-0 data-tablet-server-0 +``` + +How much data the replacement costs depends on your replication factor and on +what has been tiered to `remote.data.dir`, so rehearse the upgrade on a +non-production cluster first. + +Because the old and new names do not collide, the alternative is to install a +second release next to the existing one with the option enabled, move data +across at the Fluss level, and then uninstall the old release. That is the only +path that keeps the old cluster serving throughout. Give the new release its +own `remote.data.dir`. + ### Rolling Updates The StatefulSets support rolling updates. When you update the configuration, pods will be restarted one by one to maintain availability.