Pod networking and CNI

The Kubernetes network model, the two address ranges every cluster has, how a CNI plugin wires a Pod into the network, and how to prove two Pods can talk.

Networking · 20%35 min2 tasks

After this lesson

  • 01You can state the three rules of the Kubernetes network model and explain which component implements each one.
  • 02You can read the Pod CIDR and the Service CIDR from a running control plane and tell them apart from a node's podCIDR.
  • 03You can find any Pod's IP and node, then test Pod-to-Pod connectivity from a throwaway client Pod without leaving the terminal.

The curriculum line is Understand connectivity between Pods. It is rarely a task by itself. It is the knowledge you need in every Service, NetworkPolicy and troubleshooting question: which IP a packet goes to, who assigned it, and what sits between two Pods. Get the model straight here, and the rest of the networking module is just a series of objects built on top of it.

The network model in three rules

  • Every Pod gets its own IP address. All containers in a Pod share one network namespace, so they share that IP and reach each other on localhost. Two containers in one Pod cannot both bind port 8080.
  • Pods reach every other Pod directly, without NAT. A Pod on cka-worker connects to a Pod on cka-worker2 at the target's real Pod IP, and the target sees the caller's real Pod IP as the source.
  • Agents on a node reach every Pod on that node. kubelet's probes and any process on the host can connect to local Pod IPs. That is how liveness and readiness probes work.

Kubernetes defines these rules but does not implement them. The CNI plugin (Calico in this lab; Cilium, Flannel and others elsewhere) gives Pods their addresses and routes. kube-proxy (or a CNI that replaces it) implements Services on top of that flat Pod network. Keep the two jobs separate in your head: Pod-to-Pod is the CNI, Pod-to-Service is kube-proxy.

Two address ranges: Pod CIDR and Service CIDR

RangeWho uses itWhere it is configuredThis lab
Pod CIDR (cluster CIDR)Pod IPs, handed out by the CNI's IPAM--cluster-cidr on kube-controller-manager (kubeadm: networking.podSubnet); the CNI's own pool must match192.168.0.0/16
Node podCIDRA per-node slice of the Pod CIDRspec.podCIDR on each Node, set by the controller manager when --allocate-node-cidrs=true192.168.0.0/24, 192.168.1.0/24, …
Service CIDRClusterIPs of Services; never on any interface--service-cluster-ip-range on kube-apiserver (kubeadm: networking.serviceSubnet); visible as a ServiceCIDR object10.96.0.0/16
Read both ranges from the control planebash
# Node shell. Lab: docker exec. Exam: ssh <control-plane-node>, then sudo -i if needed.
docker exec cka-control-plane grep -- --cluster-cidr /etc/kubernetes/manifests/kube-controller-manager.yaml
docker exec cka-control-plane grep -- --service-cluster-ip-range /etc/kubernetes/manifests/kube-apiserver.yaml

# The same flags from the API, no node shell needed (static Pods are mirrored as Pods)
kubectl -n kube-system get pod -l component=kube-apiserver -o yaml | grep service-cluster-ip-range
kubectl -n kube-system get pod -l component=kube-controller-manager -o yaml | grep cluster-cidr

# Other places the values show up
kubectl get servicecidrs                                   # networking.k8s.io/v1 ServiceCIDR
kubectl -n kube-system get cm kubeadm-config -o yaml | grep -i subnet
kubectl get nodes -o custom-columns=NAME:.metadata.name,PODCIDR:.spec.podCIDR

Look closely at the lab and you will see a trap. The nodes carry podCIDRs like 192.168.1.0/24, yet the Pods on cka-worker have addresses like 192.168.220.43. Calico runs its own IPAM: it carves its IP pool (192.168.0.0/16 here) into /26 blocks and hands them to nodes as needed, ignoring spec.podCIDR. Simpler plugins such as Flannel or the host-local IPAM do use the node's podCIDR. When a question asks for "the Pod network CIDR of the cluster", it means the cluster-wide range, not one node's slice.

How a CNI plugin wires a Pod

  1. The scheduler binds the Pod to a node. kubelet asks the container runtime (containerd, through CRI) to create the Pod sandbox, which owns a fresh network namespace.
  2. The runtime reads the first config file in /etc/cni/net.d/ (lexical order) and runs the plugin binary named in it from /opt/cni/bin/ with the ADD command, passing the namespace path and Pod identity.
  3. The plugin creates a veth pair: one end becomes eth0 inside the Pod, the other stays on the host (cali… interfaces for Calico; with the bridge plugin it is attached to a Linux bridge such as cni0).
  4. The IPAM plugin (calico-ipam, host-local, …) picks a free address, and the plugin sets the Pod's IP, default route and a host route pointing at the veth.
  5. For other nodes, the plugin programs routes to their Pod blocks: plain routes, BGP, or an overlay (Calico IP-in-IP here, VXLAN in many setups). When the Pod is deleted, the runtime calls DEL and the address goes back to the pool.
See it on a nodebash
docker exec -it cka-worker bash          # exam: ssh <node>
ls /etc/cni/net.d/                       # 10-calico.conflist  calico-kubeconfig
cat /etc/cni/net.d/10-calico.conflist    # "type": "calico", "ipam": {"type": "calico-ipam"}, plus portmap
ls /opt/cni/bin/                         # calico calico-ipam host-local loopback portmap ...
ip -br link | grep cali                  # host ends of the veth pairs, one per local Pod
ip route                                 # one /32 per local Pod, one route per remote node's block
#   192.168.220.43 dev caliad73dda8ffa scope link
#   192.168.245.0/26 via 172.19.0.3 dev tunl0 onlink

kube-proxy: the Service layer

ModeHow Services are implementedStatus
iptablesNAT rules in the KUBE-SERVICES chains pick a backend at random per connectionThe default on Linux and what this lab runs
nftablesThe same idea on the nftables API, faster with many ServicesGA since v1.33; the modern choice
ipvsKernel IPVS virtual servers, with several scheduling algorithmsDeprecated; avoid for new clusters
bash
kubectl -n kube-system get ds kube-proxy
kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep -E '^(mode|clusterCIDR)'
# mode: iptables          (an empty mode also means iptables on Linux)

# The rules themselves, on any node
docker exec cka-worker iptables -t nat -L KUBE-SERVICES -n | head

Find a Pod's IP and node

bash
kubectl get pods -o wide                                     # IP and NODE columns
kubectl get pod web-0 -o jsonpath='{.status.podIP}{"\n"}'
kubectl get pod web-0 -o jsonpath='{.spec.nodeName}{"\n"}'
kubectl get pod web-0 -o jsonpath='{.status.podIPs[*].ip}{"\n"}'   # all IPs on dual-stack
kubectl get pods -l tier=cache -o custom-columns=NAME:.metadata.name,IP:.status.podIP,NODE:.spec.nodeName

# A Pod IP is not stable. Delete the Pod and its replacement gets a new one,
# which is the whole reason Services exist.

Prove connectivity with a client Pod

Interactive: the one-liner you will type on the exambash
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- wget -qO- -T 3 http://192.168.220.43:8080/hostname
# --rm deletes the Pod when the command exits; -T 3 stops wget waiting forever on a blocked port
Non-interactive: a long-lived client, then execbash
kubectl run client --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/client --timeout=60s
kubectl exec client -- wget -qO- -T 3 http://192.168.220.43:8080/hostname
kubectl exec client -- ip addr show eth0          # the client's own Pod IP
kubectl delete pod client --now

In the docs · the tab you get on exam day

Tasknw-cni-1
Networking3%~4 min

context · kubectl config use-context kind-cka

Record the Pod and Service address ranges

Find the address range this cluster allocates Pod IPs from, as configured on the control plane, and write it to /tmp/cka/pod-cidr.txt. Find the range Service ClusterIPs are allocated from and write it to /tmp/cka/service-cidr.txt. Each file must contain only the CIDR.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-cni-1/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-cni-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-cni-1/cleanup | bash
Tasknw-cni-2
Networking4%~5 min

context · kubectl config use-context kind-cka

Locate a Pod and reach it by IP

Several Pods run in namespace nw-cni. For the Pod labeled tier=cache: write its Pod IP to /tmp/cka/cache-ip.txt and the name of the node it runs on to /tmp/cka/cache-node.txt. Then, from a temporary Pod using image busybox:1.36 in the same namespace, request http://<that IP>:8080/hostname and save the response body to /tmp/cka/cache-hostname.txt. Remove the temporary Pod afterwards.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-cni-2/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-cni-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-cni-2/cleanup | bash

Finished reading?

0 of 2 tasks passed. Readiness counts tasks, so run them before you move on.