Deploying applications and testing traffic

Overview

This guide explains how to deploy applications and test both ingress and egress traffic through F5 BIG-IP Next for Kubernetes.

You’ll complete the following tasks:

Ingress traffic — Deploy an NGINX application inside the cluster and configure a Layer 4 (L4) TCP gateway so external clients can reach it. You create a GatewayClass, GatewaySettings, Gateway, and L4Route to route inbound traffic through the Traffic Management Microkernel (TMM) to your application pods.

Egress traffic — Configure an EgressGateway so pods inside the cluster can send traffic to an external server. You create a GatewayClass, Infra, GatewaySettings, and EgressGateway to route outbound traffic through TMM with Source Network Address Translation (SNAT).

Prerequisites

Before you begin, verify that the following components are installed and running:

  • F5 BIG-IP Next for Kubernetes controller

  • Traffic Management Microkernel (TMM) instances

  • F5 IngressController/IP Address Management (IPAM) controller

  • Gateway API Custom Resource Definitions (CRDs)

Ingress traffic

Ingress traffic originates from a client outside the Kubernetes cluster and is sent to a service running inside the cluster. In this example, an external client sends an HTTP request to an NGINX application in the app-ns namespace through a Layer 4 (L4) TCP gateway.

The traffic follows this path:

Ingress traffic flow

Return traffic from the application pod follows the same path in reverse through TMM, which forwards the response back to the external client.

The custom resources relate to each other as follows:

Resources

Example

  1. Create a nginx-app-deploy file with the content below:

    vi nginx-app-deploy.yaml
    

    Contents:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      labels:
        app: nginx-tcp
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx-tcp
      template:
        metadata:
          labels:
            app: nginx-tcp
        spec:
          containers:
          - name: nginx-tcp
            image: nginx:latest
            ports:
            - containerPort: 80
            imagePullPolicy: IfNotPresent
    --- 
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-app-svc
    spec:
      type: LoadBalancer
      ports:
      - port: 80
        targetPort: 80
      selector:
        app: nginx-tcp
    
  2. Deploy the NGINX application into a namespace that your BIG-IP Next for Kubernetes is watching (watchNamespace).

    kubectl apply -f nginx-app-deploy.yaml -n <your application namespace>
    

    Example:

    kubectl apply -f nginx-app-deploy.yaml -n app-ns
    
  3. Create a Gateway API application with custom resources to make the NGINX application accessible through a service proxy endpoint. The IP Address should be within the same CIDR range as your external network and must not already be in use.

    For example, if your external network’s CIDR range is 192.168.10.0/24, any IP address between 192.168.10.1 and 192.168.10.255 will work.

    Create a gateway-tcp-cr.yaml file with the contents below:

    vi gateway-tcp-cr.yaml
    

    Note

    • Update the product-ns to the namespace in which CNEInstance CR is installed.

    • User must define listeners.kinds.group for L4Route. Gateway CRs should be applied on app namespace.

    Contents:

    apiVersion: gateway.networking.k8s.io/v1
    kind: GatewayClass
    metadata:
      name: my-gateway-class
    spec:
      controllerName: "f5.com/f5-bnk-instance-f5-cne-controller"
      description: "F5 BIG-IP Kubernetes Gateway"
    ---
    apiVersion: gateway.k8s.f5.com/v1alpha1
    kind: Infra
    metadata:
      name: my-infra
      namespace: f5-bnk
    spec:
      ipams:
        - name: external-vlan-selfip-ipam
          ipPools:
            - rangeStart: "20.0.0.40"
              rangeEnd: "20.0.0.50"
    
        - name: vip-listener-ipam
          ipPools:
            - rangeStart: "10.162.15.1"
              rangeEnd: "10.162.15.100"
    
      networkAttachments:
        - name: external-vlan-nad
          networkAttachmentDefinitions:
            - name: client-net
    
      networks:
        - name: external-vlan
          type: vlan
          vlan:
            mtu: 1500
            networkAttachmentRef:
              name: external-vlan-nad
            ipamRefs:
              - name: external-vlan-selfip-ipam
    ---
    apiVersion: gateway.k8s.f5.com/v1alpha1
    kind: GatewaySettings
    metadata:
      name: gw-settings-app-1
      namespace: gateway-infra
    spec:
      ingressConfig:
        defaultListenerNetwork:
          ipamRefs:
            - name: vip-listener-ipam
    ---
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: f5-l4-gateway
      namespace: gateway-infra
    spec:
      infrastructure:
        parametersRef:
          group: gateway.k8s.f5.com
          kind: GatewaySettings
          name: gw-settings-app-1
      gatewayClassName: my-gateway-class
      listeners:
      - name: l4-nginx-tcp
        protocol: TCP
        port: 80
        allowedRoutes:
          namespaces:
          from: Same
          kinds:
          - kind: L4Route
            group: gateway.k8s.f5.com
    --- 
    apiVersion: gateway.k8s.f5.com/v1
    kind: L4Route
    metadata:
      name: l4-tcp-nginx-app
      namespace: app-ns
    spec:
      protocol: TCP
      parentRefs:
      - name: f5-l4-gateway
        namespace: gateway-infra
        port: 80
        sectionName: l4-nginx-tcp
      rules:
      - backendRefs:
        - name: nginx-app-svc
          namespace: app-ns
          port: 80
    
  4. Apply the gateway-tcp-cr.yaml file and wait for programming:

    kubectl apply -f gateway-tcp-cr.yaml
    kubectl wait gateway/f5-l4-gateway --namespace gateway-infra \
      --for=condition=Programmed --timeout=120s
    
  5. Once the Gateway CR has been deployed, you will be able to ping and curl the IP address from a Node outside of your cluster that falls within the same CIDR range, execute:

    curl <IP address>
    

    Response:

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    

Troubleshooting for Ingress

If the request times out, check the resources in dependency order:

kubectl describe gatewayclass gateway-class
kubectl describe infra my-infra --namespace f5-bnk
kubectl describe gatewaysettings gw-settings-app-1 —namespace gateway-infra
kubectl describe gateway  f5-l4-gateway --namespace gateway-infra

Review the following common causes:

  • gateway-settings-1 must be in the Gateway namespace.

  • external-vlan must match the network name in Infra.

  • Gateway f5-l4-gateway must have an available IP.

  • Make sure you can ping listener address from client.

Egress traffic

Egress traffic starts from a pod inside your Kubernetes cluster and goes to a server outside the cluster. In this example, a pod in the spk-app-1 namespace sends an HTTP request to an external NGINX server.

The traffic follows this path:

Egress traffic flow

Return traffic goes to the allocated Source Network Address Translation (SNAT) address. TMM reverses the translation and returns the response to the application pod.

The custom resources relate to each other as follows:

Resources

The EgressGateway selects eligible running pods in spk-app-1. Host-networked pods are excluded, and overlapping EgressGateway selectors are rejected.

Example

  1. Configure the external NGINX server

Use a host on the external network as the destination server. The host must have an IP address that is reachable through external-vlan.

  • On the external host, identify the interface and address:

    ip address
    
  • Start NGINX on the external host. For example, with Docker:

    docker run --name egress-nginx --detach --publish 80:80 nginx:latest
    
  • Allow TCP port 80 through any host or network firewall.

  1. Deploy the NGINX test application

  • Create the namespace that the EgressGateway selects:

    kubectl create namespace spk-app-1
    
  • Create a file named nginx-egress-test.yaml with the following contents:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-egress-test
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nginx-egress-test
      template:
        metadata:
          labels:
            app: nginx-egress-test
        spec:
          containers:
            - name: nginx
              image: nginx:latest
              imagePullPolicy: IfNotPresent
              ports:
                - name: http
                  containerPort: 80
            - name: curl
              image: curlimages/curl:latest
              imagePullPolicy: IfNotPresent
              command:
                - sleep
                - infinity
    

    The curl sidecar shares the NGINX pod’s network namespace and Pod IP. This means its requests match the EgressGateway selector.

  • Deploy the application:

    kubectl apply -f nginx-egress-test.yaml --namespace spk-app-1
    
  • Verify the pod is running and note its Pod IP and node:

    kubectl get pods --namespace spk-app-1 --output wide
    

    Output

    NAME                                  READY   STATUS    IP            NODE
    nginx-egress-test-6b9c8f6dc7-v8m2k   2/2     Running   10.244.1.17   worker-1
    

    You don’t need a Kubernetes Service for this test. The test exercises outbound traffic from the pod, not inbound traffic to NGINX.

  1. Configure the GatewayClass

  • Create a file named gw-class.yaml with the following contents:

    apiVersion: gateway.networking.k8s.io/v1
    kind: GatewayClass
    metadata:
      name: gateway-class
    spec:
      controllerName: f5.com/default-f5-cne-controller
      description: "F5 BIG-IP Kubernetes Gateway"
    
  • Apply the file and wait for acceptance:

    kubectl apply -f gw-class.yaml
    kubectl wait gatewayclass/gateway-class --for=condition=Accepted --timeout=120s
    
  1. Configure Infra

  • Create a file named infra.yaml with the following contents:

    apiVersion: gateway.k8s.f5.com/v1alpha1
    kind: Infra
    metadata:
      name: my-infra
      namespace: default
    spec:
      ipams:
        - name: external-vlan-selfip-ipam
          ipPools:
            - rangeStart: "20.0.0.40"
              rangeEnd: "20.0.0.50"
    
        - name: external-egress-snat-ipam-1
          ipPools:
            - rangeStart: "10.26.11.0"
              rangeEnd: "10.26.11.20"
        - name: external-egress-snat-ipam-2
          ipPools:
            - rangeStart: "10.26.11.21"
              rangeEnd: "10.26.11.30"
    
        - name: external-listener-ipam
          ipPools:
            - rangeStart: "10.162.15.1"
              rangeEnd: "10.162.15.100"
    
      networkAttachments:
        - name: external-vlan-nad
          networkAttachmentDefinitions:
            - name: client-net
    
      networks:
        - name: external-vlan
          type: vlan
          vlan:
            mtu: 1500
            networkAttachmentRef:
              name: external-vlan-nad
            ipamRefs:
              - name: external-vlan-selfip-ipam
    
  • Apply the file and wait for programming:

    kubectl apply -f infra.yaml
    kubectl wait infra/my-infra --namespace default \
      --for=condition=Programmed --timeout=120s
    
  1. Configure GatewaySettings

  • Create a file named gateway-settings.yaml with the following contents:

    apiVersion: gateway.k8s.f5.com/v1alpha1
    kind: GatewaySettings
    metadata:
      name: gateway-settings-1
      namespace: default
    spec:
      sourceNATPools:
        - name: external-egress-snat-pool-1
          ipamRefs:
            - name: external-egress-snat-ipam-1
        - name: external-egress-snat-pool-2
          ipamRefs:
            - name: external-egress-snat-ipam-2
      ingressConfig:
        defaultListenerNetwork:
          networkRefs:
            - name: external-vlan
          ipamRefs:
            - name: external-listener-ipam
          sourceNATConfig:
            type: Pool
            sourceNATPoolRef:
              name: external-egress-snat-pool-1
      egressConfigs:
        - name: egress-config-1
          networkRef:
            name: external-vlan
          sourceNATConfig:
            type: Pool
            sourceNATPoolRef:
              name: external-egress-snat-pool-1
        - name: egress-config-2
          networkRef:
            name: external-vlan
          sourceNATConfig:
            type: Pool
            sourceNATPoolRef:
              name: external-egress-snat-pool-2
    
  • Apply the file and wait for programming:

    kubectl apply -f gateway-settings.yaml
    kubectl wait gatewaysettings/gateway-settings-1 --namespace default \
      --for=condition=Programmed --timeout=120s
    
  1. Configure EgressGateway

  • Create a file named egress-gateway.yaml with the following contents:

    apiVersion: gateway.k8s.f5.com/v1alpha1
    kind: EgressGateway
    metadata:
      name: app-egress-gateway
      namespace: default
    spec:
      gatewayClassName: gateway-class
      infrastructure:
        parametersRef:
          name: gateway-settings-1
          sectionName: egress-config-1
      sourceSelector:
        selectionMode: NamespaceSelector
        namespaces:
          matchNames:
            - spk-app-1
    
  • Apply the file and wait for programming:

    kubectl apply -f egress-gateway.yaml
    kubectl wait egressgateway/app-egress-gateway --namespace default \
      --for=condition=Programmed --timeout=120s
    

    Note

    If the selected namespace has no running pod with a Pod IP, the Programmed condition stays pending. After the NGINX test pod becomes ready, its pod event causes the EgressGateway to reconcile.

Test egress traffic

  1. In one terminal, monitor the NGINX access log on the external server:

    docker logs --follow egress-nginx
    
  2. In a second terminal, send an HTTP request from the curl sidecar:

    kubectl exec deployment/nginx-egress-test \
      --namespace spk-app-1 \
      --container curl \
      -- curl --verbose --connect-timeout 10 http://<external-nginx-ip>/
    
  3. If the egress configuration is working, the response contains the NGINX welcome page:

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working.</p>
    </body>
    </html>
    
  4. Check the external NGINX access log. It should show an address from external-egress-snat-ipam-1 as the client address, not the original Kubernetes Pod IP.

    Example access log:

    10.26.11.5 - - [21/Aug/2026:18:42:17 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.x"
    

    The exact source address depends on the address that external-egress-snat-pool-1 allocated.

  5. For packet-level verification, capture traffic on the external server:

    sudo tcpdump --numeric --interface <external-interface> tcp port 80
    

    The incoming request should look like this:

    <allocated-snat-ip>.<source-port> > <external-nginx-ip>.80
    

    This confirms that traffic forwarded through TMM and that source address translation is working.

Troubleshooting for egress

If the request times out, check the resources in dependency order:

kubectl describe gatewayclass gateway-class
kubectl describe infra my-infra --namespace default
kubectl describe gatewaysettings gateway-settings-1 --namespace default
kubectl describe egressgateway app-egress-gateway --namespace default
kubectl get pods --namespace spk-app-1 --output wide

Review the following common causes:

  • The EgressGateway selector must match spk-app-1 exactly.

  • The selected pod must be running, have a Pod IP, and must not use hostNetwork.

  • gateway-settings-1 must be in the EgressGateway namespace. The sectionName must identify an accepted egress configuration.

  • external-vlan must match the network name in Infra.

  • external-egress-snat-ipam-1 must have an available, externally routable IP.

  • The client-net network attachment must be available on the TMM nodes.

  • The application node must have a working pod-to-TMM path. If you use PseudoCNI, verify that CSRC is running and has programmed the node routing state.

  • The external server and any intermediate firewall must allow TCP port 80.

  • The external network must route the SNAT address back to TMM.

To identify whether the issue is with the application or the egress path, first test the external NGINX server from another host on the external network:

curl --verbose http://<external-nginx-ip>/

If that works but the request from spk-app-1 fails, check the controller, CSRC, and TMM logs while you reapply the EgressGateway:

kubectl apply -f egress-gateway.yaml

Clean up

Delete resources in reverse dependency order:

kubectl delete -f egress-gateway.yaml
kubectl delete -f gateway-settings.yaml
kubectl delete -f infra.yaml
kubectl delete -f gw-class.yaml
kubectl delete -f nginx-egress-test.yaml --namespace spk-app-1
kubectl delete namespace spk-app-1

When you delete the EgressGateway, the system removes its TMM wildcard egress configuration and its pod-to-TMM routing state. When you delete GatewaySettings, the system releases the dependent SNAT configuration. When you delete Infra, the system removes the external network resources.