Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
25 changes: 25 additions & 0 deletions deploy/helm/kafscale/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions deploy/helm/kafscale/templates/proxy-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
8 changes: 8 additions & 0 deletions deploy/helm/kafscale/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ 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.
# 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.
# LFS is now a feature-flag on the unified proxy: set proxy.lfs.enabled=true.
Expand Down
74 changes: 74 additions & 0 deletions test/chart/proxy-nodeport_test.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading