--- title: Use per-namespace gateways description: "Route each namespace's InferenceServices through that namespace's own Gateway, and override the gateway for a single service with annotations." since: v1.3 --- Give the [InferenceServices](../../concepts/serving/inference-services.md) in one namespace their own Gateway and domain, so that their listeners, TLS certificate and DNS stay separate from the rest of the cluster. You map the namespace `prod` to the Gateway `prod-gw` in the Helm values, check its routes, and then override the Gateways of one InferenceService with an annotation. By default, every InferenceService's [HTTPRoutes](../../concepts/rollouts-and-traffic/ingress.md) attach to the cluster's Gateway.
- OME v1.3 or later, installed with the `ome-resources` chart in the namespace `ome`, and `kubectl` access to the cluster. See [Install OME](../../getting-started/install.md). - Ingress creation turned on with the Gateway API. It's off by default. See [Configure ingress](configure-ingress.md). - An InferenceService in `prod` whose engine is ready. The examples use `llama-chat`, which has no router. - Envoy Gateway, with two Gateways in the namespace `envoy-gateway-system`: `shared-gw`, the cluster's Gateway, for the domain `example.com`, and `prod-gw` for `prod.example.com`. - A DNS record that sends `llm.prod.example.com` to `prod-gw`. `kubectl get gateway prod-gw -n envoy-gateway-system` shows the Gateway's address. - A listener on `prod-gw` that allows routes from `prod`. By default, a listener allows routes only from its own Gateway's namespace. This listener does, for any host in `prod.example.com`: ```yaml listeners: - name: http protocol: HTTP port: 80 hostname: "*.prod.example.com" allowedRoutes: namespaces: from: Selector selector: matchLabels: kubernetes.io/metadata.name: prod ```
## Step 1: Map `prod` to its Gateway {#step-1-map-namespaces-to-gateways} Add an entry for `prod` to your `ome-resources` values file. With Gateway API routing from [Configure ingress](configure-ingress.md), the ingress values look like this: ```yaml title="values.yaml" ome: controller: ingressGateway: disableIngressCreation: false enableGatewayAPI: true domain: example.com omeIngressGateway: envoy-gateway-system/shared-gw namespaceIngressGateways: prod: primary: omeIngressGateway: envoy-gateway-system/prod-gw ingressDomain: prod.example.com ``` `namespaceIngressGateways` maps a namespace to an entry for its InferenceServices. `primary` and each Gateway in `additional` take the last three fields: | Key | What it sets | | --- | --- | | The key, here `prod` | The InferenceServices' namespace, not the Gateway's. | | `primary` | Replaces the cluster-wide `omeIngressGateway`, `domain` and `omeIngressGatewayClass` together, when it sets `omeIngressGateway`. A field you leave out stays empty, even if the cluster-wide value is set. | | `additional` | Optional. More Gateways, after the primary, in place of the cluster-wide `additionalIngressGateways`. Set it to `[]` for none, or leave it out to keep the cluster-wide list. | | `omeIngressGateway` | The Gateway, as `namespace/name`. | | `ingressDomain` | The domain of the host on this Gateway. Always set it. | | `class` | Optional. Names this Gateway's address in `status.addresses`, such as `internal`. It must be a lowercase RFC 1123 label. | Give the namespace a domain of its own, so that DNS can send its host to `prod-gw`. With the default shared host, the InferenceServices in `prod` share the host `llm.prod.example.com`, and those in other namespaces stay on `shared-gw` at `llm.example.com`. With [per-service subdomains](gateway-host-schemes.md#per-service-subdomains) and the default `domainTemplate`, the host already names the namespace, so the entry can reuse the cluster-wide domain: `ingressDomain: example.com` gives `llama-chat.prod.example.com`. For HTTPS, add a certificate for `*.prod.example.com` to an HTTPS listener on `prod-gw`. The URLs in status take their scheme from the cluster-wide `urlScheme`, or from an InferenceService's `ome.io/ingress-url-scheme` annotation. !!! warning "Existing routes move" The upgrade moves the HTTPRoutes of every InferenceService in `prod` from `shared-gw` to `prod-gw` in place, and changes their host from `llm.example.com` to `llm.prod.example.com`. Tell clients about the new URLs before you upgrade. Upgrade at the installed chart version, so that OME doesn't move ahead of the `ome-crd` chart. The `CHART` column of `helm list -n ome` shows it, here `ome-resources-1.3.0`: ```bash helm upgrade --install ome oci://ghcr.io/moirai-internal/charts/ome-resources \ --namespace ome --version 1.3.0 -f values.yaml ``` Helm prints `Release "ome" has been upgraded. Happy Helming!` and the new revision. The upgrade restarts the controller, which then applies the entry to the InferenceServices in `prod`. Check the entry that the controller reads: ```bash kubectl get configmap inferenceservice-config -n ome \ -o jsonpath='{.data.ingress}' | jq .namespaceIngressGateways ``` ```output { "prod": { "primary": { "ingressDomain": "prod.example.com", "omeIngressGateway": "envoy-gateway-system/prod-gw" } } } ``` ## Step 2: Check the routes List the HTTPRoutes in `prod` with their Gateways and hosts: ```bash kubectl get httproute -n prod \ -o 'custom-columns=NAME:.metadata.name,GATEWAY:.spec.parentRefs[*].name,HOSTNAME:.spec.hostnames[*]' ``` ```output NAME GATEWAY HOSTNAME llama-chat prod-gw llm.prod.example.com llama-chat-engine prod-gw llm.prod.example.com ``` Both routes now name `prod-gw`, at the new host. An InferenceService with a router or a decoder has a route for that component too. Check that the InferenceService's URL uses the new host, and that `IngressReady` is `True`. If it stays `False`, see [IngressReady is False](#ingressready-is-false). ```bash kubectl get inferenceservice llama-chat -n prod \ -o 'custom-columns=URL:.status.url,INGRESS:.status.conditions[?(@.type=="IngressReady")].status' ``` ```output URL INGRESS http://llm.prod.example.com/prod/llama-chat/ True ``` To send a request, use the Gateway API request in [Configure ingress](configure-ingress.md#step-3-send-a-request), with the host `llm.prod.example.com` and the path `/prod/llama-chat/`. ## Override the Gateways for one InferenceService {#per-service-annotations} Two annotations on an InferenceService override the entry for its namespace. OME picks the primary Gateway and the additional Gateways separately, each from the first row that sets it: | Source | Primary Gateway | Additional Gateways | | --- | --- | --- | | The InferenceService's annotations | `ome.io/ingress-gateway` | `ome.io/ingress-additional-gateways` | | The entry for its namespace | `primary`, when it sets `omeIngressGateway` | `additional`, when set | | The cluster-wide values | `omeIngressGateway` | `additionalIngressGateways` | So an InferenceService in `prod` with only `ome.io/ingress-additional-gateways` keeps `prod-gw` as its primary. An empty annotation still skips its part of the entry, and so does an `ome.io/ingress-additional-gateways` value that isn't a JSON list. The cluster-wide value applies instead. `status.url` is the address on the primary Gateway, and `status.addresses` lists one for each Gateway, named by its `class`. `ome.io/ingress-gateway` replaces only the Gateway. The host on it uses the cluster-wide `domain`, or the InferenceService's `ome.io/ingress-domain`, and its address takes the cluster-wide `omeIngressGatewayClass`, not the entry's `class`. [Override the gateways for one service](multiple-gateways.md#override-the-gateways-for-one-service) describes the annotations' values. Say a Gateway `partner-gw` serves `partner.example.com`, and its listener allows routes from `prod`. To serve `llama-chat` through it too, at `llm.partner.example.com`: ```bash kubectl annotate inferenceservice llama-chat -n prod \ ome.io/ingress-additional-gateways='[{"omeIngressGateway":"envoy-gateway-system/partner-gw","ingressDomain":"partner.example.com"}]' ``` ```output inferenceservice.ome.io/llama-chat annotated ``` Check that the route now attaches to both Gateways, the primary first: ```bash kubectl get httproute llama-chat -n prod \ -o 'custom-columns=GATEWAY:.spec.parentRefs[*].name,HOSTNAME:.spec.hostnames[*]' ``` ```output GATEWAY HOSTNAME prod-gw,partner-gw llm.prod.example.com,llm.partner.example.com ``` `IngressReady` waits for all of a route's Gateways, and turns `True` once both `prod-gw` and `partner-gw` accept the routes. If `partner-gw` is missing or no controller manages it, `IngressReady` stays `False` with the reason `ParentStatusNotAvailable`, and so does the InferenceService's `Ready` condition. To check each Gateway, see [A route is missing from a gateway](multiple-gateways.md#a-route-is-missing-from-a-gateway). ## Troubleshooting ### Routes still attach to the cluster's Gateway When the routes in `prod` still name `shared-gw`, check for these causes: - The entry's key isn't `prod`, the InferenceServices' namespace. - The entry has no `primary.omeIngressGateway`. An entry with only `additional` keeps the cluster's primary Gateway. - The InferenceService has an `ome.io/ingress-gateway` annotation, even an empty one. `kubectl get inferenceservice llama-chat -n prod -o jsonpath='{.metadata.annotations}'` prints its annotations. - You edited the ConfigMap instead of upgrading the release. Restart the controller, which then updates all the InferenceServices: `kubectl rollout restart deployment ome-controller-manager -n ome`. ### IngressReady is False Read the reason and message of the InferenceService's `IngressReady` condition: ```bash kubectl get inferenceservice llama-chat -n prod \ -o 'custom-columns=REASON:.status.conditions[?(@.type=="IngressReady")].reason,MESSAGE:.status.conditions[?(@.type=="IngressReady")].message' ``` For example, when a Gateway hasn't accepted the engine route yet: ```output REASON MESSAGE ParentStatusNotAvailable Engine HttpRouteNotReady ``` The message starts with the route that isn't ready: `Engine` for `llama-chat-engine`, `TopLevel` for `llama-chat`. When a Gateway turns a route down, the reason and message come from the Gateway. These are the reasons you're most likely to see: | Reason | Cause | Fix | | --- | --- | --- | | `ParentStatusNotAvailable` | A Gateway on the route, primary or additional, hasn't accepted it yet. If this lasts, the Gateway is missing or misspelled, or no controller manages it. | Compare the route's Gateways with those that `kubectl get gateway -A` lists. | | `NotAllowedByListeners` | No listener on the Gateway allows routes from `prod`. | Allow `prod` in a listener's `allowedRoutes`, as in [Before you begin](#before-you-begin). | | `NoMatchingListenerHostname` | No listener's hostname matches the route's host. | Set a listener hostname that matches the host, as in [Before you begin](#before-you-begin), or fix the entry's `ingressDomain`. | [When IngressReady is True](../../concepts/rollouts-and-traffic/ingress.md#when-ingressready-is-true) lists the other reasons. ### The controller doesn't start after the upgrade A `class` that isn't a lowercase RFC 1123 label, or a value of the wrong type, stops the controller from loading its ingress config. A new controller pod logs `Failed to initialize ingress configuration` and exits. For a bad class, the error starts with `invalid ingress config` and names the field, such as `namespaceIngressGateways["prod"].primary.class`. An `additional` written as a map gives `unable to parse ingress config json` instead. Controller pods that are still running hit the same error. They stop creating and updating routes, and serving workloads that run as Deployments or, in the deprecated `MultiNode` mode, LeaderWorkerSets. Fix the values, and run the upgrade in [Step 1](#step-1-map-namespaces-to-gateways) again. ## Clean up Remove the annotation from `llama-chat`: ```bash kubectl annotate inferenceservice llama-chat -n prod ome.io/ingress-additional-gateways- ``` ```output inferenceservice.ome.io/llama-chat annotated ``` To move `prod` back to the cluster's Gateway, delete its entry from `namespaceIngressGateways` in your values file, and run the upgrade in [Step 1](#step-1-map-namespaces-to-gateways) again. The routes in `prod` go back to `shared-gw`, at the host `llm.example.com`. ## Next steps - [Use multiple gateways](multiple-gateways.md) to attach the cluster's routes to more than one Gateway. - [Configure ingress](configure-ingress.md) for the other ingress settings.