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.
| Bereich | Wofür | Typischer Standard | Sichtbar über |
|---|---|---|---|
Pod-CIDRclusterNetwork | Adressen aller Pods, in Blöcke pro Node zerlegt | 10.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-CIDRserviceNetwork | ClusterIPs, rein virtuell, existiert nur in Regelwerken | 172.30.0.0/16 (OpenShift)10.43.0.0/16 (k3s/RKE2)10.96.0.0/12 (kubeadm) | kubectl get svc, ServiceCIDR-Objekt |
| Node-Netz | echte Adressen der Maschinen | aus dem Firmennetz vergeben | ip addr auf dem Node |
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:
| Feld | Standard | Wofür |
|---|---|---|
internalJoinSubnet | 100.64.0.0/16 | Verbindung zwischen den logischen Routern pro Node |
internalTransitSwitchSubnet | 100.88.0.0/16 | Transit-Switch zwischen den Nodes (Interconnect) |
internalMasqueradeSubnet | 169.254.0.0/17 | Masquerade 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.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
Die spezifischere Route gewinnt, immer. Ein /16 schlägt ein /0, die Default-Route kommt gar nicht erst zum Zug.
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,
curlläuft in den Timeout,tracerouteendet auf dem eigenen Node oder in einem fremden Pod. - Ein Ziel antwortet vom Host aus (
sshauf den Node, danncurl), 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
ip route get auf dem Node oft unauffällig aus, obwohl der logische Router das Paket längst ins Overlay schickt.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.
- Beim Netzwerkteam einen Block reservieren lassenNicht nur „wir nehmen 10.42“, sondern eine dokumentierte Reservierung im IPAM der Organisation. Ein
/14aus RFC 1918 kostet nichts außer Adressraum, den niemand sonst nutzt. - Großzügig dimensionierenDie Rechnung geht über
hostPrefix: BeihostPrefix: 23bekommt jeder Node 510 nutzbare Pod-Adressen, ein/14trägt dann 512 Nodes. Wer knapp plant, landet später genau bei diesem Problem. - 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
| Bereich | Wer ihn belegt |
|---|---|
172.17.0.0/16 | Docker-Bridge, auf vielen Hosts vorhanden |
169.254.0.0/16 | Link-Local, Cloud-Metadata-Dienste |
100.64.0.0/10 | CGNAT, Tailscale, manche VPNs – kollidiert mit den OVN-internen Join- und Transit-Netzen |
10.0.0.0/8 | Cilium-Default-Pool, belegt den kompletten 10er-Bereich |
| VPN-Pools | Adressen, die VPN-Clients zugewiesen bekommen |
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
| Vorhaben | Möglich? |
|---|---|
| Pod-CIDR vergrößern (gleicher Netzanteil) | ja, unterstützt |
| Pod-CIDR auf anderen Netzanteil verschieben | nein |
hostPrefix ändern | nein |
Zweiten, disjunkten clusterNetwork-Eintrag nachrüsten | nur bei der Installation |
| Calico-IPPool wechseln (bei Calico-IPAM) | bedingt – Pool soll innerhalb der Cluster-CIDR liegen |
OVN internalJoinSubnet / internalTransitSwitchSubnet ändern | ja, nachträglich |
| Service-CIDR erweitern (Kubernetes ≥ 1.33) | ja, additiv |
| Service-CIDR ersetzen | nein |
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}'
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.
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.
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
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
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
Die drei Fälle
- Ausgehend: meist schon gelöst. OVN-Kubernetes, Flannel und Calico mit
natOutgoingSNATen 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 odernatOutgoing: 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.
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“.
- Neuen Cluster aufbauenMit sauberen, reservierten Bereichen.
- Workloads per GitOps ausrollenBei sauberer Trennung von Konfiguration und Cluster ist das der günstigste Schritt.
- Persistente Daten migrierenBackup/Restore oder Storage-Replikation.
- UmschaltenÜber DNS bzw. Loadbalancer; alten Cluster als Rückfallebene behalten.
- Erst nach einer Bewährungszeit abbauen
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
hostPrefixpasst zur geplanten Node-Zahl, mit Reserve- LoadBalancer-Pool und Egress-Adressen sind separat geplant
172.17.0.0/16und169.254.0.0/16sind 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