Rancher’s Ingress controller can handle both L4 (TCP/UDP) and L7 (HTTP/HTTPS) traffic, but people often get tripped up because the default behavior seems to favor L7, and L4 configuration requires a specific annotation.
Let’s see it in action. Imagine we have a simple web application deployed in Rancher and we want to expose it externally.
First, ensure you have an Ingress controller deployed. Rancher often deploys the ingress-nginx controller by default. You can check its status with:
kubectl get pods -n ingress-nginx
You should see pods like ingress-nginx-controller-xxxx-yyyy in a Running state.
Now, let’s create a basic Ingress resource for our web app. We’ll assume your web app is running in a deployment named my-webapp on port 80.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-webapp-ingress
namespace: default # Or the namespace where your webapp is deployed
spec:
rules:
- host: my-webapp.example.com # Replace with your domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-webapp-service # Your Kubernetes Service name
port:
number: 80 # The port your service listens on
If you apply this Ingress, and your ingress-nginx controller is configured with an external IP or LoadBalancer service, you should be able to access http://my-webapp.example.com and see your web application. This is L7 routing – the Ingress controller inspects the HTTP host header and routes traffic accordingly.
But what if you need to expose a TCP service, like a database or a custom TCP protocol application, that doesn’t speak HTTP? This is where L4 ingress comes in. The ingress-nginx controller, by default, is configured to only expose HTTP/HTTPS. To expose a TCP service, you need to explicitly tell it to do so via an annotation.
Let’s say you have a TCP service named my-tcp-service on port 5432 (e.g., PostgreSQL). You’ll need to create a separate Ingress resource, or modify an existing one, and add a specific annotation.
Here’s how you’d configure L4 for a TCP service:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-tcp-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "TCP" # This is the key annotation
spec:
rules:
- http: # Note: No host specified for pure TCP L4
paths:
- path: / # Path is ignored for pure TCP L4
pathType: Prefix
backend:
service:
name: my-tcp-service
port:
number: 5432
Crucially, for L4 TCP/UDP, you typically don’t specify a host in the spec.rules. The Ingress controller will map the external port (which is usually determined by the ingress-nginx controller’s LoadBalancer service) directly to your backend service’s port. The backend-protocol: "TCP" annotation tells the Ingress controller to not try to interpret traffic as HTTP, but to pass it through directly.
To configure L4 for UDP, you’d use nginx.ingress.kubernetes.io/backend-protocol: "UDP".
The mental model here is that the ingress-nginx controller is a powerful piece of software that can act as both an L7 HTTP/HTTPS proxy (like Nginx itself) and an L4 TCP/UDP forwarder. By default, it’s tuned for the common L7 use case. You have to explicitly opt-in to L4 forwarding for non-HTTP protocols.
The ingress-nginx controller’s LoadBalancer service (e.g., kubectl get svc -n ingress-nginx) is what provides the external IP or NodePort that your Ingress resources are exposed through. For L7, the controller uses this IP and inspects the incoming HTTP requests to decide where to send them based on host and path. For L4, it essentially uses this IP and the configured port to directly forward traffic to your specified backend service and port.
A common point of confusion is when you try to expose a TCP service using an Ingress resource without the backend-protocol: "TCP" annotation. The Ingress controller will likely try to interpret the incoming TCP traffic as HTTP, leading to connection errors or unexpected behavior because the client isn’t sending valid HTTP requests.
Many people assume that if they don’t specify host and path for L4, it will just work. However, the explicit backend-protocol: "TCP" or backend-protocol: "UDP" annotation is what truly switches the Ingress controller’s internal configuration to bypass L7 processing and perform raw TCP/UDP forwarding. Without it, the controller defaults to L7 and expects HTTP/HTTPS.
If you’re exposing a service via a LoadBalancer type Service in Kubernetes directly (not via an Ingress), that Service itself acts as an L4 load balancer. When you use an Ingress, you’re essentially saying, "I want to use the Ingress controller’s capabilities to route traffic, potentially based on L7 rules, to my backend Services." The backend-protocol annotation is the specific lever to tell the Ingress controller to treat it as a simple L4 forwarder.
The next thing you’ll likely run into is configuring TLS termination for your L7 HTTPS Ingress resources, which involves Kubernetes Secrets and certificate management.