--- title: Expose a service without ingress description: Put an InferenceService behind a LoadBalancer or NodePort Service with the ome.io/service-type annotation, without an Ingress controller or a Gateway. --- The `ome.io/service-type` annotation puts an [InferenceService](../../concepts/serving/inference-services.md) behind a `LoadBalancer` or `NodePort` Service, so clients outside the cluster reach the model without an Ingress controller or a Gateway. Use `LoadBalancer` on a cluster that provisions load balancers, and `NodePort` when clients can reach the nodes. To route traffic through an Ingress or a Gateway instead, see [Configure ingress](configure-ingress.md).
- OME installed as the release `ome` in the namespace `ome`. See [Install OME](../../getting-started/install.md). - 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, add the annotations from Step 2 to it, and change the names in the commands. - For a `LoadBalancer` Service, a cluster that provisions load balancers, and a machine that can reach them. Step 2's example is for Azure Kubernetes Service. - For a `NodePort` Service, a machine that can reach the nodes.
## How the external Service works While ingress creation is off, OME puts a `ClusterIP` Service named after the InferenceService in front of its router, or its engine when there's no router. This is the external Service. It listens on the port of the router's or the engine's Service, 8080 for `srt-llama-3-2-1b-instruct`. Whatever its type, `status.url` stays its address in the cluster, here `http://llama-3-2-1b-instruct.llama-demo.svc.cluster.local:8080`. For the details, see [The external Service](../../concepts/rollouts-and-traffic/ingress.md#the-external-service). !!! warning "Give your own Services other names" OME treats any Service with the InferenceService's name as the external Service, even one you created. It rewrites that Service, and deletes it when you turn ingress creation on. ## Step 1: Check that ingress creation is off Ingress creation is off by default. Check the cluster's setting: ```bash kubectl get configmap inferenceservice-config -n ome -o jsonpath='{.data.ingress}' | jq '.disableIngressCreation' ``` ```output true ``` If it prints `false`, ingress creation is on. To turn it off for this InferenceService, add `ome.io/ingress-disable-creation: "true"` to its annotations in Step 2. To turn it off for every InferenceService, see [Turn off ingress creation](configure-ingress.md#turn-off-ingress-creation). Either way, the Ingress or HTTPRoutes that OME created before keep serving traffic until you delete them, as that section shows. ## Step 2: Set the Service type and load balancer annotations Save this version of the InferenceService from [Serve models from a PVC](../deploy-models/serve-models-from-pvc.md#step-4-deploy-an-inferenceservice), with annotations for a load balancer inside the cluster's network: ```yaml title="isvc.yaml" apiVersion: ome.io/v1beta1 kind: InferenceService metadata: name: llama-3-2-1b-instruct namespace: llama-demo annotations: ome.io/service-type: LoadBalancer service.beta.kubernetes.io/azure-load-balancer-internal: "true" spec: model: name: llama-3-2-1b-instruct kind: BaseModel runtime: name: srt-llama-3-2-1b-instruct engine: minReplicas: 1 maxReplicas: 1 annotations: ome.io/service-type: ClusterIP ``` - `ome.io/service-type: LoadBalancer` makes the external Service a `LoadBalancer` Service. For a `NodePort` Service, use `NodePort`. The value is case-sensitive. - `service.beta.kubernetes.io/azure-load-balancer-internal: "true"` asks Azure for a load balancer with an address in the cluster's virtual network. Replace it with your provider's annotations. OME copies only annotations that start with `service.beta.kubernetes.io/`, `cloud.google.com/` or `service.kubernetes.io/`. - `ome.io/service-type: ClusterIP` in the engine's `annotations` keeps the engine's Service, `llama-3-2-1b-instruct-engine`, inside the cluster. Without it, that Service takes the InferenceService's type when OME creates it, and becomes a second load balancer. Add the same line to `spec.router.annotations` and `spec.decoder.annotations` when the InferenceService has those components. !!! warning "A load balancer exposes the model to anyone who can reach it" Nothing authenticates the requests sent to the Service. Without an annotation that keeps the load balancer internal, your provider can give it a public address. Keep it internal, or put an authenticating gateway in front of the model. See [Security](../../concepts/rollouts-and-traffic/ingress.md#security). Apply the file: ```bash kubectl apply -f isvc.yaml ``` ```output inferenceservice.ome.io/llama-3-2-1b-instruct created ``` If the InferenceService already exists, kubectl prints `configured` instead. OME copies these annotations to the engine's pods too, so a running `RawDeployment` engine replaces its pods, which load the model again. ## Step 3: Check the Service and send a request Wait for the InferenceService to be ready: ```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 Services that OME created for it: ```bash kubectl get service -n llama-demo -l ome.io/inferenceservice=llama-3-2-1b-instruct \ -o 'custom-columns=NAME:.metadata.name,COMPONENT:.metadata.labels.component,TYPE:.spec.type,PORT:.spec.ports[*].port' ``` ```output NAME COMPONENT TYPE PORT llama-3-2-1b-instruct external-service LoadBalancer 8080 llama-3-2-1b-instruct-engine engine ClusterIP 8080 ``` The external Service has the type you set, and the engine's Service stays `ClusterIP`. An [OMENative](../../concepts/omenative/overview.md) engine also has a headless Service and two Services for each revision, which are always `ClusterIP`. Then send the model a request from outside the cluster: === "LoadBalancer" Read the load balancer's address. Depending on your provider, it's an IP address or a hostname: ```bash kubectl get service llama-3-2-1b-instruct -n llama-demo \ -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}{"\n"}' ``` ```output 10.224.0.100 ``` The InferenceService can be `Ready` before the load balancer exists, and until then the command prints an empty line. If it stays empty, look for your provider's events in `kubectl describe service llama-3-2-1b-instruct -n llama-demo`. From a machine that can reach the address, send a chat completion request to port 8080, with your address in place of `10.224.0.100`: ```bash curl -s http://10.224.0.100:8080/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 a load balancer?"}], "max_tokens": 64}' \ | jq -r '.choices[0].message.content' ``` `jq` prints the model's answer, which varies from run to run. === "NodePort" Read the port that Kubernetes opened on every node: ```bash kubectl get service llama-3-2-1b-instruct -n llama-demo -o jsonpath='{.spec.ports[0].nodePort}{"\n"}' ``` ```output 31627 ``` From a machine that can reach the nodes, send a chat completion request to that port on any node's address. `kubectl get nodes -o wide` lists the addresses. Replace `10.224.0.4` and `31627` with yours: ```bash curl -s http://10.224.0.4:31627/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 a node port?"}], "max_tokens": 64}' \ | jq -r '.choices[0].message.content' ``` `jq` prints the model's answer, which varies from run to run. ## Expose a multi-node engine Without a router, the external Service sends traffic to every pod of the engine, workers included, so it suits an engine whose replicas are one pod each. For a [multi-node engine](../omenative/serve-a-multi-node-model.md), give the InferenceService a router, or make the engine's Service the load balancer: - Set `ome.io/service-type: LoadBalancer` and your provider's annotations in `spec.engine.annotations`, and leave `ome.io/service-type` off the InferenceService's `metadata.annotations`. - Declare the engine's `leader` and `worker` in the InferenceService, so that the engine's Service sends traffic only to the leaders. If the runtime already sets them, add `leader: {}` and `worker: {}`, which keep the runtime's settings. - Set the annotations before OME creates the engine's Service. Since v1.3, OME sets a component Service's type and annotations only when it creates the Service. ## 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' ``` ```output READY REASON MESSAGE True IngressDisabled Ingress creation is disabled, using external service for access ``` If the reason isn't `IngressDisabled`, ingress creation is on for the InferenceService, and OME creates routes instead of the external Service: see [Step 1](#step-1-check-that-ingress-creation-is-off). For other problems, see [Troubleshoot an InferenceService](../troubleshoot/troubleshoot-an-inferenceservice.md). ### OME doesn't create the Service The InferenceService has the label `networking.knative.dev/visibility: cluster-local`, which stops OME from creating the external Service and makes OME delete an existing one. Remove the label to expose the InferenceService. For other errors, search the controller's logs: ```bash kubectl logs -n ome -l control-plane=ome-controller-manager --tail=-1 | grep 'external service' ``` ### The Service stays a ClusterIP Service - The value of `ome.io/service-type` isn't exactly `LoadBalancer` or `NodePort`. The webhook accepts any value, and OME treats the others, such as `loadbalancer`, as `ClusterIP`. - The annotation is in a component's `annotations`. The external Service reads only the InferenceService's `metadata.annotations`. ### An annotation or setting is missing from the Service OME copies only annotations with the prefixes that Step 2 lists, so GKE's `networking.gke.io/load-balancer-type` never reaches the external Service. OME also resets any change that you make to a `LoadBalancer` or `NodePort` external Service, such as setting `externalTrafficPolicy`. For these settings, create a Service of your own, with another name and the selector of the engine's Service, or of the router's when there's one. Read the selector: ```bash kubectl get service llama-3-2-1b-instruct-engine -n llama-demo -o jsonpath='{.spec.selector}' ``` ```output {"app":"llama-3-2-1b-instruct-engine"} ``` The output is for a `RawDeployment` engine. An OMENative engine's Service has a different selector. ### The engine's Service is a load balancer too The InferenceService had `ome.io/service-type: LoadBalancer` when OME created the engine's Service. Add `ome.io/service-type: ClusterIP` to the engine's `annotations` as in Step 2, apply the file, and delete the engine's Service. OME creates it again as a `ClusterIP` Service: ```bash kubectl delete service llama-3-2-1b-instruct-engine -n llama-demo ``` ```output service "llama-3-2-1b-instruct-engine" deleted from llama-demo namespace ``` ### Requests to a multi-node engine fail Without a router, the external Service sends requests to the worker pods too. See [Expose a multi-node engine](#expose-a-multi-node-engine). ## Clean up Delete the InferenceService. The Services that OME created for it go with it, and your provider removes the load balancer: ```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 keep the InferenceService without the load balancer, remove `ome.io/service-type` from `metadata.annotations` in `isvc.yaml`, and apply the file again. OME changes the external Service back to a `ClusterIP` Service. Leave the other annotations in place, since changing them restarts a `RawDeployment` engine. ## Next steps - [Configure ingress](configure-ingress.md): route traffic through a Kubernetes Ingress or Gateway API HTTPRoutes instead, at hostnames under your domain. - [Ingress and external access](../../concepts/rollouts-and-traffic/ingress.md): how the external Service, the routes and the component Services fit together. - [Traffic annotations](../../reference/api/traffic-annotations.md#service-type-annotation): the reference for `ome.io/service-type`, and the component Services it also reaches.