Skip to content

Commit 2eb0b27

Browse files
committed
feat(llm): add multi-replica worker routing
Signed-off-by: Mike Camp <mcamp@nvidia.com>
1 parent da80672 commit 2eb0b27

28 files changed

Lines changed: 875 additions & 28 deletions

File tree

.github/workflows/image-push-manual.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ jobs:
167167
exit 1
168168
fi
169169
echo "discovered: ${indexes[*]}"
170+
declare -A seen_repos=()
170171
for tgt in "${indexes[@]}"; do
171172
name="${tgt##*:}"; name="${name%_index}"
172173
# Two naming conventions exist in the tree and they mean different
@@ -186,6 +187,12 @@ jobs:
186187
*-image) repo="${name%-image}" ;;
187188
*) sub="$(printf '%s' "$name" | tr '_' '-')"; repo="${svc}-${sub}" ;;
188189
esac
190+
if [ -n "${seen_repos[$repo]:-}" ]; then
191+
echo "ERROR: ${tgt} and ${seen_repos[$repo]} both map to image repository ${repo}" >&2
192+
echo "Give each oci_image_index target a distinct image name before publishing." >&2
193+
exit 1
194+
fi
195+
seen_repos["$repo"]="$tgt"
189196
dest="${REGISTRY}/${repo}"
190197
echo "[push] ${tgt} -> ${dest}:${TAG} (+ latest-dispatch)"
191198
mkdir -p ci-ghcr

deploy/helm/gateway-routes/README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,26 @@ This repository contains the Helm chart for deploying NVCF ingress routes via th
44

55
## Overview
66

7-
The chart deploys `HTTPRoute`, `TCPRoute`, and `ReferenceGrant` resources that attach to an existing Gateway provisioned separately by the cluster operator (e.g. Envoy Gateway, Istio, Traefik, Kong). It also includes optional `PodMonitor` resources for scraping Envoy Gateway proxy metrics with Prometheus.
8-
9-
The chart deploys routing configuration only. It does not include any container images. Backend services referenced by the routes (`api`, `nvct-api`, `api-keys`, `invocation`, `llm-api-gateway`, `vanity-gateway`, `reval`, `sis`, `grpc`, `nats`) must already be deployed separately.
7+
The chart deploys `HTTPRoute`, `GRPCRoute`, `TCPRoute`, `UDPRoute`, and
8+
`ReferenceGrant` resources that attach to an existing Gateway provisioned
9+
separately by the cluster operator, such as Envoy Gateway, Istio, Traefik, or
10+
Kong. It also includes optional `PodMonitor` resources for scraping Envoy
11+
Gateway proxy metrics with Prometheus.
12+
13+
The chart deploys routing configuration only. It does not include any
14+
container images. Backend services referenced by the routes (`api`,
15+
`nvct-api`, `api-keys`, `invocation`, `llm-api-gateway`,
16+
`llm-request-router-backend-router`, `vanity-gateway`, `reval`, `sis`, `grpc`,
17+
`nats`) must already be deployed separately.
1018

1119
## Prerequisites
1220

1321
- Kubernetes cluster
1422
- Helm 3.x
1523
- `kubectl`
1624
- A Gateway API compatible controller installed in the cluster
17-
- An existing `Gateway` resource with an HTTP listener (and TCP listeners if the gRPC or NATS routes are enabled)
25+
- Existing `Gateway` resources with the listeners required by each enabled route
26+
- A Gateway controller with `UDPRoute` support when LLM worker routing is enabled
1827
- The backend services that the routes target, deployed in their respective namespaces
1928

2029
## Getting Started
@@ -57,6 +66,8 @@ Important settings to review before deployment:
5766
- `nvcfGatewayRoutes.gateways.shared.*` for the HTTP Gateway name, namespace, and listener
5867
- `nvcfGatewayRoutes.gateways.grpc.*` for the TCP Gateway name, namespace, and listener
5968
- `nvcfGatewayRoutes.gateways.nats.*` for the NATS TCP Gateway name, namespace, and listener
69+
- `nvcfGatewayRoutes.gateways.llmGrpc.*` for the LLM worker gRPC TCP listener
70+
- `nvcfGatewayRoutes.gateways.llmQuic.*` for the LLM reverse-tunnel UDP listener
6071
- `nvcfGatewayRoutes.routes.<route>.enabled` to toggle individual routes
6172
- `nvcfGatewayRoutes.routes.nvcfApi.grpc.enabled` and
6273
`nvcfGatewayRoutes.routes.nvctApi.grpc.enabled` to expose API gRPC routes
@@ -87,6 +98,7 @@ Enabled `HTTPRoute` entries must not share a resolved hostname because each `HTT
8798
| `grpc` | TCPRoute | Not rendered | `grpc.nvcf:10081` |
8899
| `grpcWorker` | TCPRoute (disabled by default) | Not rendered | `grpc.nvcf:10086` |
89100
| `nats` | TCPRoute (disabled by default) | Not rendered | `nats.nats-system:4222` |
101+
| `llmWorker` | TCPRoute and UDPRoute (disabled by default) | Not rendered | `llm-request-router-backend-router.nvcf:50071/TCP,50072/UDP` |
90102

91103
Cross-namespace routing is supported via `ReferenceGrant` resources rendered into each backend namespace.
92104

@@ -97,3 +109,6 @@ Cross-namespace routing is supported via `ReferenceGrant` resources rendered int
97109
- The `grpc` TCPRoute does not enforce HTTP hostname matching at the Gateway layer. Configure DNS or TCP load balancer routing outside this chart.
98110
- The `grpcWorker` TCPRoute is beta support for split or multi-cluster gRPC worker callbacks. It carries HTTP/1 CONNECT callback traffic only. Enable it only when the control-plane grpc-proxy runs one replica with HPA disabled. Multi-replica grpc-proxy requires pod-specific callback routing and is not supported by this shared TCPRoute.
99111
- Enabling the `nats` route requires a reachable TCP listener for NATS on the referenced Gateway. The HTTP Gateway address does not imply NATS reachability unless that same Gateway also has the NATS TCP listener configured.
112+
- The `llmWorker` routes target Stargate's authority/SNI-aware backend router.
113+
Keep the TCP and UDP Gateways separate when the infrastructure requires
114+
separate load balancers for each protocol.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
{{- if and .Values.nvcfGatewayRoutes.enabled .Values.nvcfGatewayRoutes.routes.llmWorker.enabled }}
5+
apiVersion: gateway.networking.k8s.io/v1beta1
6+
kind: ReferenceGrant
7+
metadata:
8+
name: allow-llm-worker-routes
9+
namespace: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.namespace }}
10+
labels:
11+
{{- include "nvcf-gateway.labels" . | nindent 4 }}
12+
spec:
13+
from:
14+
- group: gateway.networking.k8s.io
15+
kind: TCPRoute
16+
namespace: {{ .Values.nvcfGatewayRoutes.gateways.llmGrpc.namespace }}
17+
- group: gateway.networking.k8s.io
18+
kind: UDPRoute
19+
namespace: {{ .Values.nvcfGatewayRoutes.gateways.llmQuic.namespace }}
20+
to:
21+
- group: ""
22+
kind: Service
23+
name: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.name }}
24+
{{- end }}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
{{- if and .Values.nvcfGatewayRoutes.enabled .Values.nvcfGatewayRoutes.routes.llmWorker.enabled }}
5+
apiVersion: gateway.networking.k8s.io/v1alpha2
6+
kind: TCPRoute
7+
metadata:
8+
name: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.name }}-grpc
9+
namespace: {{ .Values.nvcfGatewayRoutes.gateways.llmGrpc.namespace }}
10+
labels:
11+
{{- include "nvcf-gateway.labels" . | nindent 4 }}
12+
app.kubernetes.io/component: llm-worker-grpc-route
13+
{{- with .Values.nvcfGatewayRoutes.routes.llmWorker.routeAnnotations }}
14+
annotations:
15+
{{- toYaml . | nindent 4 }}
16+
{{- end }}
17+
spec:
18+
parentRefs:
19+
- name: {{ .Values.nvcfGatewayRoutes.gateways.llmGrpc.name }}
20+
namespace: {{ .Values.nvcfGatewayRoutes.gateways.llmGrpc.namespace }}
21+
sectionName: {{ .Values.nvcfGatewayRoutes.gateways.llmGrpc.listenerName }}
22+
rules:
23+
- backendRefs:
24+
- name: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.name }}
25+
namespace: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.namespace }}
26+
port: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.grpcPort }}
27+
{{- end }}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
{{- if and .Values.nvcfGatewayRoutes.enabled .Values.nvcfGatewayRoutes.routes.llmWorker.enabled }}
5+
apiVersion: gateway.networking.k8s.io/v1alpha2
6+
kind: UDPRoute
7+
metadata:
8+
name: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.name }}-quic
9+
namespace: {{ .Values.nvcfGatewayRoutes.gateways.llmQuic.namespace }}
10+
labels:
11+
{{- include "nvcf-gateway.labels" . | nindent 4 }}
12+
app.kubernetes.io/component: llm-worker-quic-route
13+
{{- with .Values.nvcfGatewayRoutes.routes.llmWorker.routeAnnotations }}
14+
annotations:
15+
{{- toYaml . | nindent 4 }}
16+
{{- end }}
17+
spec:
18+
parentRefs:
19+
- name: {{ .Values.nvcfGatewayRoutes.gateways.llmQuic.name }}
20+
namespace: {{ .Values.nvcfGatewayRoutes.gateways.llmQuic.namespace }}
21+
sectionName: {{ .Values.nvcfGatewayRoutes.gateways.llmQuic.listenerName }}
22+
rules:
23+
- backendRefs:
24+
- name: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.name }}
25+
namespace: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.namespace }}
26+
port: {{ .Values.nvcfGatewayRoutes.routes.llmWorker.backend.quicPort }}
27+
{{- end }}

deploy/helm/gateway-routes/chart/values.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ nvcfGatewayRoutes:
5151
namespace: gateway
5252
# Listener (sectionName) on the Gateway to attach the route to
5353
listenerName: nats
54+
# TCP Gateway for LLM worker registration and Stargate watches.
55+
llmGrpc:
56+
name: llm-grpc-gateway
57+
namespace: gateway
58+
listenerName: llm-grpc
59+
# UDP Gateway for LLM reverse tunnels. This may be a separate load
60+
# balancer from the TCP Gateway.
61+
llmQuic:
62+
name: llm-quic-gateway
63+
namespace: gateway
64+
listenerName: llm-quic
5465

5566
# HTTPRoute configurations
5667
routes:
@@ -239,6 +250,19 @@ nvcfGatewayRoutes:
239250
namespace: nats-system
240251
port: 4222
241252
routeAnnotations: {}
253+
254+
# Backend-facing LLM registration/watch and reverse-tunnel routes.
255+
# The backend router selects the correct Stargate pod by gRPC authority
256+
# and QUIC SNI, so this route supports multi-replica request routers.
257+
llmWorker:
258+
enabled: false
259+
name: llm-worker
260+
backend:
261+
name: llm-request-router-backend-router
262+
namespace: nvcf
263+
grpcPort: 50071
264+
quicPort: 50072
265+
routeAnnotations: {}
242266

243267
# NVCF Worker Container needs to be able to fetch secrets for function pods
244268
# In split/multi cluster planes, ess resides in control-plane and requires
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env bash
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
set -euo pipefail
6+
7+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
chart_dir="${script_dir}/../chart"
9+
rendered="$(mktemp)"
10+
disabled="$(mktemp)"
11+
trap 'rm -f "$rendered" "$disabled"' EXIT
12+
13+
helm template nvcf-gateway-routes "$chart_dir" \
14+
--namespace gateway \
15+
--set nvcfGatewayRoutes.routes.llmWorker.enabled=true \
16+
--set nvcfGatewayRoutes.gateways.llmGrpc.name=llm-grpc-gateway \
17+
--set nvcfGatewayRoutes.gateways.llmGrpc.namespace=gateway \
18+
--set nvcfGatewayRoutes.gateways.llmQuic.name=llm-quic-gateway \
19+
--set nvcfGatewayRoutes.gateways.llmQuic.namespace=gateway \
20+
>"$rendered"
21+
22+
assert_contains() {
23+
local pattern="$1"
24+
local message="$2"
25+
if ! grep -Fq -- "$pattern" "$rendered"; then
26+
echo "FAIL: ${message}" >&2
27+
exit 1
28+
fi
29+
}
30+
31+
assert_contains "kind: TCPRoute" \
32+
"LLM worker routing must expose gRPC registration over TCP"
33+
assert_contains "kind: UDPRoute" \
34+
"LLM worker routing must expose reverse tunnels over UDP"
35+
assert_contains "name: llm-request-router-backend-router" \
36+
"LLM worker routes must target the authority/SNI-aware backend router"
37+
assert_contains "name: allow-llm-worker-routes" \
38+
"ReferenceGrant must permit cross-namespace LLM worker routes"
39+
assert_contains "sectionName: llm-grpc" \
40+
"TCPRoute must attach to the configured LLM gRPC listener"
41+
assert_contains "sectionName: llm-quic" \
42+
"UDPRoute must attach to the configured LLM QUIC listener"
43+
44+
helm template nvcf-gateway-routes "$chart_dir" \
45+
--namespace gateway \
46+
--set nvcfGatewayRoutes.routes.llmWorker.enabled=false \
47+
>"$disabled"
48+
49+
if grep -Eq '^ name: (llm-worker-(grpc|quic)|allow-llm-worker-routes)$' "$disabled"; then
50+
echo "FAIL: disabled LLM worker routing must not render route resources" >&2
51+
exit 1
52+
fi
53+
54+
echo "PASS: LLM worker Gateway routes render correctly"

deploy/helm/llm-request-router/README.md

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@ This repository contains the Helm chart for deploying the NVCF LLM Request Route
44

55
## Overview
66

7-
The chart packages the LLM Request Router StatefulSet with HTTP and gRPC services, a metrics endpoint, and a headless service for multi-instance DNS discovery. A Vault Agent sidecar is configured to fetch a service token from a Vault or OpenBao backend; the application reads `nvcfApiToken` from `/vault/secrets/secrets.json` and attaches it as a Bearer token to outgoing worker authentication gRPC calls.
7+
The chart packages the LLM Request Router StatefulSet with HTTP and gRPC
8+
services, a metrics endpoint, and a headless service for multi-instance DNS
9+
discovery. It can also deploy the Stargate Kubernetes backend router for
10+
worker gRPC registration and reverse QUIC tunnels through a shared Gateway or
11+
load balancer. The backend router selects the correct Stargate pod from gRPC
12+
authority and QUIC SNI.
13+
14+
A Vault Agent sidecar is configured to fetch a service token from a Vault or
15+
OpenBao backend. The application reads `nvcfApiToken` from
16+
`/vault/secrets/secrets.json` and attaches it as a Bearer token to outgoing
17+
worker authentication gRPC calls.
818

919
The default chart values do not set the required image registry and repository. They must be supplied through an additional values file at install time, and access to those images must be arranged separately.
1020

@@ -70,6 +80,7 @@ Important settings to review before deployment:
7080
- `llmRequestRouter.imagePullSecrets` for private registry access
7181
- `llmRequestRouter.replicaCount`, resource requests, and limits for your environment
7282
- `llmRequestRouter.service.*` for HTTP, gRPC, metrics, and headless service ports
83+
- `llmRequestRouter.backendRouter.*` for multi-replica worker gRPC and reverse-tunnel routing
7384
- `llmRequestRouter.metrics.enabled` to expose the metrics port on the Service (default: `false`)
7485
- `llmRequestRouter.metrics.serviceMonitor.enabled` to create a Prometheus `ServiceMonitor` (requires `metrics.enabled`)
7586
- `llmRequestRouter.certificate.*` to let cert-manager issue the Stargate QUIC server certificate
@@ -80,6 +91,51 @@ Important settings to review before deployment:
8091

8192
The default values include development-oriented placeholders. Override them before using the chart in any shared or production environment.
8293

94+
## Backend Worker Routing
95+
96+
Enable `llmRequestRouter.backendRouter.enabled` when workers reach a
97+
multi-replica request router through a shared endpoint. Set both pylon dial
98+
addresses to the external endpoints that workers can resolve:
99+
100+
```yaml
101+
llmRequestRouter:
102+
backendRouter:
103+
enabled: true
104+
image:
105+
tag: <published-stargate-version>
106+
pylonGrpcDialAddress: llm-router.example.com:443
107+
pylonReverseTunnelDialAddress: llm-router.example.com:8080
108+
```
109+
110+
The chart uses the main Stargate image for both workloads. The image must
111+
contain `/usr/local/bin/stargate-k8s-router`. Set
112+
`llmRequestRouter.backendRouter.image.tag` to an image version that contains
113+
that binary. The chart requires this explicit pin when backend routing is
114+
enabled.
115+
116+
The backend router watches EndpointSlices. The chart creates a namespaced Role
117+
and RoleBinding when `llmRequestRouter.rbac.create=true`. If the chart does not
118+
create the ServiceAccount, set `llmRequestRouter.serviceAccount.name`
119+
explicitly so the RoleBinding does not grant access to the namespace default
120+
ServiceAccount. When `rbac.create=false`, grant `get`, `list`, and `watch` on
121+
`discovery.k8s.io/endpointslices` to the selected ServiceAccount outside this
122+
chart.
123+
124+
Route TCP port `50071` and UDP port `50072` to the
125+
`llm-request-router-backend-router` Service. The NVCF gateway-routes chart can
126+
create the matching `TCPRoute`, `UDPRoute`, and `ReferenceGrant` resources.
127+
The Gateway implementation must support Gateway API `UDPRoute`.
128+
129+
When QUIC verification is enabled, the mounted certificate must cover the
130+
worker-facing reverse-tunnel hostname and the per-pod hostname template. The
131+
default template is
132+
`{pod_name}.llm-request-router-headless.<namespace>.svc.cluster.local`.
133+
134+
Stargate and the backend router read the TLS certificate and key only during
135+
process startup. After cert-manager or another issuer renews the Secret,
136+
restart both workloads or configure a Secret reloader that triggers their
137+
rollouts.
138+
83139
## Load Balancer Configuration
84140

85141
The chart can pass a Stargate load-balancer config in either of two ways:

0 commit comments

Comments
 (0)