This lab assumes that you have completed the setup in 001 and 002
- Route to a remote A2A agent by hostname (not IP address) through AgentGateway
- Preserve full A2A protocol detection, including
"protocol":"a2a"anda2a.methodin access logs - Handle port and path differences on the remote agent
- Validate A2A connectivity and observability through the gateway
In the previous lab (032-remote-a2a), we routed to a remote A2A agent using a headless Service with an EndpointSlice pointing to an external IP address. That approach works when the remote agent has a known, static IP.
But what if the remote A2A agent is reachable by hostname — for example, agent.example.com:9090? You can't put a hostname in an EndpointSlice (which requires IPv4/IPv6 addresses), and the IP behind a hostname can change at any time — nobody would keep the EndpointSlice updated.
The trick is to route AgentGateway through itself. We create two routes:
- Client-facing route (
/a2a) — routes to a Kubernetes Service withappProtocol: kgateway.dev/a2athat points back to the AgentGateway proxy pods. This triggers A2A protocol detection. - Upstream route (
/a2a-upstream) — routes to anAgentgatewayBackendwithstaticthat resolves the external hostname via DNS.
Client → AgentGateway (/a2a) → AgentGateway (/a2a-upstream) → Remote A2A Agent (hostname)
↑ ↑
Service with AgentgatewayBackend
appProtocol: kgateway.dev/a2a static: hostname:port
(A2A protocol detected) (DNS resolution)
This requires no extra deployments — AgentGateway handles everything.
| Remote agent address | Approach | A2A Protocol Detection |
|---|---|---|
IP address (e.g., 10.0.1.50:9090) |
Headless Service + EndpointSlice (lab 032) | Yes |
Hostname (e.g., agent.example.com:9090) |
Self-referencing route (this lab) | Yes |
Deploy the same echo agent from the previous lab, exposed via a LoadBalancer:
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: a2a-agent
namespace: default
labels:
app: a2a-agent
spec:
selector:
matchLabels:
app: a2a-agent
template:
metadata:
labels:
app: a2a-agent
spec:
containers:
- name: a2a-agent
image: gcr.io/solo-public/docs/test-a2a-agent:latest
ports:
- containerPort: 9090
---
apiVersion: v1
kind: Service
metadata:
name: a2a-agent-lb
namespace: default
spec:
selector:
app: a2a-agent
type: LoadBalancer
ports:
- protocol: TCP
port: 9090
targetPort: 9090
EOFWait for the pod and LoadBalancer:
kubectl wait --for=condition=ready pod -l app=a2a-agent -n default --timeout=60sGet the external IP and construct a hostname using nip.io (a wildcard DNS service that maps <IP>.nip.io back to the IP):
export A2A_AGENT_IP=$(kubectl get svc a2a-agent-lb -n default -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export A2A_AGENT_HOST="${A2A_AGENT_IP}.nip.io"
echo "A2A Agent Hostname: $A2A_AGENT_HOST"Note: In a real scenario,
A2A_AGENT_HOSTwould be the actual hostname of your remote A2A agent (e.g.,agent.example.com). We use nip.io here to simulate a hostname-based endpoint without needing custom DNS.
curl -s http://$A2A_AGENT_HOST:9090/.well-known/agent.json | jqYou should see the Echo Agent card:
{
"name": "Echo Agent",
"description": "This agent echos the input given",
"url": "http://0.0.0.0:9090/",
"version": "0.1.0",
"capabilities": {
"streaming": true,
"pushNotifications": false,
"stateTransitionHistory": false
},
"skills": [
{
"id": "my-project-echo-skill",
"name": "Echo Tool",
"description": "Echos the input given"
}
]
}Create an AgentgatewayBackend with the static type pointing to the remote agent's hostname and port. This handles DNS resolution:
kubectl apply -f - <<EOF
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayBackend
metadata:
name: a2a-upstream
namespace: agentgateway-system
spec:
static:
host: ${A2A_AGENT_HOST}
port: 9090
EOFVerify the backend is accepted:
kubectl get agentgatewaybackend -n agentgateway-system a2a-upstreamCreate a Kubernetes Service that selects the AgentGateway proxy pods and sets appProtocol: kgateway.dev/a2a. This is what triggers A2A protocol detection when traffic flows through it:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: remote-a2a-agent
namespace: agentgateway-system
spec:
selector:
app.kubernetes.io/name: agentgateway-proxy
ports:
- protocol: TCP
port: 8080
targetPort: 8080
appProtocol: kgateway.dev/a2a
EOFKey point: This Service points to the same AgentGateway proxy pods that are already running — no extra deployments needed.
Create two HTTPRoutes:
- Upstream route — forwards
/a2a-upstreamto the static backend (external hostname) - Client-facing route — forwards
/a2athrough the self-referencing Service (for A2A detection), rewriting the path to/a2a-upstream
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: a2a-upstream
namespace: agentgateway-system
spec:
parentRefs:
- name: agentgateway-proxy
rules:
- matches:
- path:
type: PathPrefix
value: /a2a-upstream
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- group: agentgateway.dev
kind: AgentgatewayBackend
name: a2a-upstream
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: remote-a2a-hostname
namespace: agentgateway-system
spec:
parentRefs:
- name: agentgateway-proxy
rules:
- matches:
- path:
type: PathPrefix
value: /a2a
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /a2a-upstream
backendRefs:
- name: remote-a2a-agent
port: 8080
EOFHow it works:
- A request to
/a2ahits theremote-a2a-hostnameroute, which rewrites the path to/a2a-upstreamand sends it through the self-referencing Service (withappProtocol: kgateway.dev/a2a— triggering A2A detection) - AgentGateway receives the request again on
/a2a-upstream, which matches thea2a-upstreamroute, rewrites the path to/, and forwards to the static backend (external hostname)
If the remote A2A agent serves at a sub-path (e.g., agent.example.com:8080/myagent), adjust the replacePrefixMatch in the upstream route:
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /myagentexport GATEWAY_IP=$(kubectl get svc -n agentgateway-system --selector=gateway.networking.k8s.io/gateway-name=agentgateway-proxy -o jsonpath='{.items[*].status.loadBalancer.ingress[0].ip}{.items[*].status.loadBalancer.ingress[0].hostname}')
echo $GATEWAY_IPcurl -s http://$GATEWAY_IP:8080/a2a/.well-known/agent.json | jqYou should see the Echo Agent card returned through the gateway.
curl -s -X POST http://$GATEWAY_IP:8080/a2a \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/send",
"params": {
"id": "hostname-test",
"message": {
"role": "user",
"parts": [{"type": "text", "text": "hello via hostname"}]
}
}
}' | jqExpected response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"id": "hostname-test",
"status": {
"state": "completed",
"message": {
"role": "agent",
"parts": [{"type": "text", "text": "on_send_task received: hello via hostname"}]
}
}
}
}kubectl logs deploy/agentgateway-proxy -n agentgateway-system --all-containers --tail 5 | grep a2aYou should see two log entries per request — one for each hop through AgentGateway:
First hop (client-facing route) — with full A2A protocol detection:
{
"route": "agentgateway-system/remote-a2a-hostname",
"http.path": "/a2a",
"http.status": 200,
"protocol": "a2a",
"a2a.method": "tasks/send"
}Second hop (upstream route) — forwards to the external hostname:
{
"route": "agentgateway-system/a2a-upstream",
"endpoint": "agent.example.com:9090",
"http.path": "/a2a-upstream",
"http.status": 200,
"protocol": "http"
}The client-facing route has full A2A observability — "protocol":"a2a", a2a.method, and request/response body logging.
kubectl delete httproute -n agentgateway-system remote-a2a-hostname a2a-upstream
kubectl delete agentgatewaybackend -n agentgateway-system a2a-upstream
kubectl delete svc -n agentgateway-system remote-a2a-agent
kubectl delete svc -n default a2a-agent-lb
kubectl delete deployment -n default a2a-agent- To route to a remote A2A agent by hostname with full A2A protocol detection, use a self-referencing route — AgentGateway routes through itself via a Service with
appProtocol: kgateway.dev/a2a, then forwards to the external hostname via anAgentgatewayBackendwithstatic - No extra deployments are needed — AgentGateway handles both the A2A protocol detection and the hostname-based routing
- The
appProtocol: kgateway.dev/a2aannotation on the Kubernetes Service is what triggers A2A protocol detection — this enables"protocol":"a2a"in logs,a2a.methodmetadata, and agent card URL rewriting - The
AgentgatewayBackendwithstatichandles DNS resolution of the remote hostname, so the IP behind the hostname can change without any manual updates - This approach preserves all the same observability, security, and traffic management capabilities as routing to an IP-based remote A2A agent