Understanding Kubernetes: Networking basics
In the last article we looked at how Kubernetes builds workloads using controllers: Deployments create ReplicaSets, ReplicaSets manage Pods, and everything is driven by reconciliation.
Now we move one layer lower in the stack: networking. Because up to this point, we’ve mostly ignored how Pods actually communicate with each other. It’s time to change that. Let’s get started.
The core networking assumption
Kubernetes networking is built on a surprisingly strict assumption: Every Pod gets its own IP address and can communicate directly with every other Pod in the cluster. No NAT between Pods. No port forwarding. No node-level address translation. Just flat, routable connectivity between workloads.
Pods and network namespaces
Under the hood, each Pod gets its own Linux network namespace, containing:
- its own network interfaces
- its own IP address
- its own routing table
- its own loopback interface
All containers inside a Pod share this namespace, which is why they can talk to each other via localhost.
So the model is:
- inside a Pod → localhost
- between Pods → cluster network
There is one important exception, though: If hostNetwork: true is set, the Pod does not get its own network namespace and instead shares the node’s network stack. This is rarely used for application workloads, but common in system components.
Kubernetes does not implement networking
One of the most important architectural points: Kubernetes defines the networking model, but does not implement it. The actual networking is handled by a CNI plugin (Container Network Interface).
The CNI is responsible for:
- assigning Pod IPs
- attaching network interfaces to Pods
- configuring routes between nodes
- ensuring Pod-to-Pod connectivity works
Popular implementations include e.g. Calico, Flannel, or Cilium which all provide the same networking behavior from Kubernetes’ perspective, but implement it differently. Some use overlay networks (VXLAN, IP-in-IP), others use native routing (BGP or cloud routes), and modern systems like Cilium move large parts of this into the Linux kernel itself.
The important point is:
Kubernetes expects a flat Pod network — the CNI makes it real.
Cross-node communication (what actually happens)
Once Pods are scheduled across multiple nodes, something has to connect those networks.
There are two common models:
Overlay networks
In overlay-based setups, Pod traffic is encapsulated before leaving the node:
Pod packet → encapsulated (VXLAN / IP-in-IP) → sent over node network → decapsulated on target node → delivered to destination Pod
This makes the underlying infrastructure simpler, because it only sees node-to-node traffic.
The downside is overhead from encapsulation and MTU considerations.
Routed networks
In routed setups, nodes exchange routing information directly:
via BGP cloud route tables or explicit L3 routing configuration
Here, Pod CIDRs are directly reachable across nodes without encapsulation.
This is more efficient, but requires tighter integration with the underlying network.
From an application perspective, none of this matters:
Pod A → Pod B
is always the same mental model.
Services: where Kubernetes stops exposing raw Pods
At this point there is a subtle but important problem.
Pods are:
ephemeral rescheduled replaced during rollouts frequently assigned new IPs
So while Pod-to-Pod communication works, relying on Pod IPs is not stable.
This is where Services come in.
A Service provides:
a stable virtual IP a stable DNS name load balancing across matching Pods
Example:
apiVersion: v1 kind: Service metadata: name: backend spec: selector: app: backend ports: - port: 80 targetPort: 8080
Now instead of targeting individual Pods:
curl http://10.244.2.7
we can rely on:
curl http://backend
The key idea is:
Services decouple communication from Pod lifecycles.
kube-proxy and Service routing
A Service is not a process. It does not “run” anywhere.
So something still needs to implement:
Service IP → actual Pod IP
Traditionally, this is done by kube-proxy.
It runs on every node and programs network rules using:
iptables or IPVS
When traffic hits a Service IP:
the kernel matches the Service rule kube-proxy rules select a backend Pod traffic is forwarded to that Pod IP
This is also the layer where load balancing across replicas happens.
In modern clusters, this layer is increasingly replaced or bypassed by eBPF-based dataplanes like Cilium, which move service routing into the kernel more efficiently.
A quick reality check
You can verify the model with two Pods:
kubectl run nginx –image=nginx kubectl run toolbox –image=nicolaka/netshoot -it –rm – sh
Then:
kubectl get pods -o wide
From inside the toolbox Pod:
curl http://
This is the simplest proof of the model:
no Service involved yet no DNS no kube-proxy abstraction
Just raw Pod networking across nodes.
The mental model that matters
If you strip everything down, Kubernetes networking is really just three layers:
Pods get real IPs inside a flat network the CNI makes cross-node connectivity possible Services add stable identity on top of volatile endpoints
Everything else builds on top of this.
Once this model is clear, the next abstractions stop feeling arbitrary.
Looking ahead
Networking in Kubernetes only starts to make sense when you see it as a layered system.
In the next article we’ll move one layer up and focus on:
Services, DNS & Service Discovery
This is where ClusterIP Services, selectors, and endpoints start to matter — and where CoreDNS quietly becomes one of the most important components in the entire cluster.
After that, we’ll go outward:
Ingress, Gateway API & External Traffic
This is the north-south traffic layer — where Kubernetes stops being “internal cluster networking” and starts acting like a real application edge with load balancers, reverse proxies, and routing rules.
And finally, we’ll close the networking series with something intentionally practical:
Network Policies
Because once everything can talk to everything, the real question becomes what shouldn’t be able to talk at all.
header image created by buddy ChatGPT
