Documentation navigation
AI Gateway – Merged Deployment Guide
Single-cluster topology: workload + inference live together. No public LB, no cross-cluster JWT. Self-hosted models only (no Azure / Vertex / Anthropic / Nebius).
| Path | Chart | Resources | Reason |
|---|---|---|---|
| ASR | inference-service per model |
plain HTTPRoute + predictor Service |
Triton clients set x-corti-model header explicitly; no body→header rewrite needed. |
| LLM | inference-service per model + ai-gateway-route (cluster-wide) |
predictor Service + AIGatewayRoute / AIServiceBackend / Backend |
OpenAI clients send model in body only; AI Gateway ExtProc must rewrite body.model → x-ai-eg-model before route match. ExtProc wires only on AIGatewayRoute. |
Request flow
---
config:
look: neo
theme: redux
layout: elk
---
flowchart LR
subgraph cluster["Merged Cluster (workload + inference)"]
subgraph workload_ns["Workload Namespaces (base, agents, ...)"]
ml_transcription["ml-service-transcription-*"]
ml_summarisation["ml-service-summarisation"]
ml_llm_reasoning["ml-service-llm-reasoning"]
ml_other["other ml-services"]
end
subgraph shared_ns["Namespace: shared (envoy-gateway chart)"]
subgraph envoy_internal_gateway["Envoy Internal Gateway (ASR)"]
ig_listener["Listener :80\nallowedRoutes: All"]
end
subgraph envoy_internal_ai_gateway["Envoy Internal AI Gateway (LLM)"]
aig_listener["Listener :80\nallowedRoutes: All"]
extproc["ExtProc Sidecar\nbody validation\nbody.model -> x-ai-eg-model"]
end
end
subgraph mlservices_ns["Namespace: mlservices (one inference-service release per model)"]
subgraph asr_inference["ASR inference-service releases"]
asr_routes["HTTPRoute (per model)\nparentRef: envoy-gateway-internal\nmatch: x-corti-model = MODEL\nbackendRef: RELEASE-predictor"]
asr_predictors["KServe Predictor Services\nasr-tdt-*, asr-aed-*,\nfaster-whisper-*, diarizer-*"]
end
subgraph llm_inference["LLM (ai-gateway-route chart + inference-service)"]
llm_routes["AIGatewayRoute (per model)\nparentRef: envoy-ai-gateway-internal\nmatch: x-ai-eg-model = MODEL\nbackendRef: AIServiceBackend"]
llm_aisb["AIServiceBackend + Backend\nfqdn: RELEASE-predictor.mlservices.svc.cluster.local:80"]
llm_predictors["KServe Predictor Services\n(deployed by inference-service)\ncorti-s1-*, corti-s1-mini-*,\ncorti-s1-tiny-*, corti-llm-gen1, ..."]
end
end
ml_transcription -->|http\nHeader x-corti-model = asr_model| ig_listener
ml_other -->|http| ig_listener
ig_listener -.->|attached via\nparentRef| asr_routes
asr_routes -->|backendRefs Service\nsame namespace| asr_predictors
ml_summarisation -->|OpenAI POST /v1/chat/completions\nbody.model = MODEL| aig_listener
ml_llm_reasoning -->|OpenAI POST /v1/chat/completions\nbody.model = MODEL| aig_listener
aig_listener --> extproc
extproc -.->|injects header\nx-ai-eg-model = MODEL| llm_routes
llm_routes --> llm_aisb
llm_aisb -->|fqdn| llm_predictors
asr_predictors@{shape: procs}
llm_predictors@{shape: procs}
style envoy_internal_gateway fill:#A594F9
style envoy_internal_ai_gateway fill:#A594F9
style ig_listener fill:#A594F9
style aig_listener fill:#A594F9
style extproc fill:#CBE2B5
style asr_routes fill:#A594F9
style llm_routes fill:#A594F9
style llm_aisb fill:#CBE2B5
style asr_predictors fill:#B7E0FF
style llm_predictors fill:#B7E0FF
end
subgraph legend["Legend"]
l_envoy["Envoy Gateway resource"]
l_ai["Envoy AI Gateway resource (ExtProc)"]
l_svc["Service / App / Predictor"]
style l_envoy fill:#A594F9
style l_ai fill:#CBE2B5
style l_svc fill:#B7E0FF
end
ASR:
- ml-service hits
envoy-gateway-internal.shared:80withx-corti-modelheader set by client. - Plain
HTTPRoute(frominference-service) matches header → forwards to<release>-predictorService inmlservices.
LLM:
- ml-service POSTs OpenAI body to
envoy-gateway-internal-ai.shared:80/v1/chat/completions, no header. - AI Gateway Controller injects ExtProc sidecar (because
AIGatewayRouteexists). ExtProc readsbody.model, setsx-ai-eg-modelheader. AIGatewayRoute(fromai-gateway-routechart) matches header →AIServiceBackend→Backend(fqdn = in-cluster Service DNS).- Forwards to
<release>-predictorService inmlservices.
No public DNS, no TLS, no JWT, no ReferenceGrant (route + Service co-located).
Chart values
envoy-gateway chart
team: platform
dnsZone: corti.app
cm:
common:
CLUSTER_NAME: <cluster-name> # required
platform:
type: aks # or scaleway
# --- Disable external + public gateways ---
gateway:
enabled: false # inference cluster external LB
publicLLMGateway:
enabled: false # Keycloak-gated public ingress
aiGatewayProxy:
enabled: false # JWKS-gated public proxy
# --- Enable internal gateways (ASR + LLM) ---
internalGateway:
enabled: true
name: envoy-gateway-internal
service:
type: ClusterIP
inferenceClusterAuth:
enabled: false # skips SA + ExternalSecret JWT plumbing
internalAIGateway:
enabled: true
name: envoy-ai-gateway-internal
service:
type: ClusterIP
inferenceClusterAuth:
enabled: false # skips cross-cluster JWT (ESO stack preserved)
# No upstream provider creds — self-hosted only
apiKeys:
azure: { enabled: false }
nebius: { enabled: false }
googleVertexAI: { enabled: false }
anthropic: { enabled: false }
# --- Controllers still required ---
ai-gateway-crds-helm:
enabled: true
ai-gateway-helm:
enabled: true
gateway-crds-helm:
enabled: true
gateway-helm:
enabled: true
# --- No ACME (external gateway dead) ---
issuers:
letsEncryptProduction: { enabled: false }
letsEncryptStaging: { enabled: false }
networkPolicy:
enabled: true
acmeHttpChallenge:
enabled: false
allowInternalToExternalGateway:
enabled: false
inference-service chart (per model release, ASR + LLM)
defaults:
gateway:
internal:
enabled: true # parent on envoy-(ai-)gateway-internal, skip hostname/sectionName
access:
enabled: false # no JWT — drops SecurityPolicy + HTTPS redirect
For LLM models, the HTTPRoute rendered here is functionally superseded by the AIGatewayRoute from ai-gateway-route (header match is the same; AIGatewayRoute additionally activates ExtProc body→header rewrite). Leave both or delete the LLM HTTPRoute — keeping both is harmless.
ai-gateway-route chart (cluster-wide, LLM only)
Required for LLM path: deploys AIGatewayRoute so AI Gateway Controller wires ExtProc on envoy-ai-gateway-internal. Uses chart ≥ 3.6.0 for in-cluster mode toggles (tls.enabled: false, auth.enabled: false, model fqdn override).
asr: {} # ASR handled by inference-service
llm:
defaults:
auth: { enabled: false } # no BackendSecurityPolicy
tls: { enabled: false } # no BackendTLSPolicy
port: 80
providers:
cortiSelfHosted:
quotas: -1 # no rate limit
defaultModelSchema: { name: OpenAI, version: "v1" }
models:
corti-s1-tiny-20260707:
enabled: true
provider: cortiSelfHosted
fqdn: corti-s1-tiny-20260707-predictor.mlservices.svc.cluster.local
timeouts:
request: 120s
backendRequest: 120s
bodyMutation:
set:
enabled: true
chat_template_kwargs:
enabled: true
value: '{"enable_thinking": false}'
# Add other body mutation rules like temperature, top_p, top_k, etc... if needed
# Configure virtualModels to point to the model(s)
virtualModels:
ml-service-summarisation:
enabled: true
models:
- corti-s1-tiny-20260707
ml-service-llm-reasoning:
enabled: true
models:
- corti-s1-tiny-20260707
copilot-api-v2:
enabled: true
models:
- corti-s1-tiny-20260707
ml-service-formatting:
enabled: true
models:
- corti-s1-tiny-20260707
ml-service-voice-command:
enabled: true
models:
- corti-s1-tiny-20260707
ml-service-facts:
enabled: true
models:
- corti-s1-tiny-20260707
Add one models.<name> entry per LLM, pointing at the in-cluster predictor DNS. No upstream provider credentials, no public FQDN.
What does not change
- ml-service env vars: still point at
envoy-gateway-internal.shared:80(ASR) andhttp://envoy-gateway-internal-ai.shared:80/v1(LLM). No image rebuild. - Header contract:
x-corti-model(ASR, set by client).body.model(LLM, rewritten by ExtProc). - OpenAI request shape unchanged — ExtProc still rewrites body→header.
- Predictor Services themselves (KServe
<release>-predictor).
Caveats
- All workloads + data in one cluster = one jurisdiction. Not suitable when data sovereignty requires inference in a separate region.
- Inference GPU pool not shareable across multiple workload clusters.
- Verify no ml-service has hardcoded
*.<cluster>.corti.appURL. Grep deployment values before cutover.