Complete Request Flow: From Browser to Kubernetes Pod — Every Hop Explained
You type a URL into a browser and press Enter. Half a second later, a response renders on screen. Between those two events, a request passes through more distinct systems than most developers have ever mapped end to end — DNS resolvers, TLS handshakes, cloud load balancers, Kubernetes controllers, kernel netfilter rules, and container network interfaces — before the first byte of your application code runs.
This post maps every hop. Not at a hand-wavy architecture level, but precisely: what component handles the packet, what decision it makes, what it reads or writes, and what happens next. By the end, you will have a mental model accurate enough to diagnose failures at any layer.
The Complete Picture
Browser
│
│ 1. DNS resolution
▼
DNS Resolver (OS → Recursive → Authoritative)
│
│ 2. TCP connection + TLS handshake
▼
Cloud Load Balancer (AWS ALB / GCP GLB / Azure Front Door)
│
│ 3. HTTP/2 or HTTP/1.1 request forwarded
▼
Ingress Controller Pod (nginx / Traefik / Envoy)
│
│ 4. Path/host routing → selects backend Service
▼
kube-proxy (iptables / IPVS rules on the node)
│
│ 5. DNAT: Service ClusterIP → Pod IP
▼
CNI Network Plugin (Calico / Flannel / Cilium)
│
│ 6. Packet routed through overlay or underlay to target node
▼
Target Node Network Stack
│
│ 7. veth pair → container network namespace
▼
Application Pod
│
│ 8. Application code handles request
▼
Response travels back through the same layers
Each arrow is a real system boundary with latency, failure modes, and configuration. Let us walk through each one.
Hop 1: DNS Resolution
The browser has a hostname — api.example.com. It needs an IP address before it can open a TCP connection.
The Resolution Chain
Browser cache
│ miss
▼
OS DNS cache (nscd / systemd-resolved / dscacheutil)
│ miss
▼
OS stub resolver → reads /etc/resolv.conf
│
▼
Recursive resolver (ISP / 8.8.8.8 / 1.1.1.1)
│ cache miss
├──► Root nameserver (. → delegates to .com)
├──► TLD nameserver (.com → delegates to example.com)
└──► Authoritative nameserver (example.com → returns A/CNAME record)
│
▼
Returns: api.example.com → 203.0.113.42 (TTL: 60s)
For Kubernetes-hosted services, the authoritative nameserver is usually a cloud DNS product (Route 53, Cloud DNS, Azure DNS). The record it returns is the IP of the cloud load balancer sitting in front of your cluster — not a pod IP, and not a cluster IP.
DNS TTL and Caching
The TTL on the DNS record determines how long resolvers cache the answer. For cloud load balancers, 60 seconds is common. For latency-sensitive failover, lower TTLs (15–30 seconds) are used, at the cost of more DNS queries.
Browsers have their own DNS caches with independent TTLs. Chrome, for example, caps its DNS cache TTL at 60 seconds regardless of the record’s TTL. This means even with a 0-second TTL in DNS, browsers will cache the result for up to a minute — an important detail during IP migration.
Latency
- Browser/OS cache hit: < 1 ms
- Recursive resolver cache hit: 5–20 ms (round-trip to resolver)
- Full recursive resolution (root → TLD → authoritative): 50–200 ms
Hop 2: TCP Connection and TLS Handshake
With an IP address in hand, the browser opens a TCP connection to port 443.
TCP Three-Way Handshake
Browser Load Balancer
│ │
│── SYN ──────────────────────────► │
│ │
│ ◄──────────────────────── SYN-ACK │
│ │
│── ACK ──────────────────────────► │
│ │
│ [TCP connection established] │
One round-trip time (RTT) for TCP establishment. With geographic co-location, this is 5–20ms. Cross-continental: 80–200ms.
TCP Fast Open (TFO) allows data to be sent on the SYN packet for repeat connections, eliminating this round-trip for known servers — but browser support and server-side enablement is inconsistent.
TLS 1.3 Handshake
Modern TLS 1.3 completes in one round trip (1-RTT) after TCP:
Browser Load Balancer
│ │
│── ClientHello ──────────────────► │
│ (supported ciphers, key share, │
│ SNI: api.example.com) │
│ │
│ ◄───────────── ServerHello ───── │
│ ◄──────── EncryptedExtensions ── │
│ ◄──────────────── Certificate ── │
│ ◄──────────── CertificateVerify │
│ ◄──────────────────── Finished ──│
│ │
│── Finished + HTTP Request ──────► │
│ [Encrypted channel established] │
0-RTT resumption (TLS 1.3 session tickets) allows the browser to send the HTTP request alongside the first TLS message for resumed sessions — effectively zero additional round-trips for TLS on repeat visits.
Certificate Validation
The browser validates the server’s certificate chain:
- Signature chain leads to a trusted CA root in the browser’s trust store.
- The
SubjectAltNameorCNmatchesapi.example.com. - The certificate is not expired.
- Optional: OCSP stapling confirms the certificate has not been revoked.
If validation fails, the browser shows an error and the connection is aborted — the request never reaches Kubernetes.
SNI: How the Load Balancer Knows Which Certificate to Present
The ServerName field in the ClientHello (Server Name Indication) tells the load balancer which hostname the client is connecting to. This allows a single IP address to host multiple TLS-protected domains — essential for cloud load balancers serving many customers on shared infrastructure.
Hop 3: The Cloud Load Balancer
The TCP connection terminates at the cloud load balancer (AWS ALB, GCP Application Load Balancer, Azure Application Gateway). This is the first component inside your infrastructure.
What the Cloud Load Balancer Does
Internet
│
│ Public IP: 203.0.113.42
▼
┌─────────────────────────────────────────────────────┐
│ Cloud Load Balancer (ALB) │
│ │
│ TLS termination (decrypts request) │
│ HTTP/2 → HTTP/1.1 conversion (if needed) │
│ Health checks against backend targets │
│ Sticky sessions (if configured) │
│ WAF rules (if attached) │
│ Access logs, metrics │
│ │
│ Targets: [Node1:30080, Node2:30080, Node3:30080] │
└────────────────────────┬────────────────────────────┘
│
Selects a target node using round-robin
(or least-connections / IP hash)
│
▼
Node2:30080 (NodePort)
The load balancer forwards the decrypted (or re-encrypted) HTTP request to one of the Kubernetes nodes on the NodePort assigned to the Ingress controller Service (typically a random high port like 30080).
NodePort vs LoadBalancer Service Type
When a Kubernetes Service of type LoadBalancer is created, the cloud controller manager provisions a cloud load balancer automatically and configures it to forward traffic to the NodePort on each node. This is the standard path for production clusters on managed Kubernetes (EKS, GKE, AKS).
# What the cloud controller sees:
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
type: LoadBalancer # triggers cloud LB provisioning
ports:
- port: 443
targetPort: 443
nodePort: 30080 # allocated by Kubernetes (or set manually)
selector:
app: ingress-nginx
The cloud load balancer’s health check hits Node:NodePort every few seconds. Nodes that fail health checks are removed from the rotation.
Added Headers
The load balancer typically adds or rewrites HTTP headers before forwarding:
X-Forwarded-For: 198.51.100.7 (original client IP)
X-Forwarded-Proto: https (original protocol)
X-Forwarded-Port: 443
X-Request-ID: a1b2c3d4... (correlation ID, if configured)
Host: api.example.com (preserved or rewritten)
These headers are critical for your application to know the real client IP and original protocol — essential for rate limiting, audit logging, and redirect generation.
Hop 4: Ingress Controller
The packet arrives at a Kubernetes node on the NodePort (e.g., :30080). kube-proxy rules (covered in the next hop) DNAT this to the Ingress controller pod’s IP and port. The Ingress controller is the first Kubernetes-native component to handle the request.
What an Ingress Controller Is
An Ingress controller is an ordinary pod running a reverse proxy (nginx, Traefik, Envoy/Contour, HAProxy). It watches the Kubernetes API for Ingress resources and translates them into proxy configuration.
# Ingress resource — the routing rules
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-tls-cert # TLS cert stored as a Secret
rules:
- host: api.example.com
http:
paths:
- path: /v1/orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 8080
- path: /v1/users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
What the Ingress Controller Does with This
The ingress-nginx controller translates the above into an nginx upstream configuration:
# Generated nginx config (simplified)
upstream order-service-production-8080 {
server 10.244.1.5:8080; # Pod IP (from Endpoints)
server 10.244.2.8:8080;
server 10.244.3.2:8080;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/tls/tls.crt;
ssl_certificate_key /etc/tls/tls.key;
location /v1/orders {
proxy_pass http://order-service-production-8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Connection ""; # enable keepalives
}
}
Critically: the Ingress controller does not forward to the Service ClusterIP. It reads the Endpoints object directly and load-balances to pod IPs itself. This bypasses kube-proxy for the application traffic, giving the Ingress controller full control over load-balancing algorithm (round-robin, least-conn, ewma), health checking, and connection management.
The Endpoints Watch Loop
┌─────────────────┐
│ kube-apiserver │
└────────┬────────┘
│ Watch /api/v1/namespaces/production/endpoints
│
┌────────▼────────┐
│ Ingress Controller│
│ │
│ on update: │
│ reload upstreams│
└─────────────────┘
When a pod is added, removed, or fails a readiness probe, the Endpoints object is updated by the endpoint controller. The Ingress controller receives the watch event and updates its upstream configuration — nginx reloads without dropping connections (using the SO_REUSEPORT approach in newer versions).
Hop 5: kube-proxy and iptables/IPVS
When the Ingress controller sends a request to a backend pod (or when any pod communicates with a Service ClusterIP), it goes through kube-proxy’s rules.
Wait — didn’t we just say the Ingress controller bypasses kube-proxy by using pod IPs directly? Yes. But for the traffic from the cloud load balancer’s NodePort to the Ingress controller pod, and for any other service-to-service communication, kube-proxy rules are in the path.
What kube-proxy Does
kube-proxy does not proxy traffic. Despite the name, it does not sit in the data path at runtime. Instead, it programs the kernel’s netfilter (iptables) or IPVS tables to implement Service ClusterIP routing. The kernel does the actual packet rewriting — kube-proxy just keeps the rules up to date.
iptables Mode (Default)
For a Service with three pods:
Service: order-service
ClusterIP: 10.96.45.20
Port: 8080
Endpoints:
10.244.1.5:8080 (Pod on Node1)
10.244.2.8:8080 (Pod on Node2)
10.244.3.2:8080 (Pod on Node3)
kube-proxy writes iptables rules like these:
# In the PREROUTING and OUTPUT chains:
-A KUBE-SERVICES -d 10.96.45.20/32 -p tcp --dport 8080 \
-j KUBE-SVC-ORDERSERVICE
# In the KUBE-SVC-ORDERSERVICE chain (probabilistic selection):
-A KUBE-SVC-ORDERSERVICE -m statistic --mode random --probability 0.333 \
-j KUBE-SEP-POD1
-A KUBE-SVC-ORDERSERVICE -m statistic --mode random --probability 0.500 \
-j KUBE-SEP-POD2
-A KUBE-SVC-ORDERSERVICE \
-j KUBE-SEP-POD3
# Each SEP chain does DNAT:
-A KUBE-SEP-POD1 -p tcp -j DNAT --to-destination 10.244.1.5:8080
-A KUBE-SEP-POD2 -p tcp -j DNAT --to-destination 10.244.2.8:8080
-A KUBE-SEP-POD3 -p tcp -j DNAT --to-destination 10.244.3.2:8080
When a packet arrives destined for 10.96.45.20:8080, the kernel traverses these chains, picks a pod probabilistically, and rewrites the destination IP:port (DNAT). The source IP is unchanged. The connection tracking (conntrack) table records the mapping so the return packets can be un-NATted.
The iptables Scaling Problem
iptables rules are evaluated linearly. A cluster with 1,000 Services and 10,000 endpoints has tens of thousands of iptables rules. Every packet traverses the relevant chains sequentially. At scale this causes:
- Measurable per-packet latency increase
- High CPU usage on kube-proxy during rule updates (the entire ruleset is replaced atomically)
- Long update times (seconds) when many Services change simultaneously
IPVS Mode
IPVS (IP Virtual Server) is a kernel load balancer that uses a hash table instead of linear rule traversal. kube-proxy in IPVS mode creates a virtual server for each Service and real servers for each endpoint:
IP Virtual Server version 1.2.1
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.45.20:8080 rr
-> 10.244.1.5:8080 Masq 1 0 0
-> 10.244.2.8:8080 Masq 1 0 0
-> 10.244.3.2:8080 Masq 1 0 0
Lookup is O(1) via hash. IPVS supports richer scheduling algorithms: round-robin (rr), least-connection (lc), destination hash (dh), source hash (sh), shortest expected delay (sed). For clusters with thousands of services, IPVS mode is strongly recommended.
Cilium: Bypassing kube-proxy Entirely
Cilium (an eBPF-based CNI) can replace kube-proxy entirely. Instead of iptables/IPVS rules in the kernel’s netfilter path, Cilium attaches eBPF programs directly to network interfaces. These programs run earlier in the packet processing path, with lower overhead and finer-grained observability.
Without Cilium (kube-proxy):
Packet → NIC → iptables PREROUTING → routing → iptables FORWARD → pod
With Cilium (kube-proxy replacement):
Packet → NIC → eBPF XDP hook → (DNAT in eBPF) → pod
↑
runs before iptables, lower latency
Hop 6: CNI and Pod-to-Pod Networking
After kube-proxy (or Cilium) rewrites the destination to a pod IP, the packet must be delivered to the pod — possibly on a different node.
Pod IPs and the Flat Network Model
Kubernetes mandates a flat network model: every pod gets a unique IP, and every pod can communicate with every other pod directly by IP, without NAT, regardless of which node they are on.
The CNI (Container Network Interface) plugin implements this guarantee. How it does so depends on the plugin:
Overlay networks (Flannel VXLAN, Weave): The pod IP packet is encapsulated in a UDP frame (VXLAN) using the node’s physical IP. The receiving node decapsulates it and delivers to the pod. This adds ~50 bytes of overhead per packet and a small latency cost.
Node1 (10.0.1.10) Node2 (10.0.1.20)
┌─────────────────┐ ┌─────────────────┐
│ Pod A │ │ Pod B │
│ 10.244.1.5 │ │ 10.244.2.8 │
└──────┬──────────┘ └──────┬──────────┘
│ veth0 │ veth0
▼ ▼
cni0 bridge cni0 bridge
│ │
▼ ▼
flannel0 (VXLAN) ──────────────► flannel0 (VXLAN)
│ │
▼ ▼
eth0 (10.0.1.10) eth0 (10.0.1.20)
[outer UDP/VXLAN packet]
Underlay/BGP networks (Calico, Cilium native): Routes are advertised via BGP directly on the physical network. No encapsulation — the pod IP is natively routable on the underlying network. Lower overhead, requires network infrastructure that supports BGP.
Node1: advertises 10.244.1.0/24 via BGP
Node2: advertises 10.244.2.0/24 via BGP
Packet from Pod A (10.244.1.5) to Pod B (10.244.2.8):
Node1's kernel routes to 10.244.2.0/24 → next hop: Node2 (10.0.1.20)
Physical network delivers to Node2
Node2's kernel routes 10.244.2.8 → veth pair → Pod B
The veth Pair
Every pod is connected to the node via a veth pair — a virtual Ethernet cable with two ends. One end (eth0) is inside the pod’s network namespace. The other end is on the node (named something like vethXXXXXXX) and connected to the CNI bridge or directly to the node’s routing table.
# On the node:
ip link show type veth
# 7: veth3a8b2c1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
# The pod sees the other end as eth0:
# (kubectl exec into pod)
kubectl exec -it order-pod -- ip addr
# 2: eth0@if7: <BROADCAST,MULTICAST,UP> inet 10.244.1.5/24
Packets entering the pod’s eth0 come from the node via the veth pair. Packets leaving the pod’s eth0 exit via the veth pair to the node, where the node’s routing table forwards them.
Hop 7: The Pod Receives the Request
The packet finally arrives at the pod. Inside the pod’s network namespace, the kernel’s TCP stack processes it. The container listens on a port (e.g., :8080), and the process handling that port (your application) receives the connection.
What the Pod Sees
The pod sees the request as coming from the Ingress controller pod’s IP (if the Ingress controller proxies without SNAT) or from the node’s IP (if SNAT was applied). The original client IP is in the X-Forwarded-For header — your application must read this header if it needs the real client IP.
// Reading the real client IP in Go
func clientIP(r *http.Request) string {
// Check headers set by load balancer / ingress controller
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// XFF can be a comma-separated list; the first is the original client
parts := strings.Split(xff, ",")
return strings.TrimSpace(parts[0])
}
if xri := r.Header.Get("X-Real-Ip"); xri != "" {
return xri
}
// Fall back to direct connection IP (will be node/proxy IP)
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
return ip
}
Request Headers by the Time They Reach the Pod
GET /v1/orders HTTP/1.1
Host: api.example.com
X-Forwarded-For: 198.51.100.7, 203.0.113.42
X-Forwarded-Proto: https
X-Request-ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
X-Real-Ip: 198.51.100.7
User-Agent: Mozilla/5.0 ...
Accept: application/json
Authorization: Bearer eyJhbGci...
Each intermediate hop has added headers. The application sees the full request enriched with routing and tracing metadata.
The Return Path
The response travels back through most of the same layers:
- Pod → veth → node routing — the response packet has source IP
10.244.1.5:8080and destination IP of the Ingress controller pod. - CNI delivers to Ingress controller pod — across nodes if needed, via overlay or BGP routes.
- Ingress controller → cloud load balancer — the Ingress controller sends the HTTP response back on the upstream TCP connection. The cloud load balancer forwards it to the browser on the established TLS connection.
- Cloud load balancer → browser — the TLS-encrypted response is delivered over the original TCP connection.
- Browser renders — the response body is decoded and rendered.
The conntrack table on each node tracks the NAT mappings, so return packets are un-NATted correctly at each hop.
Timing Budget: Where the Latency Goes
For a request from a user 50ms RTT from your load balancer, serving a simple API response in 5ms of application time:
| Hop | Latency | Notes |
|---|---|---|
| DNS resolution | 0–5 ms | Cache hit; 50–200ms cold |
| TCP handshake | 50 ms | 1 RTT to load balancer |
| TLS 1.3 handshake | 50 ms | 1 RTT (0-RTT on resume: 0ms) |
| Cloud LB → Node | 1–2 ms | Internal network |
| kube-proxy DNAT | < 0.1 ms | Kernel netfilter |
| CNI (same node) | < 0.1 ms | veth pair |
| CNI (cross-node) | 0.5–2 ms | Overlay encap or BGP routing |
| Application processing | 5 ms | Your code |
| Return path | ~50 ms | Symmetric to request path |
| Total (warm) | ~156 ms | |
| Total (cold DNS) | ~300 ms | Cold DNS + TLS |
The dominant latency is almost always the client’s RTT to the load balancer — which is why CDNs and regional load balancer endpoints exist. Everything inside the cluster is measured in sub-millisecond to low-millisecond increments.
Failure Modes at Each Hop
Understanding the path makes failures diagnosable:
DNS Failure
Symptom: curl: (6) Could not resolve host: api.example.com
Causes: TTL expired + authoritative nameserver down; misconfigured NS records; split-horizon DNS misconfiguration.
Diagnose: dig api.example.com @8.8.8.8 +trace to walk the full resolution chain.
TLS Failure
Symptom: curl: (60) SSL certificate problem: certificate has expired
Causes: Expired certificate; certificate not covering the hostname (SAN mismatch); CA not trusted.
Diagnose: openssl s_client -connect api.example.com:443 -servername api.example.com to inspect the full certificate chain.
Cloud Load Balancer Health Check Failure
Symptom: 503 from the load balancer; all nodes removed from rotation.
Causes: NodePort not reachable (firewall rules); Ingress controller pods not running; security group blocks health check probe IPs.
Diagnose: Cloud LB health check status in the console; check kubectl get pods -n ingress-nginx.
Ingress Controller Misconfiguration
Symptom: 404 from nginx; correct hostname but wrong path matching.
Causes: Ingress pathType: Prefix vs Exact mismatch; missing ingressClassName; wrong namespace.
Diagnose: kubectl describe ingress api-ingress -n production; check Ingress controller logs.
Pod Not Ready / Endpoint Missing
Symptom: Intermittent 502/503; some requests succeed, some fail.
Causes: Pod failing readiness probe but not yet removed from Endpoints; pod restarting; terminationGracePeriodSeconds too short.
Diagnose: kubectl get endpoints order-service -n production; kubectl describe pod; check readiness probe configuration.
CNI / Network Plugin Failure
Symptom: Pod-to-pod communication fails; DNS lookups from pods fail.
Causes: CNI plugin crashed; VXLAN MTU mismatch (common with overlay networks); iptables rules corrupted.
Diagnose: kubectl exec into a pod and curl another pod’s IP directly; check CNI plugin DaemonSet logs; ip route on the node.
kube-proxy / iptables Rules Stale
Symptom: Requests to ClusterIP fail; Service exists but connection refused.
Causes: kube-proxy pod crashed; iptables rules not updated after pod scaling.
Diagnose: iptables -t nat -L KUBE-SERVICES | grep <service-ip> on the node; check kube-proxy pod logs.
Key Configuration Points Across the Stack
Connection Draining (Graceful Shutdown)
When a pod is deleted or evicted, requests in flight must complete before the pod disappears:
spec:
terminationGracePeriodSeconds: 30 # Kubernetes waits this long
containers:
- name: api
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # allow LB to deregister
The preStop sleep gives the Ingress controller and kube-proxy time to remove the pod from their routing tables before the process receives SIGTERM. Without it, a brief window exists where the pod is terminating but still receives traffic — causing connection resets.
ReadinessProbe vs LivenessProbe
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
# Pod removed from Endpoints if this fails — stops NEW traffic
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
# Pod restarted if this fails — last resort recovery
The readiness probe gates Endpoint membership — a failing readiness probe removes the pod from the Endpoints object, which propagates to the Ingress controller’s upstream configuration (within seconds). The liveness probe gates pod restart — it should only fail for states the pod cannot recover from on its own.
Service topologyAwareRouting
For latency-sensitive services, route traffic preferentially to pods in the same availability zone:
apiVersion: v1
kind: Service
metadata:
name: order-service
annotations:
service.kubernetes.io/topology-mode: "Auto"
spec:
ports:
- port: 8080
selector:
app: order-service
With topologyMode: Auto, the EndpointSlice controller adds topology hints, and kube-proxy prefers endpoints in the same zone. Cross-zone traffic (which has inter-AZ data transfer costs and higher latency) is only used when local endpoints are unavailable.
Key Takeaways
The journey from browser to pod crosses more than a dozen distinct system boundaries. Each one is independently configurable, independently observable, and independently failable. Knowing the full path gives you three capabilities:
-
Precise failure diagnosis. A 502 from the cloud load balancer means something different from a 502 from the Ingress controller, which means something different from a connection reset at the pod. The error and its origin tell you exactly where to look.
-
Informed performance optimisation. The latency budget shows that DNS and TLS dominate on cold requests. CDN termination, TLS session resumption, and HTTP/2 multiplexing have more impact than most in-cluster optimisations.
-
Safe configuration changes. Adding a readiness probe endpoint, tuning
terminationGracePeriodSeconds, switching kube-proxy to IPVS mode, or replacing the CNI plugin each affects a specific layer. Knowing which layer lets you reason about the change’s blast radius before you make it.
The request flow is not magic. It is a chain of well-specified components, each doing one job, each leaving its mark in logs and metrics you can read. Map the chain, and the cluster becomes legible.