Alle Rezepte
Kubernetes 1.34 · OpenShift 4.18–4.22 · Calico 3.32

Überlappende IP-Bereiche:
wenn Pod-Netz und Firmennetz kollidieren

Ein Cluster bringt eigene IP-Bereiche mit, die nirgendwo angemeldet werden. Sind sie doppelt vergeben, entstehen Fehler, die wie DNS-, Firewall- oder Anwendungsprobleme aussehen, aber keine sind. Acht Abschnitte zu Ursache, Symptomen, Planung und den Handlungsmöglichkeiten danach.

0Drei AdressräumePod, Service, Node 1Warum es brichtLongest Prefix Match 2SymptomeGenau ein Ziel tot 3PlanungVor der Installation 4Was änderbar istVergrößern ja, verschieben nein 5VersteckenSNAT, LoadBalancer, Proxy 6NeubauDer Referenzweg 7ChecklisteVor jeder Installation
0

Die drei Adressräume eines Clusters

Jeder Kubernetes-Cluster verwaltet drei voneinander unabhängige Bereiche. Alle drei müssen untereinander und gegenüber jedem Netz, das der Cluster erreichen soll, überschneidungsfrei sein.

BereichWofürTypischer StandardSichtbar über
Pod-CIDR
clusterNetwork
Adressen aller Pods, in Blöcke pro Node zerlegt10.128.0.0/14 (OpenShift)
10.42.0.0/16 (k3s/RKE2)
10.244.0.0/16 (Flannel)
192.168.0.0/16 (Calico-Standardmanifest)
10.0.0.0/8 (Cilium cluster-pool)
node.spec.podCIDR (kubeadm, Flannel, k3s/RKE2)
Annotation k8s.ovn.org/node-subnets (OVN-K/OpenShift)
IPAMBlock (Calico-IPAM)
CiliumNode (Cilium cluster-pool)
Service-CIDR
serviceNetwork
ClusterIPs, rein virtuell, existiert nur in Regelwerken172.30.0.0/16 (OpenShift)
10.43.0.0/16 (k3s/RKE2)
10.96.0.0/12 (kubeadm)
kubectl get svc, ServiceCIDR-Objekt
Node-Netzechte Adressen der Maschinenaus dem Firmennetz vergebenip addr auf dem Node
Cilium cluster-pool: 10.0.0.0/8. Der Default-Pool belegt den gesamten 10er-Bereich und ist damit der größte Kollisionskandidat überhaupt. Bei Cilium den Pool immer explizit setzen.

Die internen Netze von OVN-Kubernetes

OpenShift bringt mit OVN-Kubernetes drei weitere Bereiche mit, die in keiner Cluster-Übersicht auftauchen:

FeldStandardWofür
internalJoinSubnet100.64.0.0/16Verbindung zwischen den logischen Routern pro Node
internalTransitSwitchSubnet100.88.0.0/16Transit-Switch zwischen den Nodes (Interconnect)
internalMasqueradeSubnet169.254.0.0/17Masquerade für Host- und Service-Verkehr
100.64.0.0/10 ist in der Praxis belegt: CGNAT-Bereich, Tailscale, viele VPN-Pools. Kommen Clients oder Ziele aus diesem Bereich, kollidieren sie mit Join- oder Transit-Subnetz.
Gern vergessen: der LoadBalancer-Pool (MetalLB, OpenShift Ingress), Egress-IPs, das Hybrid-Overlay-Netz für Windows-Nodes sowie Adressen, die eine Storage- oder Backup-Appliance mitbringt.
Der Cluster kennt drei Adressräume, die Organisation kennt nur einen davon – nämlich den, in dem die Nodes stehen.
1

Warum eine Überlappung bricht: Longest Prefix Match

Wenn der CNI startet, trägt er auf jedem Node Routen für das Pod-Netz ein. Ab diesem Moment gibt es für jede Zieladresse im Pod-Netz zwei konkurrierende Einträge: die spezifische Route ins Overlay und die Default-Route ins Firmennetz.

Die Entscheidung des Kernels

Pod schickt Paket Ziel 10.42.7.13 Routing-Tabelle auf dem Node 10.42.0.0/16 → cni0 längster Präfix gewinnt Overlay im Cluster Paket landet hier 0.0.0.0/0 → Gateway wird nie geprüft DB 10.42.7.13 in der Firma unerreichbar Der Kernel fragt nicht „intern oder extern“, sondern nur nach der Präfixlänge.

Die spezifischere Route gewinnt, immer. Ein /16 schlägt ein /0, die Default-Route kommt gar nicht erst zum Zug.

Ein Hotel nummeriert seine Zimmer intern von 1 bis 200 – in einer Straße, in der es die Hausnummern 1 bis 200 ebenfalls gibt. Post an „Nummer 47“ landet garantiert im Hotelzimmer, nie beim Nachbarn. Der Portier fragt nicht nach.
Der Rückweg ist genauso betroffen: Antwortpakete aus dem Firmennetz an eine Adresse, die dort ebenfalls existiert, werden an den falschen Host zugestellt. Ergebnis ist asymmetrisches Routing – Verbindungen bauen sich scheinbar auf und brechen dann ab.
Überlappung mit der Service-CIDR bricht anders. ClusterIPs werden nicht geroutet, sondern schon vorher behandelt: kube-proxy (iptables/nftables) bzw. der OVN-Loadbalancer schreibt sie per DNAT auf einen Pod um oder verwirft bzw. rejectet Pakete an Adressen ohne passenden Service – je nach Implementierung. Die Routing-Tabelle kommt gar nicht erst zum Zug. Ein externes Ziel im Service-Bereich ist aus Pods deshalb unerreichbar, und statt eines Timeouts kommt oft ein sofortiges connection refused.
Der Kernel liest keine Absichten, nur Präfixlängen.
2

Woran man den Konflikt erkennt

Das Fehlerbild ist tückisch, weil alles außer dem Datenpfad funktioniert. Namensauflösung ist sauber, Firewall-Logs zeigen nichts, die Anwendung meldet nur ein Timeout.

Die Signatur

  • Genau ein externer Dienst ist nicht erreichbar, alle anderen laufen. Das ist die klassische Signatur.
  • DNS liefert die richtige IP, curl läuft in den Timeout, traceroute endet auf dem eigenen Node oder in einem fremden Pod.
  • Ein Ziel antwortet vom Host aus (ssh auf den Node, dann curl), aber nicht aus einem Pod heraus.
  • Verbindungen aus dem Firmennetz in den Cluster brechen nach dem Handshake ab.
  • Nach einem Node-Neustart oder einem CNI-Update ändert sich das Verhalten, ohne dass jemand etwas an der Anwendung geändert hat.

Die Prüfung in zwei Minuten

# Die Bereiche des Clusters auslesen
kubectl cluster-info dump | grep -m2 -E 'cluster-cidr|service-cluster-ip-range'
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'

# OpenShift – node.spec.podCIDR ist unter OVN-Kubernetes leer
oc get network.config/cluster -o jsonpath='{.spec.clusterNetwork}{"\n"}{.spec.serviceNetwork}{"\n"}'
oc get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.k8s\.ovn\.org/node-subnets}{"\n"}{end}'
oc get network.operator.openshift.io cluster -o jsonpath='{.spec.defaultNetwork.ovnKubernetesConfig}{"\n"}'

Welche Route greift – je nach CNI

Linux-Routing (Flannel, Calico, Canal): Die Host-Routingtabelle entscheidet. ip route get zeigt nicht die Tabelle, sondern die Entscheidung des Kernels für genau dieses Ziel.

ip route get 10.42.7.13

OVN-Kubernetes: Pod-Verkehr entscheidet der logische Router in OVN, nicht die Host-Routingtabelle. ip route get auf dem Node prüft hier das Falsche. Stattdessen den Weg simulieren oder direkt aus dem Pod testen:

# Paketweg aus einem Pod zu einem externen Ziel simulieren
./ovnkube-trace -src-namespace <ns> -src <pod> \
  -dst-ip 10.42.7.13 -tcp -dst-port 5432

# Echter Test aus dem Pod-Netzwerk-Namespace
oc rsh -n <ns> <pod> curl -v --connect-timeout 5 http://10.42.7.13:5432
Falsches Werkzeug, falsche Antwort: Unter OVN-Kubernetes sieht ip route get auf dem Node oft unauffällig aus, obwohl der logische Router das Paket längst ins Overlay schickt.
3

Planung: der Teil, der später nicht nachholbar ist

Die Pod-CIDR ist kein Konfigurationswert, sondern ein Fundament. Anbauen geht, versetzen nicht. Deshalb gehören vor jede Installation drei Schritte.

  1. Beim Netzwerkteam einen Block reservieren lassenNicht nur „wir nehmen 10.42“, sondern eine dokumentierte Reservierung im IPAM der Organisation. Ein /14 aus RFC 1918 kostet nichts außer Adressraum, den niemand sonst nutzt.
  2. Großzügig dimensionierenDie Rechnung geht über hostPrefix: Bei hostPrefix: 23 bekommt jeder Node 510 nutzbare Pod-Adressen, ein /14 trägt dann 512 Nodes. Wer knapp plant, landet später genau bei diesem Problem.
  3. Alle Netze aufschreiben, mit denen der Cluster jemals sprechen sollInklusive der Netze von Tochtergesellschaften, Partnern und geplanten Zukäufen. Genau dort entsteht die Überlappung typischerweise: nicht am ersten Tag, sondern wenn zwei Jahre später eine weitere Organisation angebunden wird.

Weniger offensichtliche Kollisionskandidaten

BereichWer ihn belegt
172.17.0.0/16Docker-Bridge, auf vielen Hosts vorhanden
169.254.0.0/16Link-Local, Cloud-Metadata-Dienste
100.64.0.0/10CGNAT, Tailscale, manche VPNs – kollidiert mit den OVN-internen Join- und Transit-Netzen
10.0.0.0/8Cilium-Default-Pool, belegt den kompletten 10er-Bereich
VPN-PoolsAdressen, die VPN-Clients zugewiesen bekommen
Die Pod-CIDR ist das Fundament, nicht die Wandfarbe.
4

Was sich im laufenden Cluster ändern lässt – und was nicht

Hier trennen sich die Plattformen deutlich. Der entscheidende Punkt: Ein Node, der einmal einen Block bekommen hat, behält ihn – egal, wo die Zuweisung steht. Bei kubeadm und Flannel ist das node.spec.podCIDR (unveränderlich bis zur Neuregistrierung), bei OVN-Kubernetes die Annotation k8s.ovn.org/node-subnets, bei Calico-IPAM die IPAM-Blöcke, bei Cilium das CiliumNode-Objekt.

Überblick

VorhabenMöglich?
Pod-CIDR vergrößern (gleicher Netzanteil)ja, unterstützt
Pod-CIDR auf anderen Netzanteil verschiebennein
hostPrefix ändernnein
Zweiten, disjunkten clusterNetwork-Eintrag nachrüstennur bei der Installation
Calico-IPPool wechseln (bei Calico-IPAM)bedingt – Pool soll innerhalb der Cluster-CIDR liegen
OVN internalJoinSubnet / internalTransitSwitchSubnet ändernja, nachträglich
Service-CIDR erweitern (Kubernetes ≥ 1.33)ja, additiv
Service-CIDR ersetzennein
Pod-CIDR vergrößern

Unterstützt, solange der Netzanteil gleich bleibt und nur die Präfixlänge kleiner wird. Aus 10.128.0.0/16 darf 10.128.0.0/14 werden, aus 10.42.x niemals 172.20.x. hostPrefix bleibt dabei unverändert.

# OpenShift, OVN-Kubernetes: Erweiterung des bestehenden Bereichs
oc patch Network.config.openshift.io cluster --type=merge --patch \
  '{"spec":{"clusterNetwork":[{"cidr":"10.128.0.0/14","hostPrefix":23}],"networkType":"OVNKubernetes"}}'

oc get network.operator.openshift.io -o jsonpath='{.items[0].spec.clusterNetwork}'
Hilft gegen Adressknappheit, nicht gegen Überlappung. Der kollidierende Netzanteil bleibt ja erhalten.
Pod-CIDR verschieben

Nicht unterstützt. Kein Pfad, keine Dokumentation, kein Support.

Wer es hypothetisch durchspielt: Cluster-Network-Objekt und Controller-Manager-Flags ändern, jedes Node-Objekt löschen und neu registrieren lassen (weil die Subnetz-Zuweisung pro Node fest ist), CNI-State wegwerfen (OVN-Northbound-Datenbank bzw. Flannel-Subnet-Leases), Kubelets neu starten, sämtliche Pods neu erzeugen. Bei OVN-Kubernetes tragen zusätzlich die Logical Switches pro Node die alten Subnetze.

Das ist ein Vollausfall, kein Rolling Update – und im Fehlerfall steht man ohne getesteten Rückweg da.
Sonderfall Calico-IPAM

Calico benutzt bei eigenem IPAM nicht node.spec.podCIDR, sondern eigene IPPool-Objekte. Damit gibt es einen dokumentierten Migrationspfad: neuen Pool anlegen, alten Pool auf disabled: true setzen, Pods rollierend neu starten. Jeder neu erzeugte Pod bekommt eine Adresse aus dem neuen Pool.

Verbindungen bleiben nicht unberührt: Beim rollierenden Neustart verliert jeder neu gestartete Pod seine Adresse und damit alle offenen Verbindungen. Anwendungen müssen Reconnects vertragen.
calicoctl get ippools -o wide
calicoctl apply -f neuer-pool.yaml
calicoctl patch ippool alter-pool -p '{"spec":{"disabled":true}}'
# danach rollierender Neustart aller Workloads
Einschränkung laut Calico-Doku: Kubernetes erwartet, dass alle Pod-Adressen innerhalb der Cluster-CIDR liegen. Pools außerhalb davon sind technisch möglich, aber nicht empfohlen – Pods darin verlieren ihre Anbindung. Gegen Überlappung hilft der Weg damit nur bedingt: Liegt die Kollision in der Cluster-CIDR selbst, führt der empfohlene Pfad nicht aus ihr heraus. Für OpenShift scheidet dieser Weg ohnehin aus, da die Plattform die Änderung der Pod-Netz-CIDR nicht vorsieht.
Positives Gegenbeispiel: OVN-interne Netze

Anders als die Pod-CIDR lassen sich Join- und Transit-Subnetz von OVN-Kubernetes im laufenden Cluster ändern. Kollidiert etwa 100.64.0.0/16 mit CGNAT oder Tailscale, ist das ein lösbares Problem:

# Join-Subnetz
oc patch networks.operator.openshift.io cluster --type=merge -p \
  '{"spec":{"defaultNetwork":{"ovnKubernetesConfig":{"ipv4":{"internalJoinSubnet":"100.99.0.0/16"}}}}}'

# Transit-Switch-Subnetz
oc patch networks.operator.openshift.io cluster --type=merge -p \
  '{"spec":{"defaultNetwork":{"ovnKubernetesConfig":{"ipv4":{"internalTransitSwitchSubnet":"100.98.0.0/16"}}}}}'

Die neuen Bereiche dürfen sich weder untereinander noch mit Pod-, Service- oder Node-Netz überschneiden. Der Cluster Network Operator rollt die Änderung auf alle Nodes aus – Wartungsfenster einplanen.

Service-CIDR

Seit Kubernetes 1.33 ist MultiCIDRServiceAllocator stabil und standardmäßig aktiv. Damit lässt sich ein zusätzlicher Bereich als ServiceCIDR-Objekt anlegen, wenn die Adressen knapp werden.

Der ursprüngliche Bereich – das Objekt kubernetes – lässt sich dadurch nicht ersetzen. Er hängt am --service-cluster-ip-range des API-Servers und an der ClusterIP des kubernetes-Service selbst.

kubectl get servicecidr
5

Wenn die Überlappung schon existiert: verstecken statt ändern

Der pragmatische Weg ist fast immer, die Pod-Adressen nach außen gar nicht erst sichtbar werden zu lassen. Welcher Weg trägt, hängt davon ab, wer wen erreichen muss.

Die Entscheidung

Überlappung festgestellt Wer muss wen erreichen? Cluster → außen SNAT auf Node-IP meist schon Standard außen → Cluster LoadBalancer-IP aus neutralem Pool Ziel liegt selbst im Überlappungsbereich Proxy-Hop mit DNAT oder Neubau trägt für Nord-Süd-Verkehr aufwendig, fehleranfällig Solange die Gegenseite nie eine Pod-IP sieht, stört die Überlappung nicht.

Die drei Fälle

  • Ausgehend: meist schon gelöst. OVN-Kubernetes, Flannel und Calico mit natOutgoing SNATen Pod-Egress standardmäßig auf die Node-IP – die Gegenseite sieht nie eine Pod-IP. Ein Problem gibt es nur ohne SNAT, also bei per BGP annoncierten Pod-Netzen oder natOutgoing: false. Dann SNAT (wieder) aktivieren oder den Verkehr über ein Egress-Gateway führen.
  • Eingehend: Dienste ausschließlich über LoadBalancer- oder Ingress-Adressen aus einem neutralen Pool veröffentlichen.
  • Der harte Fall: Müssen aus dem Cluster heraus genau die überlappenden Zieladressen erreicht werden, hilft kein SNAT. Dann bleibt ein Proxy-Hop mit Destination-NAT auf einer Ersatzadresse – technisch machbar, betrieblich unschön, und jeder neue Dienst braucht wieder einen Eintrag.
Egress-IP ist kein Überlappungswerkzeug. Sie dient dem Firewall-Pinning – eine feste Absenderadresse pro Namespace, damit Firewall-Regeln greifen. Gegen Überlappung bringt sie gegenüber dem Standard-SNAT auf die Node-IP nichts.
SNAT versteckt, wo ein Paket herkommt. Gegen ein Ziel, das es zweimal gibt, hilft es nicht.
6

Der Neubau als Referenzweg

Ist die Überlappung nicht umgehbar, ist der Neubau daneben in der Praxis der schnellere und sicherere Weg. Der ehrliche Vergleich lautet nicht „Fundament tauschen gegen Neubau“, sondern „undokumentierter Eingriff mit Totalausfall gegen kontrollierten Umzug, bei dem der alte Cluster bis zum Umschalten weiterläuft“.

  1. Neuen Cluster aufbauenMit sauberen, reservierten Bereichen.
  2. Workloads per GitOps ausrollenBei sauberer Trennung von Konfiguration und Cluster ist das der günstigste Schritt.
  3. Persistente Daten migrierenBackup/Restore oder Storage-Replikation.
  4. UmschaltenÜber DNS bzw. Loadbalancer; alten Cluster als Rückfallebene behalten.
  5. Erst nach einer Bewährungszeit abbauen
Wer ohnehin GitOps betreibt, gewinnt mit diesem Weg fast immer.
7

Checkliste vor jeder Installation

Neun Punkte, die zehn Minuten kosten und einen Neubau ersparen.

  • Pod-CIDR, Service-CIDR und Node-Netz sind paarweise überschneidungsfrei
  • Beide Cluster-Bereiche sind im IPAM der Organisation reserviert und dokumentiert
  • Alle Netze, die der Cluster erreichen soll, sind gegengeprüft – inklusive VPN-Pools und Partnernetzen
  • hostPrefix passt zur geplanten Node-Zahl, mit Reserve
  • LoadBalancer-Pool und Egress-Adressen sind separat geplant
  • 172.17.0.0/16 und 169.254.0.0/16 sind geprüft
  • Bei OVN-Kubernetes: Join-, Transit- und Masquerade-Subnetz (100.64.0.0/16, 100.88.0.0/16, 169.254.0.0/17) gegen CGNAT, Tailscale und VPN-Pools geprüft
  • Bei Cilium: cluster-pool explizit gesetzt, nicht der Default 10.0.0.0/8
  • Bei mehreren Clustern: unterschiedliche Bereiche je Cluster, falls sie jemals direkt miteinander sprechen sollen