Configure ingress
Configure how OME exposes InferenceServices through Kubernetes Ingress or Gateway API with the ingress block of the inferenceservice-config ConfigMap.
Turn on ingress creation, and OME exposes your InferenceServices outside the cluster through a Kubernetes Ingress or Gateway API HTTPRoutes, at a hostname under your domain. You set a few values of the ome-resources chart, deploy an InferenceService, and send it a request through its route. For how the pieces fit together, see Ingress and external access.
Ingress creation is off by default, and OME creates a Service named after each InferenceService instead. To reach that Service from outside the cluster without an Ingress controller or a Gateway, see Expose a service without ingress.
Choose an ingress type¶
enableGatewayAPI picks the ingress type for the whole cluster. By default it's false, and OME creates Kubernetes Ingresses. Set it to true, and OME creates Gateway API HTTPRoutes instead.
| Kubernetes Ingress | Gateway API | |
|---|---|---|
| Served by | Your Ingress controller, through the IngressClass in ingressGateway.className |
Your Gateway, named in omeIngressGateway as <namespace>/<name> |
| Hostnames | One per InferenceService, <name>.<namespace>.<domain> by default, plus one per component when there's a router or decoder |
By default, one shared host, llm.<domain>, with a path prefix per InferenceService. Optionally, one hostname per InferenceService |
| Also supports | Several Gateways, Gateways chosen by namespace, route timeouts, and sticky routing by header | |
| Versions | v1.2.2 and later | Since v1.3 |
Use Gateway API if you run Envoy Gateway and OME v1.3 or later. Otherwise, use a Kubernetes Ingress with the Ingress controller you run, such as ingress-nginx. RawDeployment, OMENative and MultiNode (deprecated) InferenceServices all get routes. A VirtualDeployment InferenceService gets none.
Step 1: Turn on ingress creation¶
The ingress settings are values of the ome-resources chart, under ome.controller.ingressGateway. The chart writes them into the ingress key of the inferenceservice-config ConfigMap, which the controller reads. Add the values for your ingress type to your values file:
ome:
controller:
ingressGateway:
disableIngressCreation: false
domain: example.com
ingressGateway:
className: nginxOME creates an Ingress with the IngressClass nginx for each InferenceService, at the hostname <name>.<namespace>.example.com.
ome:
controller:
ingressGateway:
disableIngressCreation: false
enableGatewayAPI: true
domain: example.com
omeIngressGateway: envoy-gateway-system/ome-gatewayOME attaches the HTTPRoutes to the Gateway ome-gateway in envoy-gateway-system. The routes share the host llm.example.com, and each InferenceService gets the path prefix /<namespace>/<name>/.
Replace example.com with a domain whose DNS you control. Configuration reference lists the other settings.
OME doesn't set up TLS or authenticate requests. Terminate TLS and authenticate requests at your Ingress controller or Gateway, and set urlScheme: https so that status.url matches. See Security.
Upgrade the release with the chart version that's installed, so that OME stays in step with the ome-crd chart. The CHART column of helm list -n ome shows the version, such as ome-resources-1.2.2. On v1.2.2:
helm upgrade --install ome oci://ghcr.io/moirai-internal/charts/ome-resources \
--namespace ome --version 1.2.2 -f values.yamlOn a build of main, upgrade from your checkout instead, as in Install from source:
helm upgrade --install ome ./charts/ome-resources --namespace ome -f values.yamlHelm reports that the release ome has been upgraded, with the status deployed. Since v1.3, the upgrade restarts the controller when these values change, and the controller applies them to every InferenceService as it starts. On v1.2.2, restart it yourself:
kubectl rollout restart deployment ome-controller-manager -n omedeployment.apps/ome-controller-manager restartedCheck what the chart wrote into the ConfigMap:
kubectl get configmap inferenceservice-config -n ome -o jsonpath='{.data.ingress}' \
| jq '{disableIngressCreation, enableGatewayAPI, ingressDomain, ingressClassName, omeIngressGateway}'{
"disableIngressCreation": false,
"enableGatewayAPI": false,
"ingressDomain": "example.com",
"ingressClassName": "nginx",
"omeIngressGateway": ""
}{
"disableIngressCreation": false,
"enableGatewayAPI": true,
"ingressDomain": "example.com",
"ingressClassName": "istio",
"omeIngressGateway": "envoy-gateway-system/ome-gateway"
}ingressClassName keeps the chart's default. Gateway API ignores it.
Step 2: Deploy an InferenceService¶
Create an InferenceService that serves the BaseModel:
apiVersion: ome.io/v1beta1
kind: InferenceService
metadata:
name: llama-3-2-1b-instruct
namespace: llama-demo
spec:
model:
name: llama-3-2-1b-instruct
kind: BaseModel
runtime:
name: srt-llama-3-2-1b-instruct
engine:
minReplicas: 1
maxReplicas: 1kubectl apply -f isvc.yamlinferenceservice.ome.io/llama-3-2-1b-instruct createdOME creates the routes once the engine is ready. With Gateway API, Ready also waits for the Gateway to accept them. Wait for it:
kubectl wait --for=condition=Ready inferenceservice/llama-3-2-1b-instruct -n llama-demo --timeout=30minferenceservice.ome.io/llama-3-2-1b-instruct condition metList the routes that OME created:
kubectl get ingress llama-3-2-1b-instruct -n llama-demo \
-o 'custom-columns=CLASS:.spec.ingressClassName,HOST:.spec.rules[*].host,SERVICE:.spec.rules[*].http.paths[*].backend.service.name,PORT:.spec.rules[*].http.paths[*].backend.service.port.number'CLASS HOST SERVICE PORT
nginx llama-3-2-1b-instruct.llama-demo.example.com llama-3-2-1b-instruct-engine 8080The Ingress sends requests for the InferenceService's hostname to the engine's Service, or to the router's when there is one. With a router or decoder, each component also gets a hostname of its own, such as llama-3-2-1b-instruct-engine.llama-demo.example.com.
kubectl get httproute -n llama-demo \
-o 'custom-columns=NAME:.metadata.name,HOST:.spec.hostnames[*],PATH:.spec.rules[*].matches[*].path.value,BACKEND:.spec.rules[*].backendRefs[*].name'NAME HOST PATH BACKEND
llama-3-2-1b-instruct llm.example.com /llama-demo/llama-3-2-1b-instruct/ llama-3-2-1b-instruct-engine
llama-3-2-1b-instruct-engine llm.example.com /llama-demo/llama-3-2-1b-instruct-engine/ llama-3-2-1b-instruct-engineThe route named after the InferenceService leads to the router, or to the engine when there's no router, and each component gets a route of its own. The routes strip their path prefix, so /llama-demo/llama-3-2-1b-instruct/v1/chat/completions reaches the engine as /v1/chat/completions.
For an InferenceService without a traffic policy of its own, OME also creates a BackendTrafficPolicy named after it. The policy makes Envoy Gateway hash the x-routing-key and X-SMG-Routing-Key headers, so that requests with the same values reach the same pod. It covers only the route named after the InferenceService: a request through the engine's route, under /llama-demo/llama-3-2-1b-instruct-engine/, isn't hashed. The policy also sets mergeType: StrategicMerge, so a BackendTrafficPolicy of your own that targets the Gateway keeps applying: the route inherits its settings, such as circuit breakers and timeouts, and the default policy adds the hashing.
Step 3: Send a request¶
The InferenceService's status has the URL to call:
kubectl get inferenceservice llama-3-2-1b-instruct -n llama-demo -o jsonpath='{.status.url}{"\n"}'http://llama-3-2-1b-instruct.llama-demo.example.com:8080The URL carries the port of the engine's Service. Send requests to your Ingress controller's HTTP port instead, usually 80.
http://llm.example.com/llama-demo/llama-3-2-1b-instruct/Send a chat completion request through the route. The address 203.0.113.10 stands for your Ingress controller's or Gateway's address, which kubectl get ingress llama-3-2-1b-instruct -n llama-demo or kubectl get gateway ome-gateway -n envoy-gateway-system shows in its ADDRESS column. With DNS records in place, leave out --resolve.
curl -s --resolve llama-3-2-1b-instruct.llama-demo.example.com:80:203.0.113.10 \
http://llama-3-2-1b-instruct.llama-demo.example.com/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "meta-llama/Llama-3.2-1B-Instruct", "messages": [{"role": "user", "content": "In one sentence, what is an Ingress?"}], "max_tokens": 64}' \
| jq -r '.choices[0].message.content'curl -s --resolve llm.example.com:80:203.0.113.10 \
http://llm.example.com/llama-demo/llama-3-2-1b-instruct/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "meta-llama/Llama-3.2-1B-Instruct", "messages": [{"role": "user", "content": "In one sentence, what is an Ingress?"}], "max_tokens": 64}' \
| jq -r '.choices[0].message.content'jq prints the model's answer, which varies from run to run. If the command prints nothing or an error, see Requests don't reach the service.
Configuration reference¶
The settings are values of the ome-resources chart under ome.controller.ingressGateway. Where a setting has an annotation, the annotation overrides it for one InferenceService:
| Helm value | Chart default | Annotation | Effect |
|---|---|---|---|
disableIngressCreation |
true |
ome.io/ingress-disable-creation |
true turns ingress creation off, and OME creates a Service instead. See Turn off ingress creation. |
enableGatewayAPI |
false |
true creates HTTPRoutes instead of an Ingress. Since v1.3. |
|
domain |
svc.cluster.local |
ome.io/ingress-domain |
Domain of the hostnames, .IngressDomain in domainTemplate. |
domainTemplate |
{{ .Name }}.{{ .Namespace }}.{{ .IngressDomain }} |
ome.io/ingress-domain-template |
Go template for Ingress hostnames, and for HTTPRoute hostnames when perISVCSubdomain is true. |
urlScheme |
http |
ome.io/ingress-url-scheme |
Scheme of the URLs in the InferenceService's status. Set https when your Ingress controller or Gateway terminates TLS. |
ingressGateway.className |
istio |
IngressClass of the Ingress. | |
omeIngressGateway |
"" |
ome.io/ingress-gateway |
Gateway for the HTTPRoutes, as <namespace>/<name>. Gateway API needs it. |
omeIngressGatewayClass |
"" |
Name of the primary Gateway's entry in status.addresses, a lowercase label such as internal. Since v1.3. |
|
perISVCSubdomain |
false |
ome.io/ingress-per-isvc-subdomain |
true gives an InferenceService's HTTPRoutes a hostname of its own, from domainTemplate, in place of the shared host. See Choose a Gateway API host scheme. Since v1.3. |
sharedHostPrefix |
llm |
ome.io/ingress-shared-host-prefix |
First label of the shared host, <sharedHostPrefix>.<domain>. Empty means the bare domain. Since v1.3. |
additionalIngressGateways |
[] |
ome.io/ingress-additional-gateways |
More Gateways for the HTTPRoutes. See Use multiple gateways. Since v1.3. |
namespaceIngressGateways |
{} |
Gateways for the InferenceServices in a namespace. See Use per-namespace gateways. Since v1.3. | |
consistentHashHeaders |
["x-routing-key", "X-SMG-Routing-Key"] |
ome.io/ingress-consistent-hash-headers |
Headers that the default BackendTrafficPolicy hashes, so that requests with the same values reach the same pod. Since v1.3. |
defaultRouteTimeoutSeconds |
0 |
Request timeout of the HTTPRoutes, in seconds. 0 turns it off. See Configure route timeouts. Since v1.3. |
Leave ingressGateway.gateway, ingressGateway.gatewayService, pathTemplate, additionalIngressDomains and disableIstioVirtualHost at the chart's values. OME ignores them, but a wrong value stops the controller from starting, as The controller keeps restarting describes.
domainTemplate can use .Name, .Namespace, .IngressDomain, .Annotations and .Labels, and must render a valid DNS name. Hostnames shows the names it renders.
Per-service overrides¶
Ingress annotations gives the values that the annotations take. For example, to move one InferenceService to another domain:
kubectl annotate inferenceservice llama-3-2-1b-instruct -n llama-demo ome.io/ingress-domain=internal.example.cominferenceservice.ome.io/llama-3-2-1b-instruct annotatedIts Ingress hostname becomes llama-3-2-1b-instruct.llama-demo.internal.example.com. With Gateway API, its shared host becomes llm.internal.example.com, which the Gateway's listener must accept.
OME copies the InferenceService's annotations to its routes, so annotations for your Ingress controller or Gateway, such as ingress-nginx's, go on the InferenceService. Set them when you create it: OME copies a later change to an existing route only along with a change to the route's spec.
Kustomize installs¶
With a kustomize install, edit the ingress key of the inferenceservice-config ConfigMap in the ome namespace, apply it, and restart the controller as in Step 1. Its settings have the names of the Helm values, except for four: domain is ingressDomain, ingressGateway.className is ingressClassName, ingressGateway.gateway is ingressGateway, and ingressGateway.gatewayService is ingressService.
The kustomize ConfigMap leaves out sharedHostPrefix, consistentHashHeaders and defaultRouteTimeoutSeconds. Without them, the shared host is the bare domain, OME hashes only x-routing-key, and Envoy's 15-second request timeout applies. To match a Helm install, add the three keys with the chart defaults.
Turn off ingress creation¶
To turn ingress creation off for the whole cluster, set disableIngressCreation: true in your values file, and upgrade the release as in Step 1, including the restart on v1.2.2.
To turn it off for one InferenceService, annotate it:
kubectl annotate inferenceservice llama-3-2-1b-instruct -n llama-demo ome.io/ingress-disable-creation=trueinferenceservice.ome.io/llama-3-2-1b-instruct annotatedWhile ingress creation is off for the cluster, ome.io/ingress-disable-creation: "false" turns it on for one InferenceService.
OME leaves the routes it created in place, and they keep serving traffic. Delete them yourself:
kubectl delete ingress llama-3-2-1b-instruct -n llama-demoingress.networking.k8s.io "llama-3-2-1b-instruct" deleted from llama-demo namespacekubectl delete httproute llama-3-2-1b-instruct llama-3-2-1b-instruct-engine -n llama-demo
kubectl delete backendtrafficpolicy llama-3-2-1b-instruct -n llama-demohttproute.gateway.networking.k8s.io "llama-3-2-1b-instruct" deleted from llama-demo namespace
httproute.gateway.networking.k8s.io "llama-3-2-1b-instruct-engine" deleted from llama-demo namespace
backendtrafficpolicy.gateway.envoyproxy.io "llama-3-2-1b-instruct" deleted from llama-demo namespaceOME creates the Service named after the InferenceService instead, and sets IngressReady to True with the reason IngressDisabled. Check the Service:
kubectl get service llama-3-2-1b-instruct -n llama-demo \
-o 'custom-columns=NAME:.metadata.name,TYPE:.spec.type,PORT:.spec.ports[*].port'NAME TYPE PORT
llama-3-2-1b-instruct ClusterIP 8080The InferenceService's status.url becomes http://llama-3-2-1b-instruct.llama-demo.svc.cluster.local:8080. To reach the Service from outside the cluster, see Expose a service without ingress.
Troubleshooting¶
Start with the InferenceService's IngressReady condition:
kubectl get inferenceservice llama-3-2-1b-instruct -n llama-demo \
-o 'custom-columns=READY:.status.conditions[?(@.type=="IngressReady")].status,REASON:.status.conditions[?(@.type=="IngressReady")].reason,MESSAGE:.status.conditions[?(@.type=="IngressReady")].message'For example, while ingress creation is off:
READY REASON MESSAGE
True IngressDisabled Ingress creation is disabled, using external service for accessTrue with no reason means that OME created the routes, and with Gateway API, that the Gateway accepted them. When IngressReady is True explains the other reasons.
OME doesn't create a route¶
- With the reason
IngressDisabled, ingress creation is off. Check the ConfigMap as in Step 1, and look for theome.io/ingress-disable-creationannotation on the InferenceService. If the ConfigMap has"disableIngressCreation": false, restart the controller. - With the reason
ComponentNotReady, the router, decoder or engine isn't ready, and OME creates its route when it is. With Gateway API, an OMENative component needs only a serving Instance for its route, so the reason means it has none, as Routes describes. See Troubleshoot an InferenceService. - If the condition is missing, or doesn't change after you restart the controller, an error stops OME before it updates the status. Read the logs of every controller replica:
kubectl logs -n ome -l control-plane=ome-controller-manager --tail=-1Look for errors that name the InferenceService:
no kind is registered for the type v1.HTTPRoute: Gateway API on v1.2.2, or on a controller that started before you setenableGatewayAPI: true. Use Kubernetes Ingress on v1.2.2, and restart the controller on later releases.no matches for kind "BackendTrafficPolicy": Envoy Gateway's CRDs are missing. OME creates the HTTPRoutes, but the InferenceService doesn't becomeReady. Install Envoy Gateway, then restart the controller.invalid domain name: see The hostname is missing or wrong.
The controller keeps restarting¶
If the controller's pods restart over and over, and their logs show Failed to initialize ingress configuration, the controller rejects the ingress key. The error after that message says why. Since v1.3, ingressGateway.gateway and ingressGateway.gatewayService must be set, as they are in the chart. A list in additionalIngressDomains breaks the key on any release. Fix the value in your values file, and upgrade the release.
The hostname is missing or wrong¶
- With Kubernetes Ingress,
status.urlstays empty until OME creates the Ingress, once the component is ready. - A hostname under
svc.cluster.localmeans thatdomainstill has the chart's default. One underexample.comthat you didn't set means thatdomainis empty. Setdomainas in Step 1. - With Gateway API, a shared host without
llm.means thatsharedHostPrefixis empty, as in kustomize installs, or that the InferenceService'some.io/ingress-shared-host-prefixannotation is empty. - If an InferenceService has a hostname of its own where you expected the shared host, or the other way around, check
perISVCSubdomainand its annotation. See Choose a Gateway API host scheme. invalid domain namein the controller's logs means thatdomainTemplate, or theome.io/ingress-domainorome.io/ingress-domain-templateannotation, renders an invalid hostname. Until you fix it, OME doesn't create or update the routes, or the serving workload ofRawDeploymentandMultiNode(deprecated) components.
Requests don't reach the service¶
- Check that the hostname resolves to your Ingress controller or Gateway, or use
curl --resolveas in Step 3. - With Kubernetes Ingress,
IngressReadyturnsTrueas soon as OME creates the Ingress, whether or not your Ingress controller serves it. Check thatingressGateway.classNamenames an IngressClass thatkubectl get ingressclasslists, then readkubectl describe ingress llama-3-2-1b-instruct -n llama-demoand the Ingress controller's log. - With Gateway API, read the route's status with
kubectl describe httproute llama-3-2-1b-instruct -n llama-demo.NotAllowedByListenersmeans that no listener of the Gateway allows routes from the InferenceService's namespace. Set the listener'sallowedRoutes.namespaces.fromtoAll, or toSelectorwith a selector that matches the namespace.NoMatchingListenerHostnamemeans that no listener's hostname matches the route's. - With Gateway API, if
IngressReadystaysFalsewith the reasonParentStatusNotAvailable, no Gateway has reported on the route. Check thatomeIngressGatewaynames an existing Gateway as<namespace>/<name>. - With the shared host, requests must start with the path prefix,
/<namespace>/<name>/. A request to/v1/chat/completionson the shared host matches no route. - If long requests fail after about 15 seconds, the route has no timeout of its own, and Envoy's 15-second default applies. Set
defaultRouteTimeoutSeconds, or the component'stimeoutSeconds, as Configure route timeouts shows.
Requests with the same header reach different pods¶
With Gateway API, first check the path. The default BackendTrafficPolicy covers only the route named after the InferenceService, so a request through a component's route, such as /llama-demo/llama-3-2-1b-instruct-engine/, isn't hashed. Send it through /llama-demo/llama-3-2-1b-instruct/, or declare the headers in spec.traffic, whose policy covers every route.
IngressReady can also be True while Envoy Gateway hasn't accepted the BackendTrafficPolicy. Read the policy's status:
kubectl describe backendtrafficpolicy llama-3-2-1b-instruct -n llama-demoThe Status section holds what Envoy Gateway reported about the policy.
Clean up¶
Delete the InferenceService. The routes, BackendTrafficPolicy and Service that OME created for it go with it:
kubectl delete inferenceservice llama-3-2-1b-instruct -n llama-demoinferenceservice.ome.io "llama-3-2-1b-instruct" deleted from llama-demo namespaceTo stop OME creating routes, turn ingress creation off as in Turn off ingress creation.
Next steps¶
- Choose a Gateway API host scheme: a shared host with path prefixes, or a hostname per InferenceService.
- Use multiple gateways: attach the routes to more than one Gateway, such as an internal and an external one.
- Configure route timeouts: keep Envoy's 15-second default from cutting off long requests.