--- title: Configure ingress description: 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](../../concepts/rollouts-and-traffic/ingress.md). 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](expose-without-ingress.md).
- OME installed with the `ome-resources` Helm chart, as the release `ome` in the namespace `ome`, and the values file you installed it with. See [Install OME](../../getting-started/install.md). For kustomize installs, see [Kustomize installs](#kustomize-installs). - For Kubernetes Ingress, an Ingress controller and its IngressClass. `kubectl get ingressclass` lists the classes. The examples use the class `nginx`. - For Gateway API, OME v1.3 or later, the Gateway API CRDs, and Envoy Gateway. Until v1.3 is released, run [a build of `main`](../../getting-started/install.md#install-from-source). - For Gateway API, a Gateway for the routes. The examples use the Gateway `ome-gateway` in the namespace `envoy-gateway-system`, with an HTTP listener on port 80 that allows routes from the namespace `llama-demo`. If the listener sets a hostname, it must match `llm.example.com`. - The BaseModel `llama-3-2-1b-instruct` in the namespace `llama-demo`, and the ClusterServingRuntime `srt-llama-3-2-1b-instruct`, as in [Serve models from a PVC](../deploy-models/serve-models-from-pvc.md) up to its Step 3. To use an InferenceService that you already run, skip the apply in Step 2, and change the names in the commands. - Optionally, DNS records that point your hostnames at the Ingress controller or Gateway. Step 3 uses `curl --resolve` instead.
## 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 `/` | | Hostnames | One per InferenceService, `..` by default, plus one per component when there's a router or decoder | By default, one shared host, `llm.`, 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](../../concepts/omenative/overview.md) 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: === "Kubernetes Ingress" ```yaml title="values.yaml" ome: controller: ingressGateway: disableIngressCreation: false domain: example.com ingressGateway: className: nginx ``` OME creates an Ingress with the IngressClass `nginx` for each InferenceService, at the hostname `..example.com`. === "Gateway API" ```yaml title="values.yaml" ome: controller: ingressGateway: disableIngressCreation: false enableGatewayAPI: true domain: example.com omeIngressGateway: envoy-gateway-system/ome-gateway ``` OME 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 `///`. Replace `example.com` with a domain whose DNS you control. [Configuration reference](#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](../../concepts/rollouts-and-traffic/ingress.md#security). !!! warning Turning on ingress creation deletes the Service named after each existing InferenceService. Clients in the cluster that call `..svc.cluster.local` must switch to the route, or to a component's Service, such as `llama-3-2-1b-instruct-engine`. 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: ```bash helm upgrade --install ome oci://ghcr.io/moirai-internal/charts/ome-resources \ --namespace ome --version 1.2.2 -f values.yaml ``` On a build of `main`, upgrade from your checkout instead, as in [Install from source](../../getting-started/install.md#install-from-source): ```bash helm upgrade --install ome ./charts/ome-resources --namespace ome -f values.yaml ``` Helm 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: ```bash kubectl rollout restart deployment ome-controller-manager -n ome ``` ```output deployment.apps/ome-controller-manager restarted ``` Check what the chart wrote into the ConfigMap: ```bash kubectl get configmap inferenceservice-config -n ome -o jsonpath='{.data.ingress}' \ | jq '{disableIngressCreation, enableGatewayAPI, ingressDomain, ingressClassName, omeIngressGateway}' ``` === "Kubernetes Ingress" ```output { "disableIngressCreation": false, "enableGatewayAPI": false, "ingressDomain": "example.com", "ingressClassName": "nginx", "omeIngressGateway": "" } ``` === "Gateway API" ```output { "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: ```yaml title="isvc.yaml" 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: 1 ``` ```bash kubectl apply -f isvc.yaml ``` ```output inferenceservice.ome.io/llama-3-2-1b-instruct created ``` OME creates the routes once the engine is ready. With Gateway API, `Ready` also waits for the Gateway to accept them. Wait for it: ```bash kubectl wait --for=condition=Ready inferenceservice/llama-3-2-1b-instruct -n llama-demo --timeout=30m ``` ```output inferenceservice.ome.io/llama-3-2-1b-instruct condition met ``` List the routes that OME created: === "Kubernetes Ingress" ```bash 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' ``` ```output CLASS HOST SERVICE PORT nginx llama-3-2-1b-instruct.llama-demo.example.com llama-3-2-1b-instruct-engine 8080 ``` The 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`. === "Gateway API" ```bash 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' ``` ```output 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-engine ``` The 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](../../concepts/rollouts-and-traffic/traffic-policy.md#defaults-when-you-declare-nothing) 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: ```bash kubectl get inferenceservice llama-3-2-1b-instruct -n llama-demo -o jsonpath='{.status.url}{"\n"}' ``` === "Kubernetes Ingress" ```output http://llama-3-2-1b-instruct.llama-demo.example.com:8080 ``` The URL carries the port of the engine's Service. Send requests to your Ingress controller's HTTP port instead, usually 80. === "Gateway API" ```output 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`. === "Kubernetes Ingress" ```bash 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' ``` === "Gateway API" ```bash 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](#requests-dont-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](#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 `/`. 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](gateway-host-schemes.md). Since v1.3. | | `sharedHostPrefix` | `llm` | `ome.io/ingress-shared-host-prefix` | First label of the shared host, `.`. Empty means the bare domain. Since v1.3. | | `additionalIngressGateways` | `[]` | `ome.io/ingress-additional-gateways` | More Gateways for the HTTPRoutes. See [Use multiple gateways](multiple-gateways.md). Since v1.3. | | `namespaceIngressGateways` | `{}` | | Gateways for the InferenceServices in a namespace. See [Use per-namespace gateways](namespace-gateways.md). 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](configure-route-timeouts.md). 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](#the-controller-keeps-restarting) describes. `domainTemplate` can use `.Name`, `.Namespace`, `.IngressDomain`, `.Annotations` and `.Labels`, and must render a valid DNS name. [Hostnames](../../concepts/rollouts-and-traffic/ingress.md#hostnames-and-urls) shows the names it renders. ### Per-service overrides [Ingress annotations](../../reference/api/labels-and-annotations.md#ingress-annotations) gives the values that the annotations take. For example, to move one InferenceService to another domain: ```bash kubectl annotate inferenceservice llama-3-2-1b-instruct -n llama-demo ome.io/ingress-domain=internal.example.com ``` ```output inferenceservice.ome.io/llama-3-2-1b-instruct annotated ``` Its 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: ```bash kubectl annotate inferenceservice llama-3-2-1b-instruct -n llama-demo ome.io/ingress-disable-creation=true ``` ```output inferenceservice.ome.io/llama-3-2-1b-instruct annotated ``` While 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: === "Kubernetes Ingress" ```bash kubectl delete ingress llama-3-2-1b-instruct -n llama-demo ``` ```output ingress.networking.k8s.io "llama-3-2-1b-instruct" deleted from llama-demo namespace ``` === "Gateway API" ```bash kubectl 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-demo ``` ```output httproute.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 namespace ``` OME creates the Service named after the InferenceService instead, and sets `IngressReady` to `True` with the reason `IngressDisabled`. Check the Service: ```bash kubectl get service llama-3-2-1b-instruct -n llama-demo \ -o 'custom-columns=NAME:.metadata.name,TYPE:.spec.type,PORT:.spec.ports[*].port' ``` ```output NAME TYPE PORT llama-3-2-1b-instruct ClusterIP 8080 ``` The 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](expose-without-ingress.md). ## Troubleshooting Start with the InferenceService's `IngressReady` condition: ```bash 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: ```output READY REASON MESSAGE True IngressDisabled Ingress creation is disabled, using external service for access ``` `True` with no reason means that OME created the routes, and with Gateway API, that the Gateway accepted them. [When IngressReady is True](../../concepts/rollouts-and-traffic/ingress.md#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 the `ome.io/ingress-disable-creation` annotation 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](../../concepts/rollouts-and-traffic/ingress.md#routes) describes. See [Troubleshoot an InferenceService](../troubleshoot/troubleshoot-an-inferenceservice.md). - 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: ```bash kubectl logs -n ome -l control-plane=ome-controller-manager --tail=-1 ``` Look 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 set `enableGatewayAPI: 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 become `Ready`. Install Envoy Gateway, then restart the controller. - `invalid domain name`: see [The hostname is missing or wrong](#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.url` stays empty until OME creates the Ingress, once the component is ready. - A hostname under `svc.cluster.local` means that `domain` still has the chart's default. One under `example.com` that you didn't set means that `domain` is empty. Set `domain` as in Step 1. - With Gateway API, a shared host without `llm.` means that `sharedHostPrefix` is empty, as in kustomize installs, or that the InferenceService's `ome.io/ingress-shared-host-prefix` annotation is empty. - If an InferenceService has a hostname of its own where you expected the shared host, or the other way around, check `perISVCSubdomain` and its annotation. See [Choose a Gateway API host scheme](gateway-host-schemes.md). - `invalid domain name` in the controller's logs means that `domainTemplate`, or the `ome.io/ingress-domain` or `ome.io/ingress-domain-template` annotation, renders an invalid hostname. Until you fix it, OME doesn't create or update the routes, or the serving workload of `RawDeployment` and `MultiNode` (deprecated) components. ### Requests don't reach the service - Check that the hostname resolves to your Ingress controller or Gateway, or use `curl --resolve` as in Step 3. - With Kubernetes Ingress, `IngressReady` turns `True` as soon as OME creates the Ingress, whether or not your Ingress controller serves it. Check that `ingressGateway.className` names an IngressClass that `kubectl get ingressclass` lists, then read `kubectl describe ingress llama-3-2-1b-instruct -n llama-demo` and 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`. `NotAllowedByListeners` means that no listener of the Gateway allows routes from the InferenceService's namespace. Set the listener's `allowedRoutes.namespaces.from` to `All`, or to `Selector` with a selector that matches the namespace. `NoMatchingListenerHostname` means that no listener's hostname matches the route's. - With Gateway API, if `IngressReady` stays `False` with the reason `ParentStatusNotAvailable`, no Gateway has reported on the route. Check that `omeIngressGateway` names an existing Gateway as `/`. - With the shared host, requests must start with the path prefix, `///`. A request to `/v1/chat/completions` on 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's `timeoutSeconds`, as [Configure route timeouts](configure-route-timeouts.md) 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`](../../concepts/rollouts-and-traffic/traffic-policy.md#session-affinity-with-consistenthash), whose policy covers every route. `IngressReady` can also be `True` while Envoy Gateway hasn't accepted the BackendTrafficPolicy. Read the policy's status: ```bash kubectl describe backendtrafficpolicy llama-3-2-1b-instruct -n llama-demo ``` The `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: ```bash kubectl delete inferenceservice llama-3-2-1b-instruct -n llama-demo ``` ```output inferenceservice.ome.io "llama-3-2-1b-instruct" deleted from llama-demo namespace ``` To stop OME creating routes, turn ingress creation off as in [Turn off ingress creation](#turn-off-ingress-creation). ## Next steps - [Choose a Gateway API host scheme](gateway-host-schemes.md): a shared host with path prefixes, or a hostname per InferenceService. - [Use multiple gateways](multiple-gateways.md): attach the routes to more than one Gateway, such as an internal and an external one. - [Configure route timeouts](configure-route-timeouts.md): keep Envoy's 15-second default from cutting off long requests.