From d06faae7ab97748c5e7328a6de180c432e1bba34 Mon Sep 17 00:00:00 2001 From: Scalytics Date: Thu, 4 Jun 2026 19:31:03 +0200 Subject: [PATCH 1/2] fix(chart): proxy service honors service.nodePort (ADR-0002 pinning) The proxy Service template did not render spec.ports[0].nodePort, so with type=NodePort Kubernetes assigned a random node port and a fixed host:port mapping (e.g. a kind host 9092 -> 30092 mapping) could never reach the proxy. Render nodePort when type==NodePort and a nodePort is set; default empty keeps the auto-assign behaviour. Lets the proxy be pinned to a known NodePort, which matters because the proxy is the single Kafka entrypoint. Co-Authored-By: Claude Opus 4.8 (1M context) --- deploy/helm/kafscale/templates/proxy-service.yaml | 3 +++ deploy/helm/kafscale/values.yaml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/deploy/helm/kafscale/templates/proxy-service.yaml b/deploy/helm/kafscale/templates/proxy-service.yaml index e884889f..dc4d0181 100644 --- a/deploy/helm/kafscale/templates/proxy-service.yaml +++ b/deploy/helm/kafscale/templates/proxy-service.yaml @@ -38,4 +38,7 @@ spec: port: {{ .Values.proxy.service.port }} targetPort: kafka protocol: TCP + {{- if and (eq .Values.proxy.service.type "NodePort") .Values.proxy.service.nodePort }} + nodePort: {{ .Values.proxy.service.nodePort }} + {{- end }} {{- end }} diff --git a/deploy/helm/kafscale/values.yaml b/deploy/helm/kafscale/values.yaml index a5ae4f9e..9b4f353d 100644 --- a/deploy/helm/kafscale/values.yaml +++ b/deploy/helm/kafscale/values.yaml @@ -151,6 +151,10 @@ proxy: port: 9092 annotations: {} loadBalancerSourceRanges: [] + # When type is NodePort, pin the node port (otherwise Kubernetes assigns a + # random one and a fixed host:port mapping cannot reach the proxy). Leave + # empty to let Kubernetes choose. Only rendered when type == NodePort. + nodePort: "" # NOTE: The standalone lfs-proxy has been removed. # LFS is now a feature-flag on the unified proxy: set proxy.lfs.enabled=true. From fb24a8cdafb96c603d5281fec055f2d4858f47a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mirko=20K=C3=A4mpf?= Date: Mon, 15 Jun 2026 23:42:34 +0200 Subject: [PATCH 2/2] test(chart): cover proxy service nodePort rendering + document it Add a self-contained chart template test for the proxy Service nodePort behaviour (test/chart/proxy-nodeport_test.sh), wired as `make test-chart-proxy-nodeport`. It runs `helm template` with three value-sets and asserts the rendered nodePort: 1. type=NodePort + nodePort=30092 -> renders nodePort: 30092 2. type=NodePort + empty -> line omitted (auto-assign) 3. type=LoadBalancer + value -> line suppressed helm only, no plugins, no cluster. Document proxy.service.nodePort in the chart README under a new "Proxy Service" section, including the valid NodePort range 30000-32767. Note in values.yaml that the field is scoped to the proxy (the single Kafka entrypoint); console/mcp expose service.type but are left as-is and tracked separately. Default render is unchanged; the field is opt-in. Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 5 ++- deploy/helm/kafscale/README.md | 25 +++++++++++ deploy/helm/kafscale/values.yaml | 4 ++ test/chart/proxy-nodeport_test.sh | 74 +++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100755 test/chart/proxy-nodeport_test.sh diff --git a/Makefile b/Makefile index 93581a28..d44fe92d 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ LOCAL_NODE_BIN := $(LOCAL_NODE_DIR)/bin LOCAL_NODE := $(LOCAL_NODE_BIN)/node LOCAL_NPM := $(LOCAL_NODE_BIN)/npm -.PHONY: proto build test test-nested-modules tidy lint generate build-sdk docker-build docker-build-e2e-client docker-build-etcd-tools docker-clean ensure-minio start-minio stop-containers release-broker-ports test-produce-consume test-produce-consume-debug test-consumer-group test-ops-api test-mcp test-multi-segment-durability test-full test-operator test-acl demo demo-platform demo-platform-bootstrap iceberg-demo kafsql-demo platform-demo help clean-kind-all ensure-local-node check vet race fmt fmt-check test-fuzz code-ql code-ql-summary code-ql-gate commit-check +.PHONY: proto build test test-nested-modules tidy lint generate build-sdk docker-build docker-build-e2e-client docker-build-etcd-tools docker-clean ensure-minio start-minio stop-containers release-broker-ports test-produce-consume test-produce-consume-debug test-consumer-group test-ops-api test-mcp test-multi-segment-durability test-full test-operator test-acl demo demo-platform demo-platform-bootstrap iceberg-demo kafsql-demo platform-demo help clean-kind-all ensure-local-node check vet race fmt fmt-check test-fuzz code-ql code-ql-summary code-ql-gate commit-check test-chart-proxy-nodeport REGISTRY ?= ghcr.io/kafscale STAMP_DIR ?= .build @@ -205,6 +205,9 @@ fmt-check: ## Check formatting (fails if unformatted) test-fuzz: ## Run Go fuzz test(s) bash scripts/test_fuzz.sh +test-chart-proxy-nodeport: ## Run the proxy Service nodePort chart template test (helm only, no cluster) + bash test/chart/proxy-nodeport_test.sh + code-ql: ensure-local-node ## Run local CodeQL and emit SARIF under .tmp/codeql/ bash scripts/codeql_local.sh diff --git a/deploy/helm/kafscale/README.md b/deploy/helm/kafscale/README.md index 4aa8cd22..e2706e44 100644 --- a/deploy/helm/kafscale/README.md +++ b/deploy/helm/kafscale/README.md @@ -111,6 +111,31 @@ See [values.yaml](values.yaml) for the full list of configurable parameters. | `lfsDemos.*` | Demo applications | | `mcp.*` | MCP server settings | +### Proxy Service + +The proxy is the single Kafka entrypoint, so its Service is the one clients connect to. + +| Key | Description | Default | +|-----|-------------|---------| +| `proxy.service.type` | Service type (`ClusterIP`, `NodePort`, `LoadBalancer`) | `LoadBalancer` | +| `proxy.service.port` | Service port for the Kafka listener | `9092` | +| `proxy.service.nodePort` | Pin the node port for the Kafka listener. Only rendered when `type == NodePort`; leave empty to let Kubernetes auto-assign | `""` | + +When `type` is `NodePort` and `nodePort` is empty, Kubernetes assigns a random +node port, so a fixed host-to-node-port mapping (for example a kind +`hostPort: 9092 -> containerPort: 30092` mapping) never reaches the proxy. Set +`proxy.service.nodePort` to a value in the valid NodePort range `30000-32767` +to pin it. A value outside that range renders fine but is rejected by the API +server at apply time. + +```yaml +proxy: + enabled: true + service: + type: NodePort + nodePort: 30092 +``` + ## LFS Proxy The LFS Proxy implements the claim-check pattern for large Kafka messages: diff --git a/deploy/helm/kafscale/values.yaml b/deploy/helm/kafscale/values.yaml index 9b4f353d..2b28eb61 100644 --- a/deploy/helm/kafscale/values.yaml +++ b/deploy/helm/kafscale/values.yaml @@ -154,6 +154,10 @@ proxy: # When type is NodePort, pin the node port (otherwise Kubernetes assigns a # random one and a fixed host:port mapping cannot reach the proxy). Leave # empty to let Kubernetes choose. Only rendered when type == NodePort. + # Valid range is 30000-32767; a value outside it renders but is rejected + # at apply time. Scoped to the proxy because it is the single Kafka + # entrypoint; console/mcp expose service.type but are left as-is and + # tracked separately. nodePort: "" # NOTE: The standalone lfs-proxy has been removed. diff --git a/test/chart/proxy-nodeport_test.sh b/test/chart/proxy-nodeport_test.sh new file mode 100755 index 00000000..07a8a7bc --- /dev/null +++ b/test/chart/proxy-nodeport_test.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Copyright 2026 KafScale team. +# +# Licensed 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. + +# Chart template test for the proxy Service nodePort behaviour. +# +# Asserts that deploy/helm/kafscale/templates/proxy-service.yaml renders +# spec.ports[0].nodePort only when proxy.service.type == NodePort AND +# proxy.service.nodePort is set. Self-contained: needs only helm and awk, +# no helm plugins. Run directly or via `make test-chart-proxy-nodeport`. + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +CHART_DIR="${ROOT_DIR}/deploy/helm/kafscale" + +command -v helm >/dev/null 2>&1 || { echo "helm is required"; exit 1; } + +fail=0 + +# Render the proxy Service object only. The chart guards the proxy template +# behind proxy.enabled, so every case enables it. +render_proxy_service() { + helm template kafscale "${CHART_DIR}" \ + --show-only templates/proxy-service.yaml \ + --set proxy.enabled=true \ + "$@" +} + +# nodePort value rendered on the kafka port, or empty string if absent. +proxy_nodeport() { + render_proxy_service "$@" | awk '/^[[:space:]]*nodePort:/ { print $2; exit }' +} + +assert_eq() { + local desc="$1" want="$2" got="$3" + if [ "${got}" = "${want}" ]; then + echo "PASS: ${desc} (nodePort=\"${got}\")" + else + echo "FAIL: ${desc} (want nodePort=\"${want}\", got \"${got}\")" + fail=1 + fi +} + +echo "==> chart proxy nodePort template test" + +# Case 1: NodePort + explicit nodePort -> renders nodePort: 30092. +got="$(proxy_nodeport --set proxy.service.type=NodePort --set proxy.service.nodePort=30092)" +assert_eq "NodePort + nodePort=30092 renders the value" "30092" "${got}" + +# Case 2: NodePort + empty nodePort -> line omitted (Kubernetes auto-assigns). +got="$(proxy_nodeport --set proxy.service.type=NodePort --set 'proxy.service.nodePort=')" +assert_eq "NodePort + empty nodePort omits the line" "" "${got}" + +# Case 3: LoadBalancer + value -> line suppressed (only NodePort honours it). +got="$(proxy_nodeport --set proxy.service.type=LoadBalancer --set proxy.service.nodePort=30092)" +assert_eq "LoadBalancer + nodePort=30092 suppresses the line" "" "${got}" + +if [ "${fail}" -ne 0 ]; then + echo "==> chart proxy nodePort template test FAILED" + exit 1 +fi +echo "==> chart proxy nodePort template test passed"