Runtime Fabric (RTF) deploys Mule applications as Kubernetes pods inside worker namespaces. External traffic traditionally reached those pods through an Nginx Ingress Controller, but the Kubernetes Gateway API is now the CNCF standard and a better fit for RTF clusters: it handles cross-namespace routing natively, requires no vendor annotations, and separates cluster-operator concerns from application-developer concerns.
This guide assumes RTF is already installed and registered in Anypoint Platform. For RTF installation see Runtime Fabric Setup, and for MCP-based lifecycle management see Managing Runtime Fabric with the Anypoint MCP.
How RTF structures Kubernetes namespaces?
Understanding RTF’s namespace layout is essential before configuring routing.
graph TD
subgraph cluster [Kubernetes Cluster]
subgraph rtfNs [rtf namespace - RTF agent]
agent[RTF Agent pods]
end
subgraph env1 [env-abc123 - Mule worker namespace]
app1[order-api pod :8081]
app2[customer-api pod :8081]
end
subgraph gw [gateway namespace - Nginx Gateway Fabric]
ngf[NGF controller]
svc[LoadBalancer Service]
end
end
svc -->|HTTPRoute| app1
svc -->|HTTPRoute| app2
RTF creates one worker namespace per Anypoint environment. Each namespace carries these labels applied by the RTF agent:
| Label | Value |
|---|---|
rtf.mulesoft.com/agentNamespace | The RTF agent namespace (e.g. rtf) |
rtf.mulesoft.com/envId | Anypoint environment ID |
rtf.mulesoft.com/org | Anypoint organisation ID |
rtf.mulesoft.com/role | Always workers for application namespaces |
Mule applications listen on port 8081 by default for HTTP traffic. The Kubernetes Service created for each app exposes this port cluster-internally.
Why the Gateway API replaces Nginx Ingress on RTF?
| Nginx Ingress on RTF | Kubernetes Gateway API |
|---|---|
kubernetes.io/ingress.class: nginx annotation per resource | GatewayClass selected once at the Gateway level |
| No cross-namespace routing | Native cross-namespace via ReferenceGrant |
| One Ingress resource spans hosts and paths | Separate HTTPRoute per application, owned by the app team |
| Routing changes require cluster-level Ingress edits | App teams update only their own HTTPRoute |
nginx.ingress.kubernetes.io/* annotations for timeouts, CORS, rate limits | Typed fields on HTTPRoute filters or policy CRDs |
Prerequisites
- RTF installed and at least one Mule application deployed
kubectlaccess to the RTF cluster with cluster-admin rights- Helm 3
- Anypoint Platform access to retrieve environment IDs and application names (or the Anypoint MCP connected to Cursor)
Step 1: Install the Gateway API CRDs
Install the standard channel CRD bundle onto the RTF cluster:
1
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml
Verify:
1
kubectl get crd | grep gateway.networking.k8s.io
Expected output includes gatewayclasses, gateways, httproutes, and referencegrants.
Do not skip CRD installation even if Nginx Ingress Controller is already running. The Gateway API CRDs are a separate, independent API group and do not conflict with the existing Ingress API.
Step 2: Install Nginx Gateway Fabric
Nginx Gateway Fabric is the direct, Gateway API-native successor to Nginx Ingress Controller from the same vendor. It replaces the controller while keeping NGINX as the underlying proxy.
1
2
3
4
helm install ngf oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric \
--namespace nginx-gateway \
--create-namespace \
--set service.type=LoadBalancer
Wait for the controller to be ready:
1
kubectl -n nginx-gateway rollout status deployment/ngf-nginx-gateway-fabric
Note the external IP: this is the cluster entry point that will replace the Nginx Ingress Controller’s LoadBalancer:
1
kubectl -n nginx-gateway get service ngf-nginx-gateway-fabric
Update your DNS records to point your API hostnames at this new IP before cutting traffic over.
Nginx Ingress Controller and Nginx Gateway Fabric can run simultaneously on the same cluster during migration. They use separate API groups (
networking.k8s.io/v1vsgateway.networking.k8s.io/v1) and do not interfere with each other.
Step 3: Create a GatewayClass
1
2
3
4
5
6
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nginx
spec:
controllerName: gateway.nginx.org/nginx-gateway-controller
1
2
kubectl apply -f gatewayclass.yaml
kubectl get gatewayclass nginx
ACCEPTED must show True before proceeding.
Step 4: Create a Gateway targeting RTF worker namespaces
The Gateway is deployed in a dedicated gateway namespace. The key RTF-specific configuration is allowedRoutes.namespaces.from: Selector with a label selector that targets all RTF worker namespaces at once, regardless of environment:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: rtf-gateway
namespace: gateway
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
rtf.mulesoft.com/role: workers
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: rtf-tls-secret
kind: Secret
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
rtf.mulesoft.com/role: workers
1
2
3
kubectl create namespace gateway
kubectl apply -f rtf-gateway.yaml
kubectl -n gateway get gateway rtf-gateway
The PROGRAMMED column shows True once the NGINX proxy configuration is live.
Using
rtf.mulesoft.com/role: workersas the selector means this single Gateway automatically covers every Anypoint environment deployed on the cluster. New environments added later require no Gateway changes.
Step 5: Grant cross-namespace access with ReferenceGrant
Because the Gateway lives in the gateway namespace and the Mule application Services live in RTF worker namespaces, Kubernetes requires an explicit ReferenceGrant in each worker namespace.
Create one grant per Anypoint environment namespace:
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-gateway-to-mule-apps
namespace: <rtf-worker-namespace>
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: <rtf-worker-namespace>
to:
- group: ""
kind: Service
Replace <rtf-worker-namespace> with the actual namespace (for example the Anypoint environment ID such as env-abc123). Ask the MCP to list your applications to discover the correct namespace:
List all applications in the "Production" environment.
The response includes the Kubernetes namespace and service name for each deployed Mule application.
Step 6: Deploy a Mule application with the Anypoint MCP
If the application is not yet deployed, use the MCP to deploy it and retrieve its service details in one step:
Deploy my application from ./order-api to the "Production" environment
targeting the "prod-rtf" fabric. Use Mule runtime 4.9.4:2e-java17.
Once deployed, confirm the Kubernetes service name:
List applications in "Production" including the application "order-api".
The MCP returns the application name, environment ID (the worker namespace), and deployment status. The Kubernetes Service name matches the application name as registered in Anypoint.
Verify the service exists directly:
1
kubectl get svc -n <rtf-worker-namespace>
Step 7: Create an HTTPRoute per Mule application
Place each HTTPRoute in the same namespace as the Mule application’s service (the RTF worker namespace):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: order-api-route
namespace: <rtf-worker-namespace>
spec:
parentRefs:
- name: rtf-gateway
namespace: gateway
sectionName: https
hostnames:
- order-api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: order-api
port: 8081
1
2
kubectl apply -f order-api-route.yaml
kubectl -n <rtf-worker-namespace> describe httproute order-api-route
Both Accepted and ResolvedRefs conditions must be True. A False status includes a human-readable reason: the most common causes on RTF are a missing ReferenceGrant or an incorrect service port.
Routing multiple Mule applications on the same hostname
If your APIs share a base hostname and are distinguished by path prefix:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: multi-api-route
namespace: <rtf-worker-namespace>
spec:
parentRefs:
- name: rtf-gateway
namespace: gateway
sectionName: https
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /orders
backendRefs:
- name: order-api
port: 8081
- matches:
- path:
type: PathPrefix
value: /customers
backendRefs:
- name: customer-api
port: 8081
Step 8: TLS certificate for the Gateway
Create the TLS secret in the gateway namespace (where the Gateway listener reads it):
1
2
3
4
kubectl create secret tls rtf-tls-secret \
--cert=path/to/cert.pem \
--key=path/to/key.pem \
-n gateway
The Gateway already references rtf-tls-secret in the https listener defined in Step 4. Update DNS to point your hostnames at the Gateway’s LoadBalancer IP, then test:
1
curl https://order-api.example.com/api/health
Step 9: Verify the full routing chain
1
2
3
4
5
6
7
8
9
10
11
# All Gateway API resources across all namespaces
kubectl get gatewayclass,gateway,httproute,referencegrant -A
# Status of a specific route
kubectl -n <rtf-worker-namespace> describe httproute order-api-route
# Nginx Gateway Fabric controller logs
kubectl -n nginx-gateway logs -l app.kubernetes.io/name=nginx-gateway-fabric --tail=50
# Mule application logs (via RTF agent or kubectl)
kubectl -n <rtf-worker-namespace> logs -l app=order-api --tail=50
Expected status conditions on each HTTPRoute:
| Condition | Expected value |
|---|---|
Accepted | True |
ResolvedRefs | True |
If ResolvedRefs is False with reason RefNotPermitted, the ReferenceGrant is missing or misconfigured.
Migrating from Nginx Ingress on RTF
| Nginx Ingress on RTF | Gateway API equivalent |
|---|---|
kubernetes.io/ingress.class: nginx | GatewayClass named nginx, referenced in Gateway |
spec.rules[].host on Ingress | spec.hostnames[] on HTTPRoute |
spec.rules[].http.paths[] | spec.rules[].matches[].path on HTTPRoute |
spec.tls[].secretName | spec.listeners[].tls.certificateRefs[].name on Gateway |
| Ingress in app namespace with cluster-wide effect | HTTPRoute in app namespace, Gateway in gateway namespace |
nginx.ingress.kubernetes.io/proxy-read-timeout | HTTPRoute spec.rules[].timeouts.backendRequest |
| No cross-namespace service reference | ReferenceGrant in each RTF worker namespace |
For each existing Nginx Ingress resource in an RTF namespace:
- Create the equivalent
HTTPRoutein the same namespace. - Verify the
HTTPRoutestatus showsAccepted: True. - Test the route with
curl. - Delete the Ingress resource.
- Repeat per application, one at a time.
What to avoid?
Do not place the Gateway in an RTF worker namespace. RTF worker namespaces are managed by the RTF agent. Resources placed there outside of Anypoint Platform can be overwritten or deleted by the agent during application reconciliation. Keep the Gateway and GatewayClass in a separate, agent-unmanaged namespace.
Do not use allowedRoutes.namespaces.from: All in production. This allows any namespace in the cluster to attach routes to your Gateway, including non-RTF workloads. Scope it to RTF worker namespaces using the rtf.mulesoft.com/role: workers label selector.
Do not target port 8082 or 8083 for external traffic. Mule applications on RTF expose their HTTP listener on port 8081. Ports 8082 (HTTPS) and 8083 (management) are used internally by RTF and should not be exposed through the Gateway.
Do not skip ReferenceGrant before testing HTTPRoute status. Without the grant the controller silently marks the route as RefNotPermitted and drops all traffic. Always apply the ReferenceGrant before creating the HTTPRoute.
Do not delete the Nginx Ingress Controller before all HTTPRoutes are verified. Run both controllers in parallel during migration. Confirm each HTTPRoute is Accepted and traffic flows correctly before removing the old Ingress resources and decommissioning the Nginx Ingress Controller.
Do not hardcode the RTF worker namespace name in shared GitOps manifests. The worker namespace is derived from the Anypoint environment ID, which differs between environments. Use a Kustomize overlay or Helm value to parameterise <rtf-worker-namespace> per environment.