← All Posts

IP Planning for Kubernetes: Pod CIDRs, Service CIDRs, and the Day You Run Out of 10/8

For decades, exhausting the 10.0.0.0/8 private range was a problem reserved for enormous enterprises, since the block holds 16.7 million addresses and most companies own a few thousand devices. Then container platforms arrived, and mid-size companies started running out. The culprit is Kubernetes, the open-source system (often shortened to K8s) that runs applications as fleets of containers, lightweight packaged copies of an app that start in seconds and multiply on demand. Each new cluster quietly claims an address block the size of a large corporation's entire network, nobody writes the claim down, and a few years later the address plan is full of invisible tenants.

The hunger comes from a deliberate design choice. Kubernetes groups containers into units called pods, and the platform's networking model gives every pod its own real IP (Internet Protocol) address, with all pods able to reach each other directly. A busy cluster runs thousands of pods, and they churn constantly, since every deploy, scale-up, and restart destroys old pods and creates new ones that draw fresh addresses. To express how much space that needs, Kubernetes configuration leans entirely on CIDR (Classless Inter-Domain Routing) notation, the slash numbers like /16 and /24 that describe block sizes, and our visual guide to CIDR notation is the right primer if those numbers read as noise.

Every cluster actually consumes three separate kinds of address space, and they have very different visibility to the rest of your network. The node CIDR covers the actual machines, virtual or physical, that the cluster runs on; these live in an ordinary subnet, often inside a cloud VPC (Virtual Private Cloud, a private network you define inside a cloud provider). The pod CIDR is the big one, the block that pods draw their addresses from. The service CIDR holds virtual addresses, one per service, that act as stable front doors for groups of interchangeable pods, so that other software has a fixed address to call even as the pods behind it churn.

PlaneWhat lives thereTypical sizeVisible outside the cluster?
Node CIDRThe cluster's machines/24 to /22Yes, ordinary subnet
Pod CIDREvery running pod/16 by habitSometimes, and that's the trap
Service CIDRVirtual front-door addresses/20 or largerNo, but it shadows what it overlaps

The pod CIDR's size follows from per-node arithmetic. Kubernetes caps each node at 110 pods by default, and the common pattern hands every node its own /24, 256 addresses, so there is headroom for churn, with old addresses lingering briefly while new pods claim fresh ones. A cluster that might someday grow to 100 nodes therefore needs at least 100 /24s, which is a /17, and since round numbers are comfortable, tooling and tutorials default to a full /16: 65,536 addresses per cluster. The defaults themselves are worth knowing because they show up everywhere. Flannel, a popular cluster network plugin, defaults to 10.244.0.0/16; Calico, another, defaults to 192.168.0.0/16, which overlaps roughly every home router on earth, a fact that surfaces the first time an engineer working from home connects to the cluster over a VPN (Virtual Private Network, an encrypted tunnel into the company network). The kubeadm installer defaults the service CIDR to 10.96.0.0/12, which is over a million addresses for what is usually a few hundred services.

Service CIDRs deserve one extra beat of explanation, because they are the strangest of the three. A service address exists only as a forwarding rule inside the cluster's own machinery; no network interface anywhere actually holds it, and no router outside the cluster ever learns a route to it. That means the block can be modest, since a /20 covers about 4,000 services, and it never directly collides with outside networks. The danger runs the other direction: from inside the cluster, the service CIDR shadows whatever real network shares its range. If your service CIDR overlaps the subnet where the corporate database lives, pods asking for the database's address get the cluster's forwarding rules instead, and the connection dies somewhere unhelpful.

All of this stays invisible right up until the cluster touches another network, which is why the failure has such a long fuse. On day one the cluster is an island, the overlapping addresses bother nobody, and the team ships features. Months later someone connects the cluster to the office over a VPN, or peers its VPC with another VPC, or adds a path to the data center, and a router somewhere now faces the same impossible question we walked through in our guide to merging networks after an acquisition: one routing table, one entry per destination, two networks claiming 10.244.0.0/16. The symptom is unforgettable once you have seen it: the cluster can reach the entire world except one specific network, because traffic to those addresses never leaves the pod network that claims them locally.

Now multiply by how clusters actually breed. Nobody runs one: there is production, staging, and development, then a second region, then the data science team's cluster, and platform teams routinely tend six or more. At a /16 apiece, six clusters consume 393,216 addresses, and if each also claims a service CIDR and a node subnet, the total climbs further. The 10.0.0.0/8 block contains only 256 /16s, and a typical enterprise plan has already carved most of them into regional and departmental allocations, the aggregation structure from our worked example of designing an IP plan from scratch. Clusters did to private address space what nothing else managed in thirty years: they made 10/8 feel small.

The escape hatch is a block most networks have never used: 100.64.0.0/10, four million addresses set aside for CGNAT (carrier-grade NAT, the large-scale address translation that internet providers run), one of the special-purpose blocks from our tour of the 10 reserved IP ranges every engineer should know. Pod addresses are island addresses; your office never needs a route to an individual pod, since traffic enters clusters through load balancers and gateways with ordinary addresses. That makes pod and service CIDRs ideal tenants for borrowed space, and the major cloud providers explicitly support running cluster networks in this range. Moving pods to CGNAT space hands the /16s back to networks with actual devices. Two cautions before you standardize on it: confirm nothing in your path already uses the range, since some VPN services and carrier equipment legitimately do, and treat the borrowed block as one more allocation to plan, because the same range is also the favorite neutral ground for merger NAT seams and other translation tricks, and two clever uses of 100.64.0.0/10 can collide with each other just as painfully as two clusters on 10.244.0.0/16.

Which points at the real discipline, the one that costs nothing and prevents all of it: treat every cluster's CIDRs as first-class allocations in the company address plan, even though no inventory scan will ever see them. The pod CIDR has no devices you can audit, no DHCP (Dynamic Host Configuration Protocol) pool to watch, and no switch port to trace, so it exists only as a paper claim, and a claim nobody wrote down is a claim the next engineer cannot honor. Carve a dedicated parent block for cluster infrastructure, size each cluster's slice from the node math above rather than from a tutorial's default, and grant it the same growth headroom you would any subnet, the runway thinking from our guide to subnet utilization and capacity planning. Our free subnet splitter will carve a parent block into equal cluster-size slices in a few clicks if you want to sketch the layout first.

Cluster address space as part of the documented hierarchy

10.240.0.0/12  Kubernetes clusters  (parent block, reserved)
├─ 10.240.0.0/16   prod-us-east   pod CIDR    env: production
├─ 10.241.0.0/16   prod-eu-west   pod CIDR    env: production
├─ 10.242.0.0/16   staging        pod CIDR    env: staging
├─ 10.243.0.0/20   prod-us-east   service CIDR
├─ 10.243.16.0/20  prod-eu-west   service CIDR
└─ 10.244.0.0/16   ← next cluster takes this, and nothing collides

No pods are tracked individually. The block is the record.

This is exactly the shape IPCraft is built around. Subnets nest in a hierarchy, so you create the parent block once and each cluster's pod and service CIDRs become children inside it, with parent-child relationships resolved automatically as you add them. Custom fields carry the cluster name, environment, and owning team on each block, and because the record is the block rather than its 65,536 addresses, documenting a cluster takes about a minute. The payoff arrives every time someone provisions cluster number seven: instead of copying a tutorial default that is already in use twice, they look at the tree, see what is taken, and take the next free slice. Provisioning pipelines can do the same without a human, registering the new cluster's CIDRs through the API in the same Terraform run that creates the cluster, so the documentation and the infrastructure are born together and never drift. For the broader picture of tracking address space across cloud accounts and on-premises networks in one place, our guide to multi-cloud and hybrid IPAM picks up where this one stops.

The long-term exit is the same one that keeps appearing at the end of these guides. Kubernetes runs dual-stack today, and in IPv6 (Internet Protocol version 6, the successor with an address space large beyond intuition) a single standard subnet holds more addresses than every pod that will ever run, everywhere, combined, a scale our guide to IPv6 address planning tries to make graspable. Until your clusters live there, the recipe stands: size pod CIDRs from node count and pod density instead of habit, keep service CIDRs modest and clear of anything pods must reach, move pod space into 100.64.0.0/10 when 10/8 gets tight, and write every cluster's blocks into the same plan as everything else, because an address claim that exists only in a YAML file is an outage with a long fuse.