The most surprising thing about CNI plugins for containerd is how little they actually do to "network" your containers.
Let’s see it in action. Imagine you have a simple Kubernetes cluster with containerd as your container runtime. You’ve just deployed a pod, and you want to see how it gets an IP address and can talk to other pods.
# First, let's get the pod's IP address.
kubectl get pod my-nginx-pod -o wide
# Output might look like:
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# my-nginx-pod 1/1 Running 0 1m 10.244.1.5 worker-01 <none> <none>
# Now, let's exec into the pod and see its network interfaces.
kubectl exec -it my-nginx-pod -- ip addr show eth0
# Output will show an IP address assigned by your CNI plugin.
# 2: eth0@if123: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default cgroup
# link/ether 2a:63:8c:61:8a:13 brd ff:ff:ff:ff:ff:ff link-netns 123
# inet 10.244.1.5/24 brd 10.244.1.255 scope global eth0
# valid_lft forever preferred_lft forever
# Let's try pinging another pod.
kubectl exec -it my-nginx-pod -- ping -c 3 10.244.2.10
# If successful, you'll see ping replies.
This all happens because of a small, unassuming binary that containerd invokes. When containerd needs to set up network connectivity for a new container (like when Kubernetes creates a pod), it looks for a configuration file, typically in /etc/cni/net.d/. This file is usually JSON and describes a list of CNI plugins to use.
Here’s a snippet of what that config might look like for Calico:
{
"cniVersion": "0.3.1",
"name": "k8s-pod-network",
"plugins": [
{
"type": "calico",
"logFile": "/var/log/calico/cni.log",
"logSeverity": "Info",
"datastoreType": "kubernetes",
"nodename": "worker-01",
"mtu": 1450,
"ipam": {
"type": "calico-ipam"
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
When containerd sees this, it executes the binaries specified by the "type" field. For Calico, it would be something like /opt/cni/bin/calico. This binary then takes the environment variables and arguments provided by containerd (which ultimately come from Kubernetes) and performs its magic.
The "magic" is usually two-fold:
- IP Address Management (IPAM): The
ipamsection in the CNI config, or a separateipamplugin, is responsible for assigning an IP address to the container’s network interface. Calico’scalico-ipamplugin, for instance, talks to the Calico control plane (often via the Kubernetes API ifdatastoreTypeiskubernetes) to get an IP from its allocated pool. - Network Configuration: Once an IP is assigned, the CNI plugin configures the container’s network namespace. This often involves creating a virtual Ethernet pair (veth pair), moving one end into the container’s network namespace as
eth0, and configuring the other end in the host’s network namespace. It also sets up routing rules and potentially network policies.
The portmap plugin in the example is a common post-configuration step. It allows for hostPort mappings by translating traffic destined for a specific host port to the correct pod IP and port.
The CNI specification is deliberately minimal. It defines a standard interface for network plugins to interact with container runtimes. The actual networking (routing, policies, encapsulation, etc.) is left to the individual CNI plugin implementations like Calico, Flannel, Cilium, or Weave Net. Containerd itself doesn’t "do" networking; it just orchestrates the execution of these CNI plugins.
Most CNI plugins, when they create the network interface for your container, also set up the necessary routes on the host to ensure that traffic destined for other pods on different nodes is correctly forwarded. This might involve BGP peering with your network infrastructure (like Calico often does), or it could involve encapsulating pod traffic in VXLAN or IP-in-IP tunnels (like Flannel often does). The CNI plugin is responsible for installing these host-level routes and potentially configuring the overlay network.
When you’re troubleshooting connectivity, remember that the CNI plugin is a set of binaries and a configuration file. The actual network policies and IP address pools are managed by the CNI’s control plane (e.g., Calico’s calico-node daemonset or Flannel’s kube-flannel daemonset). If you see IP addresses being assigned but pods can’t talk to each other, you’re likely looking at a routing problem on the host or a network policy enforcement issue within the CNI plugin itself.
The next thing you’ll likely grapple with is how to configure network policies to control traffic flow between pods.