Skip to content

Expose agents

Route traffic from outside the cluster to an agent’s facade through a Gateway API Gateway.

  • A Kubernetes cluster with the Gateway API CRDs and a Gateway controller.
  • An AgentRuntime with spec.externalAuth set. Without it the external listener has no validators and rejects every request with a 401. See Configure authentication.

Install the Gateway API CRDs:

Terminal window
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml

If you have no controller, Istio is one option:

Terminal window
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update
helm install istio-base istio/base -n istio-system --create-namespace
helm install istiod istio/istiod -n istio-system --wait

Set these Helm values. className must match your controller (istio, envoy, and so on):

gateway:
enabled: true
name: agents
className: istio

The chart creates a Gateway named <fullname>-agents in the release namespace. For a release named omnia in omnia-system that is omnia-agents. For all gateway and internalGateway values, see Helm values.

Choose one.

Set defaultExposure in the Helm values. The operator then creates a host-based HTTPRoute for each agent that opts in:

defaultExposure:
enabled: true
baseDomain: agents.example.com
gateway:
name: omnia-agents
namespace: omnia-system

Opt each facade in on its own entry, or use the Expose externally switch on the agent’s Connectivity tab in the dashboard, which sets it on the primary facade:

spec:
facades:
- type: websocket
expose:
enabled: true
- type: a2a
expose:
enabled: true

The operator creates one HTTPRoute per exposed facade, each to that facade’s own Service port. The hostname is <agent>.<namespace>.<baseDomain> for the primary facade and <agent>-<type>.<namespace>.<baseDomain> for a secondary one, so the agent above is reachable at wss://<agent>.<namespace>.<baseDomain>/ws and https://<agent>-a2a.<namespace>.<baseDomain>/a2a. Set expose.host on a facade to override its hostname; do not give two facades the same host, because every route matches the root path and the Gateway serves only the older one. The Gateway listener’s certificate must cover each hostname. See facades[].expose.

The backend is the agent’s Service, which has the agent’s name, on the facade port (8080 by default). Host-based routing needs no rewrite:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-agent
namespace: default
spec:
parentRefs:
- name: omnia-agents
namespace: omnia-system
hostnames:
- "my-agent.agents.example.com"
rules:
- backendRefs:
- name: my-agent
port: 8080

To share one hostname between agents, match a path prefix and strip it. The facade serves /ws, so without the rewrite it receives /my-agent/ws and rejects the connection:

rules:
- matches:
- path:
type: PathPrefix
value: /my-agent
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: my-agent
port: 8080

Create the TLS Secret in the Gateway’s namespace:

Terminal window
kubectl create secret tls agents-tls \
--cert=path/to/cert.pem \
--key=path/to/key.pem \
-n omnia-system

Enable the HTTPS listener:

gateway:
listeners:
https:
enabled: true
tlsSecretName: agents-tls

In a hand-written route, add sectionName: https to the parentRefs entry.

Print the endpoints the operator discovered from the route:

Terminal window
kubectl get agentruntime my-agent \
-o jsonpath='{.status.facade.endpoints}'

The same list appears on the agent’s Connectivity tab in the dashboard. Each entry has a url and a valid flag.

Get the Gateway address and point the hostname at it in DNS:

Terminal window
kubectl get gateway omnia-agents -n omnia-system \
-o jsonpath='{.status.addresses[0].value}'

Connect with a credential that spec.externalAuth accepts. <url> is the url from the endpoint list:

Terminal window
websocat -H "Authorization: Bearer $TOKEN" <url>

An endpoint with valid: false is advertised but will not connect. A path-prefix route without the ReplacePrefixMatch rewrite above gives the reason path prefix not stripped before facade. Add the rewrite or switch to host-based routing.

A 401 means the request reached the facade but no spec.externalAuth validator admitted it. Check that the agent sets externalAuth and that the client sends a matching credential.

No endpoints listed: the route’s backend name must equal the AgentRuntime name and its port must be a facade port, and the parent Gateway must resolve.

Check that the Gateway and route are accepted:

Terminal window
kubectl describe gateway omnia-agents -n omnia-system
kubectl describe httproute my-agent