Skip to content

Commit 928c0da

Browse files
committed
docs: update KServe integration routing
Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>
1 parent 69d0875 commit 928c0da

1 file changed

Lines changed: 142 additions & 90 deletions

File tree

assets/agw-docs/pages/integrations/kserve.md

Lines changed: 142 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
kubectl create namespace kserve
2626
```
2727

28-
2. Create a `Gateway` resource that agentgateway manages. KServe attaches `HTTPRoute` resources to this gateway automatically for each `InferenceService` you deploy.
28+
2. Create a `Gateway` resource that agentgateway manages. KServe attaches
29+
`HTTPRoute` resources to this gateway automatically for each
30+
`LLMInferenceService` you deploy.
2931
```yaml
3032
kubectl apply -f - <<EOF
3133
apiVersion: gateway.networking.k8s.io/v1
@@ -63,17 +65,20 @@
6365

6466
## Step 3: Install KServe
6567

66-
1. Install the KServe CRDs.
68+
1. Install the KServe `LLMInferenceService` CRDs.
6769
```shell
68-
helm install kserve-crd oci://ghcr.io/kserve/charts/kserve-crd --version v0.19.0
70+
helm install kserve-llmisvc-crd \
71+
oci://ghcr.io/kserve/charts/kserve-llmisvc-crd \
72+
--version v0.19.0 \
73+
--namespace kserve
6974
```
7075

71-
2. Install KServe resources using Helm.
76+
2. Install the KServe `LLMInferenceService` resources by using Helm.
7277
```shell
73-
helm install kserve oci://ghcr.io/kserve/charts/kserve-resources \
78+
helm install kserve-llmisvc-resources \
79+
oci://ghcr.io/kserve/charts/kserve-llmisvc-resources \
7480
--version v0.19.0 \
7581
--namespace kserve \
76-
--create-namespace \
7782
--set kserve.controller.deploymentMode=Standard \
7883
--set kserve.controller.gateway.ingressGateway.enableGatewayApi=true \
7984
--set kserve.controller.gateway.ingressGateway.createGateway=false \
@@ -86,19 +91,33 @@
8691
--set kserve.controller.gateway.localGateway.gatewayService=""
8792
```
8893

89-
3. Verify that the KServe controller is available.
94+
3. Install the default `LLMInferenceServiceConfig` resources. KServe merges
95+
these defaults with the settings in each `LLMInferenceService`.
9096

9197
```shell
92-
kubectl wait --for=condition=available deployment/kserve-controller-manager -n kserve --timeout=180s
93-
kubectl get deployment kserve-controller-manager -n kserve
98+
helm install kserve-runtime-configs \
99+
oci://ghcr.io/kserve/charts/kserve-runtime-configs \
100+
--version v0.19.0 \
101+
--namespace kserve \
102+
--set kserve.llmisvcConfigs.enabled=true
103+
```
104+
105+
4. Verify that the KServe `LLMInferenceService` controller is available.
106+
107+
```shell
108+
kubectl wait --for=condition=available \
109+
deployment/llmisvc-controller-manager \
110+
-n kserve \
111+
--timeout=180s
112+
kubectl get deployment llmisvc-controller-manager -n kserve
94113
```
95114

96115
Example output:
97116

98117
```
99-
deployment.apps/kserve-controller-manager condition met
100-
NAME READY UP-TO-DATE AVAILABLE AGE
101-
kserve-controller-manager 1/1 1 1 45s
118+
deployment.apps/llmisvc-controller-manager condition met
119+
NAME READY UP-TO-DATE AVAILABLE AGE
120+
llmisvc-controller-manager 1/1 1 1 45s
102121
```
103122

104123
## Step 4: Deploy a mocked LLM with llm-d-inference-sim
@@ -111,29 +130,89 @@ Instead of a real model, this guide uses [llm-d-inference-sim](https://github.co
111130
kubectl create namespace kserve-test
112131
```
113132

114-
2. Deploy an `InferenceService` using llm-d-inference-sim directly via `spec.predictor.containers`. This approach bypasses KServe's model runtime machinery entirely, no `ClusterServingRuntime` or model storage is needed.
133+
2. Create an `{{< reuse "agw-docs/snippets/backend.md" >}}` that points
134+
to the workload service that KServe creates for the
135+
`LLMInferenceService`. The backend identifies the endpoint as an
136+
OpenAI-compatible LLM so that agentgateway can apply LLM-aware features.
137+
138+
```yaml
139+
kubectl apply -f - <<EOF
140+
apiVersion: {{< reuse "agw-docs/snippets/api-version.md" >}}
141+
kind: {{< reuse "agw-docs/snippets/backend.md" >}}
142+
metadata:
143+
name: mock-llm-backend
144+
namespace: kserve-test
145+
spec:
146+
ai:
147+
provider:
148+
openai:
149+
model: mock-llm
150+
host: mock-llm-kserve-workload-svc.kserve-test.svc.cluster.local
151+
port: 8000
152+
path: "/v1/chat/completions"
153+
EOF
154+
```
155+
156+
3. Deploy an `LLMInferenceService` that runs llm-d-inference-sim. The
157+
`spec.router.route.http` settings instruct KServe to generate an
158+
`HTTPRoute` that references the
159+
`{{< reuse "agw-docs/snippets/backend.md" >}}` directly. Because the
160+
simulator does not need model files, the example disables KServe's storage
161+
initializer.
162+
115163
```yaml
116164
kubectl apply -f - <<EOF
117-
apiVersion: serving.kserve.io/v1beta1
118-
kind: InferenceService
165+
apiVersion: serving.kserve.io/v1alpha1
166+
kind: LLMInferenceService
119167
metadata:
120168
name: mock-llm
121169
namespace: kserve-test
122170
spec:
123-
predictor:
171+
model:
172+
name: mock-llm
173+
uri: hf://mock/mock-llm
174+
replicas: 1
175+
storageInitializer:
176+
enabled: false
177+
router:
178+
route:
179+
http:
180+
spec:
181+
parentRefs:
182+
- group: gateway.networking.k8s.io
183+
kind: Gateway
184+
name: kserve-ingress-gateway
185+
namespace: kserve
186+
hostnames:
187+
- mock-llm-kserve-test.example.com
188+
rules:
189+
- backendRefs:
190+
- group: {{< reuse "agw-docs/snippets/group.md" >}}
191+
kind: {{< reuse "agw-docs/snippets/backend.md" >}}
192+
name: mock-llm-backend
193+
matches:
194+
- path:
195+
type: PathPrefix
196+
value: /v1/chat/completions
197+
timeouts:
198+
backendRequest: 0s
199+
request: 0s
200+
template:
124201
containers:
125-
- name: kserve-container
202+
- name: main
126203
image: ghcr.io/llm-d/llm-d-inference-sim:v0.9.0-rc3
204+
command:
205+
- /app/llm-d-inference-sim
127206
args:
128207
- --model
129208
- mock-llm
130209
- --port
131-
- "8080"
210+
- "8000"
132211
- --mode
133212
- echo
134213
ports:
135-
- containerPort: 8080
136-
protocol: TCP
214+
- name: http
215+
containerPort: 8000
137216
resources:
138217
requests:
139218
cpu: "100m"
@@ -144,29 +223,49 @@ Instead of a real model, this guide uses [llm-d-inference-sim](https://github.co
144223
EOF
145224
```
146225

147-
3. Wait for the `InferenceService` to become ready.
226+
4. Wait for the `LLMInferenceService` to become ready.
148227

149228
```shell
150-
kubectl get inferenceservices mock-llm -n kserve-test --watch
229+
kubectl wait --for=condition=Ready \
230+
llminferenceservice/mock-llm \
231+
-n kserve-test \
232+
--timeout=300s
151233
```
152-
234+
235+
5. Verify that KServe created one `HTTPRoute` whose backend is the
236+
`{{< reuse "agw-docs/snippets/backend.md" >}}`.
237+
238+
```shell
239+
kubectl get httproute mock-llm-kserve-route -n kserve-test \
240+
-o jsonpath='{.spec.rules[0].backendRefs[0]}'
241+
```
242+
243+
Example output:
244+
245+
```json
246+
{"group":"agentgateway.dev","kind":"AgentgatewayBackend","name":"mock-llm-backend","weight":1}
247+
```
248+
153249
## Optional Step 4b: Apply a transformation policy to the KServe-generated HTTPRoute
154250

155251
Without a policy, agentgateway forwards requests and responses as-is. This
156252
step shows how a transformation policy can enrich responses with additional
157253
headers — without touching the inference service itself.
158254

159-
1. Verify that KServe created an HTTPRoute after the Gateway becomes `READY`. The route attaches to `kserve/kserve-ingress-gateway` with hostname `mock-llm-kserve-test.example.com`.
255+
1. Verify that KServe created an HTTPRoute after the
256+
`LLMInferenceService` becomes `Ready`. The route attaches to
257+
`kserve/kserve-ingress-gateway` with hostname
258+
`mock-llm-kserve-test.example.com`.
160259

161260
```shell
162-
kubectl get httproute mock-llm -n kserve-test -o yaml
261+
kubectl get httproute mock-llm-kserve-route -n kserve-test -o yaml
163262
```
164263

165264
{{< tabs >}}
166265
{{% tab name="Cloud Provider LoadBalancer" %}}
167266
2. Get the external address of the gateway and save it in an environment variable.
168267
```shell
169-
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kserve agentgateway-proxy \
268+
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kserve kserve-ingress-gateway \
170269
-o=jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
171270
echo $INGRESS_GW_ADDRESS
172271
```
@@ -203,7 +302,7 @@ headers — without touching the inference service itself.
203302
targetRefs:
204303
- group: gateway.networking.k8s.io
205304
kind: HTTPRoute
206-
name: mock-llm
305+
name: mock-llm-kserve-route
207306
traffic:
208307
transformation:
209308
response:
@@ -275,7 +374,7 @@ headers — without touching the inference service itself.
275374
targetRefs:
276375
- group: gateway.networking.k8s.io
277376
kind: HTTPRoute
278-
name: mock-llm
377+
name: mock-llm-kserve-route
279378
traffic:
280379
transformation:
281380
response:
@@ -311,72 +410,21 @@ headers — without touching the inference service itself.
311410
{{< /tabs >}}
312411

313412

314-
## Step 5: Create a backend
315-
316-
KServe generates the `HTTPRoute` with a plain Kubernetes `Service` as the `backendRef`. However, to apply a token-based rate limiting policy, agentgateway needs the backend to be an {{< reuse "agw-docs/snippets/backend.md" >}}. This way, agentgateway knows that the backend is an LLM that has a response body with the `usage.total_tokens` field to count against the rate limit bucket. In the following steps, you create an {{< reuse "agw-docs/snippets/backend.md" >}} and a second HTTPRoute to route to it as a workaround to the KServe-created, Service-based setup.
317-
318-
1. Create an `{{< reuse "agw-docs/snippets/backend.md" >}}` that points at the llm-d-inference-sim service.
319-
```yaml
320-
kubectl apply -f - <<EOF
321-
apiVersion: {{< reuse "agw-docs/snippets/api-version.md" >}}
322-
kind: {{< reuse "agw-docs/snippets/backend.md" >}}
323-
metadata:
324-
name: mock-llm-backend
325-
namespace: kserve-test
326-
spec:
327-
ai:
328-
provider:
329-
openai:
330-
model: mock-llm
331-
host: mock-llm-predictor.kserve-test.svc.cluster.local
332-
port: 80
333-
path: "/v1/chat/completions"
334-
EOF
335-
```
336-
337-
2. Create a second `HTTPRoute` that routes to the `{{< reuse "agw-docs/snippets/backend.md" >}}`. This route uses the same hostname as the KServe-generated route but matches only the `/v1/chat/completions` path, so the gateway prefers it for LLM traffic.
338-
```yaml
339-
kubectl apply -f - <<EOF
340-
apiVersion: gateway.networking.k8s.io/v1
341-
kind: HTTPRoute
342-
metadata:
343-
name: mock-llm-ai
344-
namespace: kserve-test
345-
spec:
346-
parentRefs:
347-
- group: gateway.networking.k8s.io
348-
kind: Gateway
349-
name: kserve-ingress-gateway
350-
namespace: kserve
351-
hostnames:
352-
- mock-llm-kserve-test.example.com
353-
rules:
354-
- matches:
355-
- path:
356-
type: PathPrefix
357-
value: /v1/chat/completions
358-
backendRefs:
359-
- name: mock-llm-backend
360-
namespace: kserve-test
361-
group: {{< reuse "agw-docs/snippets/group.md" >}}
362-
kind: {{< reuse "agw-docs/snippets/backend.md" >}}
363-
EOF
364-
```
365-
366-
## Step 6: Test the endpoint
413+
## Step 5: Test the endpoint
367414

368415
{{< tabs >}}
369416
{{% tab name="Cloud Provider LoadBalancer" %}}
370417
1. Get the external address of the gateway and save it in an environment variable.
371418
```shell
372-
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kserve agentgateway-proxy \
419+
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kserve kserve-ingress-gateway \
373420
-o=jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
374421
echo $INGRESS_GW_ADDRESS
375422
```
376423

377424
2. Send a request to verify the setup works end-to-end.
378425
```shell
379426
curl -s http://$INGRESS_GW_ADDRESS/v1/chat/completions \
427+
-H "Host: mock-llm-kserve-test.example.com" \
380428
-H "Content-Type: application/json" \
381429
-d '{
382430
"model": "mock-llm",
@@ -466,11 +514,14 @@ KServe generates the `HTTPRoute` with a plain Kubernetes `Service` as the `backe
466514
{{% /tab %}}
467515
{{< /tabs >}}
468516

469-
## Optional Step 7: Apply token-based rate limiting
517+
## Optional Step 6: Apply token-based rate limiting
470518

471519
How token counting works: Agentgateway reads `usage.total_tokens` from the JSON response body returned by the inference service. Each request deducts that many tokens from the bucket. When the bucket empties, subsequent requests receive `429 Too Many Requests` until the next fill interval.
472520

473-
1. Apply an {{< reuse "agw-docs/snippets/policy.md" >}} that caps requests at **70 tokens per minute**. The policy targets the `mock-llm-ai` route that selects the `{{< reuse "agw-docs/snippets/backend.md" >}}`.
521+
1. Apply an {{< reuse "agw-docs/snippets/policy.md" >}} that caps
522+
requests at **70 tokens per minute**. The policy targets the KServe-generated
523+
`mock-llm-kserve-route`, which selects the
524+
`{{< reuse "agw-docs/snippets/backend.md" >}}`.
474525
```yaml
475526
kubectl apply -f - <<EOF
476527
apiVersion: {{< reuse "agw-docs/snippets/api-version.md" >}}
@@ -482,7 +533,7 @@ How token counting works: Agentgateway reads `usage.total_tokens` from the JSON
482533
targetRefs:
483534
- group: gateway.networking.k8s.io
484535
kind: HTTPRoute
485-
name: mock-llm-ai
536+
name: mock-llm-kserve-route
486537
traffic:
487538
rateLimit:
488539
local:
@@ -501,7 +552,7 @@ How token counting works: Agentgateway reads `usage.total_tokens` from the JSON
501552
{{% tab name="Cloud Provider LoadBalancer" %}}
502553
3. Get the external address of the gateway and save it in an environment variable.
503554
```shell
504-
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kserve agentgateway-proxy \
555+
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kserve kserve-ingress-gateway \
505556
-o=jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
506557
echo $INGRESS_GW_ADDRESS
507558
```
@@ -511,6 +562,7 @@ How token counting works: Agentgateway reads `usage.total_tokens` from the JSON
511562
for i in $(seq 1 30); do
512563
curl -s -o /dev/null -w "%{http_code}\n" \
513564
-X POST http://$INGRESS_GW_ADDRESS/v1/chat/completions \
565+
-H "Host: mock-llm-kserve-test.example.com" \
514566
-H "Content-Type: application/json" \
515567
-d '{"model": "mock-llm", "messages": [{"role": "user", "content": "Hello"}]}'
516568
done
@@ -581,12 +633,12 @@ Remove the resources created in this guide.
581633
```shell
582634
kubectl delete agentgatewaypolicy llm-token-budget -n kserve-test
583635
kubectl delete {{< reuse "agw-docs/snippets/policy.md" >}} -n kserve-test model-echo-headers
584-
kubectl delete httproute mock-llm-ai -n kserve-test
636+
kubectl delete llminferenceservice mock-llm -n kserve-test
585637
kubectl delete agentgatewaybackend mock-llm-backend -n kserve-test
586-
kubectl delete inferenceservice mock-llm -n kserve-test
587638
kubectl delete namespace kserve-test
588-
helm uninstall kserve -n kserve
589-
helm uninstall kserve-crd
639+
helm uninstall kserve-runtime-configs -n kserve
640+
helm uninstall kserve-llmisvc-resources -n kserve
641+
helm uninstall kserve-llmisvc-crd -n kserve
590642
kubectl delete gateway kserve-ingress-gateway -n kserve
591643
kubectl delete namespace kserve
592644
```

0 commit comments

Comments
 (0)