Cilium doesn’t just assign IPs to pods; it can be told to manage IP address assignment per node, which is wild because most networking solutions just have a global pool.
Let’s see this in action. Imagine we have two nodes, node-1 and node-2, and we want node-1 to exclusively use IPs from 10.0.1.0/24 for its pods, and node-2 from 10.0.2.0/24. This is useful for isolating traffic, ensuring predictable IP assignment, or integrating with external systems that rely on IP ranges.
Here’s how you’d set it up. First, you need to tell Cilium about these per-node IP pools. This is done via the CiliumNodeConfig custom resource.
apiVersion: cilium.io/v2alpha1
kind: CiliumNodeConfig
metadata:
name: node-1-ipam-config
spec:
nodeSelector:
matchLabels:
kubernetes.io/hostname: node-1
ipam:
operator:
clusterPoolIPv4PodCIDRs:
- 10.0.1.0/24
And for node-2:
apiVersion: cilium.io/v2alpha1
kind: CiliumNodeConfig
metadata:
name: node-2-ipam-config
spec:
nodeSelector:
matchLabels:
kubernetes.io/hostname: node-2
ipam:
operator:
clusterPoolIPv4PodCIDRs:
- 10.0.2.0/24
Apply these manifests:
kubectl apply -f node-1-ipam-config.yaml
kubectl apply -f node-2-ipam-config.yaml
Once applied, when a pod is scheduled onto node-1, Cilium’s IPAM operator will pull an IP for it from the 10.0.1.0/24 range. Similarly, pods on node-2 will get IPs from 10.0.2.0/24. You can verify this by inspecting the CiliumNode status:
kubectl get ciliumnode node-1 -o yaml
You’ll see an ipam.podCIDRs field populated with the assigned range for that node.
The core problem this solves is managing IP exhaustion and providing granular control over IP allocation in large or complex clusters. Without per-node IPAM, a single busy node could deplete the global IP pool, starving other nodes. By segmenting the pool per node, you ensure that each node has a dedicated set of IPs, preventing one node from negatively impacting the others. The CiliumNodeConfig acts as a declarative way to inform the Cilium agent on each node about its specific IPAM configuration. The nodeSelector is key here, ensuring the configuration is applied to the intended nodes.
The ipam.operator.clusterPoolIPv4PodCIDRs field is where the magic happens. This tells the Cilium IPAM operator which CIDR(s) to use for assigning pod IPs on the selected node. Cilium internally maintains a pool of available IPs within these CIDRs for each node. When a pod needs an IP, the agent on that node requests one from its local, node-specific pool.
This mechanism is deeply integrated with Kubernetes scheduling. When a pod is scheduled to a node, the Cilium agent on that node becomes aware of it and initiates the IP allocation process from its designated CIDR. This is why you see the ipam.podCIDRs field update on the CiliumNode object – it reflects the currently allocated and available IP ranges managed by Cilium for that specific node.
It’s also important to understand how this interacts with the broader cluster networking. If you’re using BGP or other external routing mechanisms, you can leverage these per-node CIDRs to advertise specific IP ranges for specific nodes, making routing more predictable and manageable. The operator essentially acts as a local IP address manager for each node, but it’s still managed and coordinated by the central Cilium control plane.
The surprising part is how Cilium’s IPAM operator dynamically manages these per-node CIDRs. It doesn’t just assign a static block and forget it; it actively monitors the CIDR utilization on each node and can even dynamically adjust or re-allocate CIDRs if necessary (though this is a more advanced topic). The operator’s job is to ensure that the CiliumNode object accurately reflects the IPAM state for that node, and that the Cilium agent has the necessary information to assign IPs correctly.
The next step is understanding how to configure IPv6 per-node IPAM or how to integrate these per-node CIDRs with advanced routing protocols.