Module 4: Networking Ingress And Networkpolicy

2. Kubernetes' networking model: every Pod, an IP

Description

Since Module 2, this guide has used Pod IP addresses without explaining where they come from: 10.244.1.3, 10.244.2.4, different each time a Pod is recreated. This lesson opens that box. Kubernetes requires, as a non-negotiable contract, that every Pod have its own IP address, with no need for network address translation (NAT) to talk to another Pod — regardless of whether that other Pod lives on the same node or a different one. That contract is fulfilled by a piece you hadn't met by name yet: the CNI. kind ships one by default, kindnet, and this lesson explains exactly what it does.

Connection to the module

This lesson is the technical foundation for everything else in this module. Lesson 3's Ingress depends on the controller being able to reach any andes-cargo-status-api Pod regardless of which node it runs on; lesson 6's NetworkPolicy depends on there being a layer capable of intercepting that same traffic and deciding whether to let it through. Both are the CNI's work — this lesson explains what it is before building on top of it.


Kubernetes' networking contract, in four rules

Kubernetes doesn't implement networking itself — it delegates that responsibility to an external plugin, the CNI (Container Network Interface), but it requires any CNI installed to satisfy four rules, with no exception, documented by the project itself:

  1. All Pods can communicate with all other Pods, without using NAT.
  2. All nodes can communicate with all Pods (and vice versa), without NAT.
  3. The IP a Pod sees for itself is the same IP everyone else sees — no address translation along the way.
  4. DaemonSet Pods/system agents can communicate the same as any other Pod.

The first rule is the one that most breaks the intuition of anyone coming from plain Docker: in Docker, by default, containers on the same bridge network can talk to each other, but exposing a port to the outside world typically goes through host port mapping (-p 8080:8080) — a form of NAT. Kubernetes discards that idea at the root: every Pod gets a complete, routable IP inside the cluster, period. You're never going to see, in a Kubernetes manifest, anything resembling docker run's -p.

                WHAT THE CNI GUARANTEES (the four rules)

  Node: andes-cargo-cluster-worker         Node: andes-cargo-cluster-worker2
  ┌───────────────────────────┐            ┌───────────────────────────┐
  │  Pod A                     │            │  Pod C                     │
  │  10.244.1.2                │◄──────────►│  10.244.2.3                 │
  │                             │  no NAT    │                             │
  │  Pod B                     │            │  Pod D                     │
  │  10.244.1.5                │            │  10.244.2.2                 │
  └───────────────────────────┘            └───────────────────────────┘
         ▲                                          ▲
         │              no NAT, in either              │
         └──────────────  direction  ────────────────────┘

Who assigns those addresses: each node's podCIDR

Each node in andes-cargo-cluster receives, when joining the cluster, its own range of addresses the CNI hands out one IP from to each Pod created there. You can confirm it yourself against your own cluster:

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"  podCIDR="}{.spec.podCIDR}{"\n"}{end}'

What to expect (literal — these three ranges are kind's default configuration, fixed unless you change kind-config.yaml):

andes-cargo-cluster-control-plane  podCIDR=10.244.0.0/24
andes-cargo-cluster-worker  podCIDR=10.244.1.0/24
andes-cargo-cluster-worker2  podCIDR=10.244.2.0/24

Three /24 ranges (254 usable addresses each), one per node, all inside the broader 10.244.0.0/16 range kind reserves for Pods across the whole cluster. This explains something you already saw without knowing it: why andes-cargo-status-api's Pod IPs in Module 2 started with 10.244.1. or 10.244.2. — the prefix gives away which node each one runs on, with no need to run kubectl get pods -o wide to know it.


kindnet: the CNI kind installs by default

When you ran kind create cluster in Module 1, the ✓ Installing CNI 🔌 line in the output wasn't decorative — it really installed a CNI called kindnet, maintained by the kind project itself. Confirm it:

kubectl get daemonset kindnet -n kube-system -o wide

What to expect (literal — the image's version tag, formatted vYYYYMMDD-<hash>, changes with each kind release; the rest is literal for your kind version):

NAME      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE     CONTAINERS    IMAGES                                          SELECTOR
kindnet   3         3         3       3            3           kubernetes.io/os=linux   7m15s   kindnet-cni   docker.io/kindest/kindnetd:v20260528-9350166c   app=kindnet

DESIRED: 3 — one kindnet Pod per each of the cluster's three nodes, because kindnet is declared as a DaemonSet (a workload type this module doesn't cover in depth, but you can already guess from the name: "one daemon per node," no exceptions, including the control-plane). Each kindnetd instance is responsible for programming the network routes on the node it lives on, so the CNI's four rules hold between that node and every other one.

kindnet is deliberately simple: it uses standard Linux kernel network routes (netlink) and a minimal ptp/host-local implementation to assign IPs — enough to satisfy the contract's four rules, without an enterprise-grade CNI's advanced capabilities (traffic segmentation between namespaces, in-transit encryption, its own network policy engines). That simplicity has a concrete consequence this module's lesson 7 investigates in depth with real evidence: how far kindnet actually goes when you ask it to enforce a NetworkPolicy.


Proof of the four rules, with real evidence from this same cluster

No need to just believe the theory — the cluster already demonstrates it. In Module 2, a Service (status-api-service) balanced traffic across Pods running on different nodes (andes-cargo-cluster-worker and andes-cargo-cluster-worker2), with no additional network configuration making it possible — it worked because the CNI already satisfied rule 1 from the moment the cluster was created. Confirm it again, now that you know what makes it possible:

kubectl get pods -n andes-cargo -o wide

What to expect (names, IPs, and AGE are your variable values — the pattern of two different nodes, with 10.244.1. and 10.244.2. prefixes, is what confirms rule 1 in action):

NAME                                      READY   STATUS    RESTARTS   AGE   IP           NODE                          NOMINATED NODE   READINESS GATES
andes-cargo-status-api-548966dd97-bpqjq   1/1     Running   0          10m   10.244.1.2   andes-cargo-cluster-worker    <none>           <none>
andes-cargo-status-api-548966dd97-jwx5r   1/1     Running   0          10m   10.244.2.3   andes-cargo-cluster-worker2   <none>           <none>
andes-cargo-status-api-548966dd97-ls2bc   1/1     Running   0          10m   10.244.2.2   andes-cargo-cluster-worker2   <none>           <none>

Three Pods, spread across two different nodes by kube-scheduler (a piece Module 1, lesson 6, already introduced), and Module 2's status-api-service talks to all three exactly the same way, with no configuration difference based on which node each one lives on. That uniformity — "it doesn't matter where the Pod runs, the network works the same" — is, in one sentence, everything this contract promises.


Analogy: a city's postal system, with new streets every week

Think of each Pod as a new resident of a city where the streets get rebuilt constantly — Pods that get recreated, with different IPs each time, exactly as you saw in Module 2. Without a reliable postal system, every time someone moved you'd have to tell all their contacts their new address by hand. The CNI is the city's postal system: it guarantees that, no matter which street (node) each resident lives on today, any letter (network packet) addressed to their current address (Pod IP) arrives directly, with no central office translating addresses (NAT) along the way. The mail carrier (kindnet) does that work behind the scenes, on every corner of the city (every node), with no resident having to think about how delivery works.


Deep dive: why this matters for EKS

The four-rule contract isn't a kind quirk — it's part of the Kubernetes specification itself, and any cluster calling itself "Kubernetes," including EKS, has to satisfy it. What does change between kind and EKS is which CNI implements the contract: kind uses kindnet by default (this lesson); EKS uses, by default, the Amazon VPC CNI, an AWS-native CNI that assigns each Pod a real IP from the VPC where the cluster lives — not a separate internal range like 10.244.0.0/16 — which lets a Pod talk directly to other AWS resources (an EC2 instance, an RDS database) with no additional hop. This guide's Module 7 comes back to this difference in more detail. What does not change is the contract itself: the same NetworkPolicy you write against kind in this module's lesson 7 is syntactically identical to what you'd write against a real EKS cluster — the Kubernetes API is the same, only which CNI decides, underneath, whether that policy is actually enforced changes.


Common mistakes

Assuming a Pod's IP is stable, and saving it somewhere to reuse later (conceptual, already previewed in Module 2 but easy to forget here). What happens: someone, seeing a Pod has a "real," routable IP, copies it and uses it directly in a script or another configuration, instead of using the Service's DNS name. Why it happens: today's lesson demonstrates the IP really works, and it's tempting to treat it as a permanent address. How to spot it: if you have a Pod IP hardcoded anywhere in this project's configuration. How to fix it: the IP is real and routable, but ephemeral — it changes every time the Pod is recreated (Module 2, lesson 6). The CNI guarantees the IP works while it exists; it never guarantees it keeps existing.

Thinking a node's podCIDR limits how many Pods can run there, with no verification (calculation). What happens: someone sees /24 (254 addresses) and assumes that's also the real per-node Pod limit, without considering Kubernetes has an independent, typically lower limit (110 Pods per node by default, configurable). How to spot it: if you're sizing a cluster based only on the podCIDR's size. How to fix it: the podCIDR is the upper bound of available addresses; the real limit of Pods a node can sustain is almost always reached first by the node's own CPU/memory capacity, not by address space — this guide never reaches that limit in any module, but it's worth not confusing the two numbers.

Confusing "the CNI satisfies the four rules" with "any traffic is allowed" (this lesson's most important conceptual mistake, which lesson 6 corrects in depth). What happens: someone concludes that, because every Pod can technically talk to every Pod, that means it should be able to in a real production cluster. Why it happens: "can" and "should be able to" sound the same right after you just confirmed connectivity with real evidence. How to spot it: if you finish this lesson thinking Kubernetes' networking model is, by design, insecure. How to fix it: the CNI's contract guarantees technical capability for communication — it says nothing about whether that communication should exist. That's exactly the question NetworkPolicy answers, starting in this same module's lesson 6.


Exercises

Exercise 1 — List the four rules without looking at the lesson. Without going back to the corresponding section, write from memory the four rules any Kubernetes CNI has to satisfy.

See solution
  1. All Pods can communicate with all other Pods, with no NAT.
  2. All nodes can communicate with all Pods (and vice versa), with no NAT.
  3. The IP a Pod sees for itself is the same one everyone else sees.
  4. System agent Pods (DaemonSet) communicate the same as any other Pod.

Exercise 2 — Predict a five-node cluster's podCIDR. If andes-cargo-cluster had, instead of three nodes, five (one control-plane and four workers), and kind kept handing out consecutive /24 ranges inside 10.244.0.0/16, which podCIDR would you expect to see on the fifth node (the last worker)?

See solution

10.244.4.0/24 — following the same pattern you already saw (control-plane gets 10.244.0.0/24, the first worker gets 10.244.1.0/24, the second 10.244.2.0/24), the assignment order continues sequentially: the third worker would get 10.244.3.0/24, and the fourth (the cluster's fifth node overall) would get 10.244.4.0/24.

Exercise 3 — Explain, without the word "NAT," why plain Docker and Kubernetes differ on this point. A colleague who only knows docker run -p 8080:8080 asks you why Kubernetes doesn't use host port mapping the same way to expose a Pod. Explain it to them in two or three sentences, without using the word "NAT."

See solution

A reasonable explanation: "Docker, by default, gives each container an internal IP only the machine itself can reach directly — mapping a host port is how you open a window into that container from outside. Kubernetes solves the problem differently: it gives every Pod a complete address, directly reachable by any other Pod in the cluster, with no need for any special window — not because it's 'simpler,' but because a cluster has dozens or hundreds of Pods talking to each other all the time, and translating every one of those connections would be far more work than giving each one a real address from the start."


Summary and next step

This lesson opened up Kubernetes' networking model: four rules any CNI has to satisfy (Pod-to-Pod with no NAT, node-to-Pod with no NAT, consistent IP, system agents included), each node's own podCIDR handing out addresses, and kindnet as the simple CNI kind installs by default to satisfy that contract — confirmed, with real evidence from your own cluster, both in the DaemonSet itself and in the cross-node traffic Module 2 already demonstrated without you knowing it.

Before moving on you should be able to: list the CNI's four rules from memory; explain where a new Pod's IP comes from; and distinguish "the CNI technically allows the communication" from "the communication should be allowed."

Next lesson: Ingress, an HTTP door for the cluster. With the networking model as the foundation, lesson 3 explains the object that's going to use that internal connectivity to solve a different problem: how traffic gets in from outside the cluster, through a single HTTP door, instead of one per Service.

Resources

  1. Kubernetes — Cluster Networking — the official specification for a Kubernetes cluster's four networking rules.
  2. kind — Configuration: Networking — official reference for kind-config.yaml's networking field, including podSubnet/serviceSubnet.
  3. GitHub — kubernetes-sigs/kind: kindnetd — the source code for the CNI you installed without knowing it since Module 1.
  4. Kubernetes — kubectl Reference: get nodes — reference for kubectl get -o jsonpath, used in this lesson to inspect the podCIDR.