How External Traffic Reaches Your Pods
Summary
- A user's request travels through DNS, a cloud load balancer, an ingress data plane, a Service, and finally kube-proxy rules before it lands on a pod.
- Insight: Kubernetes has no built-in L7 HTTP router.
Ingressand Gateway API are just configuration; a running controller (nginx, Envoy, a cloud ALB) does the actual routing. - Key Concepts: North-south traffic, Service (ClusterIP), Endpoints/EndpointSlice, kube-proxy, ingress controller data plane, L4 vs L7.
- When to Use: Any time you expose an HTTP or HTTPS app to clients outside the cluster and need host or path routing, TLS termination, and stable public entry.
- Limitations/Trade-offs: More hops means more places to misconfigure. Each layer has its own health checks, timeouts, and failure modes.
- Related Topics: Ingress objects, ingress controllers, TLS certificates, and the newer Gateway API.
Foundations
External traffic reaching a pod is a chain of independent systems, not one component.
Think of it as five links: DNS resolution, a cloud load balancer, the ingress controller's proxy, a Kubernetes Service, and the pod's network namespace.
Each link is owned by a different layer of the stack, and each can be debugged in isolation.
The mental model that matters most: routing rules and the thing that enforces them are separate.
An Ingress object or an HTTPRoute is inert YAML in etcd. It only has an effect because a controller watches it and reprograms a proxy.
If no controller is installed, your Ingress will exist happily and route nothing.
This separation is why "I applied the Ingress but nothing works" is almost always a controller problem, not a manifest problem.
Mechanics & Interactions
Follow one request from https://app.example.com/checkout down to a pod.
Step 1 - DNS. The client resolves app.example.com to a public IP. That record points at your cloud load balancer or the external IP of the controller's Service.
Step 2 - Cloud load balancer (L4 or L7). Most managed clusters front the ingress controller with a Service of type: LoadBalancer, which provisions a cloud LB. This LB does L4 (TCP) forwarding to the nodes, or in the case of an ALB-style controller, L7 itself.
Step 3 - The ingress data plane. Traffic arrives at the controller's proxy pods (nginx, Envoy, HAProxy, Traefik). The proxy inspects the HTTP host header and path, matches an ingress rule, and picks a backend Service.
Step 4 - The Service. The rule names a Service and port. The controller resolves that Service to its current set of pod IPs via EndpointSlices, then load-balances across them.
Here is the key nuance: many modern controllers route directly to pod IPs read from EndpointSlices, bypassing the Service's ClusterIP entirely.
Older or simpler setups send traffic to the ClusterIP, where kube-proxy (iptables or IPVS rules on the node) does the final DNAT to a pod.
Step 5 - The pod. The packet lands in the pod's network namespace on the container's listening port, and your application handles it.
A minimal Service and Ingress that wire this up:
apiVersion: v1
kind: Service
metadata:
name: checkout
spec:
selector:
app: checkout
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /checkout
pathType: Prefix
backend:
service:
name: checkout
port:
number: 80The ingressClassName binds this object to a specific installed controller.
Without a matching IngressClass, no controller claims the object.
Advanced Considerations & Applications
L4 versus L7 matters for what you can do. A Service of type: LoadBalancer alone gives you L4: it forwards ports, but cannot route by host or path or terminate TLS with SNI. For HTTP routing you need an L7 layer, which is what ingress controllers and Gateway API provide.
TLS termination usually happens at the controller. The proxy holds the certificate and private key, decrypts the request, and forwards plaintext (or re-encrypted traffic) to the pod. This centralizes cert management.
externalTrafficPolicy affects the source IP. With Cluster (the default), a node may forward to a pod on another node, hiding the client IP behind SNAT. With Local, traffic only goes to pods on the receiving node, preserving the client source IP but risking imbalance.
Readiness gates the whole chain. A pod without a passing readiness probe is removed from its EndpointSlice, so the controller stops sending it traffic. Bad probes are a common cause of intermittent 502s.
Direct-to-pod routing changes failure behavior. When the controller talks to pod IPs directly, kube-proxy is out of the hot path, latency drops, and load balancing is more even, but the controller must react quickly to endpoint changes.
Common Misconceptions
"An Ingress object load-balances traffic." No. The Ingress is configuration. The controller's proxy load-balances. Delete the controller and routing stops even though the object remains.
"Services are the L7 router." A Service is L4. It has no concept of HTTP hosts, paths, or headers. L7 routing is the ingress controller's job.
"kube-proxy handles ingress." kube-proxy programs Service ClusterIP and NodePort rules. Many ingress controllers bypass it by routing to pod IPs directly. It is not the ingress data plane.
"type: LoadBalancer gives me path routing." It gives you a raw L4 load balancer. Host and path routing require an L7 ingress or Gateway layer on top.
"Docker Engine runs my pods, so it handles the networking." On cluster nodes the CRI runtime is containerd, not Docker Engine. Docker is for building images and local development, not the in-cluster data plane.
FAQs
What is the difference between north-south and east-west traffic? North-south is traffic entering or leaving the cluster (clients to pods). East-west is pod-to-pod traffic inside the cluster. Ingress handles north-south.
Do I always need a cloud load balancer? No. On bare metal you might use MetalLB, a NodePort, or hostNetwork for the controller. Managed clouds typically provision an LB for the controller's Service.
Why do I get a 502 from the ingress but the pod is running? The pod is likely not in the Service's EndpointSlice because its readiness probe is failing, or the target port does not match the container's listening port.
Can the client's real IP survive all these hops? Only if you preserve it. Use externalTrafficPolicy: Local or trust the X-Forwarded-For header the proxy sets, and configure the LB accordingly.
Is Gateway API a different path than Ingress? It is a different API for expressing the same north-south routing, backed by a controller. The physical hops (LB, proxy, Service, pod) are the same.
Related
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).