Kubernetes networking
With the basics from chapter 24, Kubernetes networking becomes four simple questions: how do containers in a Pod talk, how do Pods talk, how do Pods reach a Service, and how does the outside world get in. Each answer reuses an idea you already know.
The four kinds of traffic
Click each numbered path:
1 · Container to container
Same Pod = same network namespace. They call each other on localhost. Basic idea used: network namespaces (chapter 24, #10).
2 · Pod to Pod
Every Pod has a real IP from the Pod range, reachable from any node. Basic ideas: IP ranges and routing (#4, #6).
3 · Pod to Service
Call a stable name; DNS gives a virtual IP; rules on the node rewrite it to a real Pod IP. Basic ideas: DNS, NAT, load balancing (#5, #7, #8).
4 · Outside to inside
NodePort, LoadBalancer or Ingress lets the internet in. Basic ideas: port forwarding and reverse proxy (#7, #8).
The three rules
Kubernetes itself does not wire up the network. It sets rules, and a CNI plugin (Calico, Cilium, Flannel, or your cloud's own) builds a network that follows them:
- Every Pod gets its own IP address.
- Every Pod can reach every other Pod by that IP, on any node, with no NAT: the address a Pod sees for itself is the address others use.
- Nodes can reach every Pod, and Pods can reach nodes.
So the whole cluster feels like one big flat network. No port mapping between Pods, unlike docker run -p.
1 · Container to container: localhost
Covered in chapter 6: one Pod is one network namespace, so its containers share one IP and one localhost, and must use different ports.
sidecar → http://localhost:4000 → posts (never leaves the Pod)
2 · Pod to Pod: a flat network
Each node gets a slice of the Pod range for its Pods. Step through a request from posts on node 1 to comments on node 2:
node-1 owns 10.244.1.0/24 node-2 owns 10.244.2.0/24 posts Pod 10.244.1.15 comments Pod 10.244.2.7The cluster's Pod range (say
10.244.0.0/16) is split into one/24per node. The first three numbers tell you the node.[posts Pod] eth0 10.244.1.15 │ veth pair [node-1 bridge cni0]The packet for
10.244.2.7:4001leaves the Pod's namespace through its veth pair and arrives on node-1, exactly like the Docker bridge.node-1 routing table: 10.244.1.0/24 dev cni0 # my own Pods 10.244.2.0/24 via 192.168.1.12 # node-2's Pods → send to node-2Not one of node-1's own Pods, so the route says: send it to node-2. Some CNIs wrap the packet in a tunnel (VXLAN, an "overlay") instead; same idea.
[node-2] → bridge → veth → [comments Pod] eth0 10.244.2.7 :4001node-2 delivers it into the comments Pod. The comments app sees the request coming from
10.244.1.15, the real posts Pod IP. No NAT.
[posts Pod 10.244.1.15] ─veth─┐ ├─ bridge cni0 (node-1) [query Pod 10.244.1.22] ─veth─┘Both Pods are on node-1. The bridge, a virtual switch, connects them directly. The packet never leaves the machine.
3 · Pod to Service: a virtual IP
A ClusterIP like 10.96.31.40 is not on any machine. No network card has it. It exists only as rules on every node, written by kube-proxy, saying "traffic to 10.96.31.40:4001 → pick one of these Pod IPs". That is NAT plus load balancing.
// in the posts container axios.post('http://comments-srv:4001/events', event)Your code uses the Service name.
DNS (CoreDNS): comments-srv → 10.96.31.40The name becomes the Service's virtual IP. See the DNS section below.
packet: 10.244.1.15 → 10.96.31.40:4001 kube-proxy rule on node-1 (iptables / IPVS / nftables): 10.96.31.40:4001 → one of: 10.244.2.7:4001 10.244.1.30:4001Before the packet leaves node-1, the rule swaps the destination for a real Pod IP, chosen at random. Ready Pods only: the list comes from the Service's EndpointSlice (chapter 11).
packet: 10.244.1.15 → 10.244.2.7:4001 (rewritten)From here it is ordinary Pod-to-Pod traffic, as in the previous section.
reply: 10.244.2.7 → 10.244.1.15 node rewrites source back to 10.96.31.40The node remembers the connection (conntrack), so the reply appears to come from the Service IP. posts never knows which Pod answered.
Why ping a ClusterIP fails
The rules only cover the Service's TCP/UDP ports. Ping (ICMP) matches nothing, and nobody owns the address. Test a Service with curl or wget, never ping.
Long connections stick
The Pod is chosen per connection, not per request. A client holding one keep-alive or gRPC connection talks to the same Pod all the time.
DNS inside the cluster
The cluster runs its own DNS server, CoreDNS, itself a Deployment with a Service. Every Pod's /etc/resolv.conf points to it and lists search domains: suffixes tried automatically after a short name. That is why plain posts-srv works. Try names and namespaces:
| You write | Works from |
|---|---|
posts-srv | Pods in the same namespace |
posts-srv.default | Any namespace |
posts-srv.default.svc.cluster.local | Anywhere in the cluster (the full name) |
$ kubectl run tmp --rm -it --image=busybox:1.36 -- nslookup posts-srv
Name: posts-srv.default.svc.cluster.local
Address: 10.96.20.14
4 · Outside to inside
Pod and Service IPs are private to the cluster. To let a browser in, use one of these (chapter 11 for the first two):
| Way in | How | Use for |
|---|---|---|
kubectl port-forward | A tunnel from your laptop | Debugging only |
| NodePort | Same port opened on every node | Local clusters, demos |
| LoadBalancer | A cloud load balancer per Service | One or two public services |
| Ingress | One reverse proxy routing by host and path | Most web apps: one address, many Services, HTTPS in one place |
For the blog app from chapter 17, one Ingress replaces four LoadBalancers:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog
spec:
ingressClassName: nginx
rules:
- host: blog.example.com
http:
paths:
- path: /posts
pathType: Prefix
backend: { service: { name: posts-srv, port: { number: 4000 } } }
- path: /comments
pathType: Prefix
backend: { service: { name: comments-srv, port: { number: 4001 } } }
- path: /
pathType: Prefix
backend: { service: { name: client-srv, port: { number: 3000 } } }
NetworkPolicy: who may talk to whom
Rule 2 above means: by default every Pod can reach every Pod, in every namespace. A NetworkPolicy is a firewall rule for Pods, chosen by labels. Example: only the event bus may call moderation:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: moderation-only-from-bus
spec:
podSelector:
matchLabels: { app: moderation } # protect these Pods
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels: { app: event-bus } # only from these
ports:
- port: 4003
- Once a policy selects a Pod, everything not explicitly allowed is dropped, which shows up as a timeout, not "refused" (chapter 24, #9).
- It only works if your CNI supports it (Calico and Cilium do; plain Flannel does not).
- Chapter 28 goes deeper: default deny, namespaces, egress and common traps.
Debugging checklist
"posts cannot reach comments-srv". Go down the list; each step uses an idea from chapter 24:
- 1 · Does the name resolve? (DNS)
$ kubectl exec deployment/posts-depl -- nslookup comments-srvFails → typo, wrong namespace, or the Service does not exist (
kubectl get svc -A). - 2 · Does the Service have endpoints? (labels)
$ kubectl get endpointslices -l kubernetes.io/service-name=comments-srvEmpty → selector does not match Pod labels, or no Pod is ready.
- 3 · Does the Pod answer directly? (IP + port)
$ kubectl get pods -o wide # find the comments Pod IP $ kubectl exec deployment/posts-depl -- wget -qO- http://10.244.2.7:4001/Refused → app not listening on that port, or listening on 127.0.0.1. Check
targetPort= the app's port. - 4 · Does the Service answer? (kube-proxy)
$ kubectl exec deployment/posts-depl -- wget -qO- http://comments-srv:4001/Pod works but Service does not → check the Service
port/targetPort. - 5 · Timeouts? (policy)
$ kubectl get networkpolicy -AA policy silently dropping traffic is the usual cause of timeouts inside a cluster.
Recap
| Basic idea (ch. 24) | Where it shows up in Kubernetes |
|---|---|
| Network namespace | One per Pod; containers share it → localhost |
| IP ranges | Pod range split per node; separate Service range |
| Routing / bridge / veth | CNI plugin connects Pods across nodes |
| NAT + load balancing | kube-proxy rules turn a ClusterIP into a Pod IP |
| DNS | CoreDNS + search domains: posts-srv, posts-srv.team-a |
| Reverse proxy | Ingress: one entry point, routes by host and path |
| Firewall | NetworkPolicy |
| 0.0.0.0 | Apps in Pods must listen on all addresses |
Quick check
team-a calls http://posts-srv:4000 and gets ENOTFOUND. posts-srv runs fine in default. Fix?Show answer
Short names only search the caller's own namespace. Use http://posts-srv.default:4000.
ping 10.96.31.40 fails, but curl 10.96.31.40:4001 works. Is the Service broken?Show answer
No. A ClusterIP is virtual; only its Service ports are forwarded. Ping is not.
Kubernetes networking: common questions
Why can I not ping a Kubernetes ClusterIP?
A ClusterIP is virtual. kube-proxy only forwards the Service ports, so ICMP ping matches nothing. Test with curl or wget instead.
How do I call a Service in another namespace?
Add the namespace to the name, for example http://posts-srv.default:4000. Short names only resolve inside the caller’s own namespace.