Zum Inhalt springen
← Blog

Der ipvs-Modus hat ein Löschdatum.

Kubernetes 1.37 bringt genau eine Abkündigung, und es ist der ipvs-Modus von kube-proxy. Standardmäßig aus in 1.40, Quellcode gelöscht in 1.43. Hier stehen der Wechsel auf nftables, die Kernel-Untergrenze, die vier Verhaltensänderungen und die Falle, die auch Cluster erwischt, die nie ipvs eingesetzt haben.

·24 Min. Lesezeit
  • Kubernetes
  • Netzwerk
  • Linux
  • nftables

Kubernetes v1.37 ist am 26. August 2026 mit 67 Enhancements erschienen, und genau eines davon ist eine Abkündigung – der ipvs-Modus von kube-proxy. Das ist die kleinere Schlagzeile als Gang Scheduling oder Pod-Zertifikate und der deutlich größere Betriebsposten, denn es ist das Einzige in diesem Release, an dessen Ende Code aus einer Datenebene gelöscht wird, die in diesem Moment produktiv läuft. Der Fahrplan steht bereits geschrieben: ein Feature Gate in 1.37, standardmäßig aus in 1.40, pkg/proxy/ipvs entfernt in 1.43.

Titelbild aus drei Feldern. Das linke Feld mit der Überschrift „mode: ipvs“ zeigt drei übereinanderliegende Schichten: IPVS-Virtual-Server, das Dummy-Interface kube-ipvs0 mit den ClusterIPs und darunter eine breitere Schicht mit der Beschriftung iptables und ipset – versehen mit einem roten Stempel „Quellcode gelöscht in 1.43“. Die mittlere Spalte ist ein Zeitstrahl mit fünf Stationen: 1.35 Warnung, 1.37 Feature Gate, 1.40 standardmäßig aus, 1.43 Code entfernt, 1.46 Gate entfernt. Das rechte Feld mit der Überschrift „mode: nftables“ zeigt zwei Kästen mit den Beschriftungen table ip kube-proxy und table ip6 kube-proxy über einer Zeile „benötigt Linux 5.13“, darunter ein grüner Balken mit der Aufschrift „der nächste Standard“.
Was tatsächlich in Rente geht: drei Schichten Kernel-Zustand, von denen nur die oberste IPVS ist – ersetzt durch zwei nftables-Tabellen und eine Kernel-Untergrenze.

Es gibt eine Fassung dieser Migration, die aus einer einzigen geänderten Zeile in einer ConfigMap besteht, und eine, die einen Dienstag ruiniert – und der Unterschied liegt vollständig darin, was Sie zuerst prüfen. Was folgt, ist das Ganze: die fünf KEP-Stufen und welche davon Termine statt Absichtserklärungen sind, wie Sie herausfinden, was Ihr Cluster wirklich fährt (einschließlich der Antwort, die schlimmer ist als ipvs), warum dieser Modus nie der Ausstieg aus iptables war, als den man ihn in Erinnerung hat, warum die IPVS-Scheduler nicht die Aufgabe erledigen, für die man sie behält, die Kernel-Untergrenze und welche Node-Images sie erreichen, die vier dokumentierten Verhaltensänderungen, ein Rollout pro Knoten, der Zustand, der von Hand aufgeräumt werden muss, ein Prüfskript mit einem Rückgabewert ungleich null und ein Rollback, das tatsächlich aus zwei Befehlen besteht.

Eine Abkündigung in 1.37 – und das ist sie

Keines der folgenden Fehlerbilder sagt Abkündigung, und genau deshalb fällt die Sache spät auf – meistens demjenigen, der das Upgrade auf 1.40 fährt, drei Releases nachdem die Warnung erstmals erschien. Der Modus verschlechtert sich nicht. Er funktioniert tadellos bis zu dem Release, in dem es ihn nicht mehr gibt, und die Warnungen dazwischen landen in einem Log, das niemand durchsucht.[rel137]

Was Sie sehenWas es tatsächlich bedeutetWo
kube-proxy protokolliert bei jedem Start The ipvs proxier is now deprecatedStufe 1 von KEP-5495, ausgeliefert in Kubernetes 1.35. Nichts ist kaputt; die Uhr läuft seit zwei Releases.Die fünf Stufen
mode: ist in der kube-proxy-ConfigMap leerDer Cluster hat keine Datenebene gewählt. Upstream wird den empfohlenen Standard von iptables auf nftables umstellen, und ein nicht festgelegter Cluster folgt dem.Welcher Modus
Nach dem Upgrade auf 1.40 beendet sich kube-proxy mit einem Fehler und listet die gültigen Modi aufStufe 3. Das Gate KubeProxyIPVS steht jetzt standardmäßig auf false, und niemand hat es zurückgesetzt.Die fünf Stufen
Ein NodePort antwortet direkt nach dem Wechsel auf einem zweiten Interface nicht mehrDer nftables-Modus nutzt standardmäßig --nodeport-addresses primary. Das ist die mit Abstand häufigste Regression dieser Migration.Was sich ändert
Verkehr zu einer ClusterIP wird verworfen, und nur auf einem KnotenVeraltete Adressen auf einem übrig gebliebenen kube-ipvs0 ohne IPVS-Regeln dahinter. Der Knoten beantwortet ARP für eine Adresse, die er nicht mehr bedient.Überbleibsel

Die zweite Zeile dieser Tabelle sollte man zweimal lesen, denn sie hat mit IPVS nichts zu tun und trifft Cluster, die es nie eingesetzt haben. Der Standardmodus in Kubernetes 1.37 ist weiterhin iptables, und die Dokumentation schreibt unmissverständlich, dass ein künftiges Release diesen Standard auf nftables umstellen wird. Wenn Ihre kube-proxy-Konfiguration keinen Modus nennt, dann sind Sie nicht aus Entscheidung auf iptables – Sie sind auf dem, was Upstream gerade empfiehlt, und der Austausch Ihrer Datenebene steht im Kalender von jemand anderem.[vips]

Die Empfehlung von Upstream selbst, und das Billigste in diesem ganzen Artikel: Damit das Proxy-Backend eines Clusters bei einem Upgrade nicht unerwartet gewechselt wird, sollten Sie sicherstellen, dass alle Cluster eine kube-proxy-Konfiguration haben, die den zu verwendenden Modus ausdrücklich angibt. Das ist eine Änderung von fünf Minuten, und sie lohnt sich heute, ganz gleich, auf welchem Modus Sie am Ende landen.

Die fünf Stufen, und welche zwei Termine sind

KEP-5495 gliedert das in fünf Stufen, und die nützliche Disziplin besteht darin, die Ankündigungen von den Terminen zu trennen. Stufe 1 und 2 sind bereits passiert und haben am Betrieb eines Clusters nichts geändert. Stufe 3 ändert einen Standardwert, ändert also, was mit einem Cluster geschieht, in dem niemand etwas getan hat. Stufe 4 entfernt den Code, und das ist die einzige unumkehrbare.[kep5495]

StufeKubernetesWas sich ändertWas das von Ihnen verlangt
11.35kube-proxy warnt im ipvs-Modus beim Start; die Dokumentation wird als veraltet gekennzeichnet. nftables-Fixes werden nach 1.33 und 1.34 zurückportiert, damit ältere Cluster noch migrieren können.Zur Kenntnis nehmen. Sonst nichts.
21.37Das Feature Gate KubeProxyIPVS kommt hinzu: GA, Standard true. Das Verhalten bleibt unverändert.Planen. Das ist das letzte ruhige Release.
31.40Das Gate steht standardmäßig auf false. Ein im ipvs-Modus gestartetes kube-proxy beendet sich ohne Übersteuerung mit einem Fehler und listet die gültigen Modi auf.Fertig sein – oder das Gate setzen und drei Minor-Releases kaufen.
41.43pkg/proxy/ipvs verschwindet aus dem Repository. Das Feature Gate kann es nicht mehr zurückholen; die restlichen Erwähnungen verlassen die Dokumentation.Nichts. Es gibt keinen ipvs-Modus mehr.
51.46Das Feature Gate selbst wird entfernt.Nur für Werkzeuge relevant, die Gates auswerten.

Zwei dieser Zeilen verdienen eine Anmerkung. Die Aufräumstufe in 1.46 steht im KEP und in so gut wie keiner Berichterstattung über diese Abkündigung, was nur zählt, wenn Sie Werkzeuge schreiben, die Feature Gates auswerten – bis dahin ist das Gate ein echtes, gelistetes GA-Gate. Und die Begründung, die SIG Network überhaupt dafür gegeben hat, sollte man kennen, denn es ist nicht die Performance: der Gruppe fehlen Maintainer, die mit dem Code des ipvs-Backends vertraut sind, und wer ipvs-Fehler meldet, wird seit geraumer Zeit auf nftables verwiesen. Ein Backend, das niemand repariert, ist eine Last, unabhängig davon, was seine Benchmarks einmal hergaben.[depol][k8srel]

In welchem Modus läuft dieser Cluster wirklich

Fangen Sie bei dem an, was tatsächlich konfiguriert ist, und nicht bei dem, was im Runbook steht – in jedem Cluster, der älter als etwa zwei Jahre ist, sind das zwei verschiedene Dokumente. Es gibt drei Antworten, und die dritte ist die interessante: ipvs, iptables oder gar nichts.[kpcfg]

# The one-liner from the v1.37 release announcement. On a kubeadm-built cluster
# the whole of kube-proxy's configuration lives in one ConfigMap key.
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep 'mode:'
# mode: ipvs

# Careful with the empty answer, because it is the most common one and it does
# NOT mean "iptables forever". An unset mode means "whatever kube-proxy decides
# is the recommended default", and upstream says in as many words that a future
# release will change that default from iptables to nftables. If this comes back
# blank, you have a data-plane change scheduled that nobody in your team chose.
#   mode: ""     <- pin it, whichever mode you intend to be on

# Managed clusters and non-kubeadm installers do not necessarily use that
# ConfigMap. Ask the process instead - this is true wherever the config came from.
kubectl -n kube-system get ds kube-proxy \
  -o jsonpath='{.spec.template.spec.containers[0].args}' | tr ',' '\n'

# And from the node, which is the only answer that cannot be out of date:
ps -o args= -C kube-proxy
ipvsadm -L -n --daemon 2>/dev/null; ipvsadm -L -n | head -20
# IP Virtual Server version 1.2.1 (size=4096)
# Prot LocalAddress:Port Scheduler Flags
# TCP  10.96.0.1:443 rr
#   -> 192.168.4.11:6443            Masq    1      3          0

# Not every cluster runs kube-proxy at all. If this returns nothing, your CNI
# is implementing Services itself and none of this deprecation reaches you.
kubectl -n kube-system get ds -l k8s-app=kube-proxy

Suchen Sie danach die Warnung, die seit Kubernetes 1.35 in Ihren Logs steht und der billigste Beleg dafür ist, dass Sie betroffen sind. Das in 1.37 hinzugekommene Feature Gate sollten Sie im selben Durchgang lokalisieren – nicht weil Sie es setzen müssten, denn es steht standardmäßig auf an und ändert in diesem Release nichts, sondern weil es der Hebel ist, der Ihnen in 1.40 angeboten wird, und weil man vorher wissen sollte, dass er die Frist nur um drei Minor-Releases verlängert, statt sie aufzuheben.[gates]

# Stage 1 of the deprecation landed in Kubernetes 1.35: kube-proxy logs a
# warning on every start in ipvs mode. It has been in your logs for two releases.
kubectl -n kube-system logs ds/kube-proxy --tail=-1 --prefix \
  | grep -i -m5 'ipvs.*deprecat'
# The ipvs proxier is now deprecated and may be removed in a future release.
# Please use 'nftables' instead.

# Stage 2 is what 1.37 added: a feature gate. Today it is on by default, so
# nothing changes yet. In 1.40 the default flips and kube-proxy in ipvs mode
# exits with an error unless the gate is set back by hand; in 1.43 the gate
# cannot save you because the code is gone.
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep -A3 featureGates

# What the 1.40 override would look like. Write this down as the thing you are
# choosing NOT to depend on, rather than as a plan:
#
#   featureGates:
#     KubeProxyIPVS: true
#
# It buys three minor releases, or roughly nine months at the current cadence,
# and it stops working entirely in 1.43.

# Count how much of the fleet this is really about, because in most clusters
# the answer is "some node pools, historically".
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,'\
'KERNEL:.status.nodeInfo.kernelVersion,OS:.status.nodeInfo.osImage'

Was der ipvs-Modus ist – und wofür er gehalten wird

Es lohnt sich, genau zu sagen, was hier in Rente geht, denn die populäre Fassung der Geschichte ist auf eine Weise falsch, die die Entscheidung verändert. Der ipvs-Modus kam in Kubernetes 1.8 hinzu, um die Kosten einer Regelliste loszuwerden, die proportional zur Zahl der Services wächst. Das hat er geleistet. Was er nie geleistet hat, ist, iptables zu ersetzen: die IPVS-API des Kernels kann für sich genommen die Service-API von Kubernetes nicht vollständig abbilden – Masquerade-Entscheidungen, NodePort-Filterung, LoadBalancer-Source-Ranges, das Reject-Verhalten für einen Service ohne Endpoints – und deshalb steuert der Modus unterhalb der IPVS-Tabelle zusätzlich iptables und ipset. Die Ankündigung zu v1.37 sagt genau das. Zählen Sie die Regeln auf Ihrem eigenen Knoten nach, wenn Sie es niemandem glauben wollen.[kep3866]

# Three separate pieces of kernel state, which is the first surprise for anyone
# who believed ipvs mode meant "no iptables".

# (a) The IPVS virtual servers. This part is what people think of as ipvs mode.
ipvsadm -L -n | wc -l

# (b) A dummy interface holding every ClusterIP - and every LoadBalancer IP -
#     as a /32 on the node. This is why the node answers ARP for addresses it
#     does not own, and therefore why MetalLB in layer-2 mode needs strictARP.
ip -brief addr show kube-ipvs0 | head
# kube-ipvs0  DOWN  10.96.0.1/32 10.96.0.10/32 10.107.44.9/32 ...

# (c) The iptables rules and ipsets that ipvs mode drives underneath, because
#     the kernel IPVS API on its own cannot express masquerade decisions,
#     LoadBalancer source ranges, NodePort filtering or the reject rules for a
#     Service with no endpoints. Count them before you claim to be iptables-free.
ipset list -name | grep -c '^KUBE-'
iptables-save -t nat | grep -c '^-A KUBE-'

# The scheduler in use, which is the setting this whole mode is usually kept for:
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep -A4 '^ipvs:'
# ipvs:
#   scheduler: "lc"
#   strictARP: true

# One field, for the whole cluster. There is no per-Service scheduler in
# Kubernetes: it is not in the Service API, and kube-proxy does not read one.
# Whatever is on that line is what every Service on every node gets.

Die zweite Hälfte der populären Fassung sind die Scheduler, und sie sind der Grund, warum die meisten Cluster, die noch auf ipvs laufen, noch darauf laufen. Irgendwer hat vor Jahren scheduler: "lc" gesetzt, und seither gilt der Cluster als einer, der nach Least Connections verteilt. Das tut er nicht, und der Grund ist architektonisch und kein Fehler. kube-proxy läuft auf jedem Knoten, und jede Instanz führt ihre eigene IPVS-Tabelle, die ausschließlich die Verbindungen zählt, die dieser Knoten geöffnet hat. „Wenigste Verbindungen“ wird also pro Client-Knoten berechnet: bei zwanzig ausgelasteten Knoten haben Sie zwanzig voneinander unabhängige lokale Entscheidungen und nicht eine globale, und Verkehr, der von außerhalb des Clusters kommt, taucht in keiner dieser Zählungen auf. SIG Network hat dieses Missverständnis beim Schreiben der Abkündigung ausdrücklich benannt.[vips]

# The claim to test: "we run lc so connections go to the least loaded pod".
#
# Every node runs its own kube-proxy with its own IPVS table, and that table
# only counts connections this node opened. Ask two nodes about the same
# Service and the connection counts will not agree - because they are answers
# to different questions.

SVC_IP=$(kubectl get svc -n prod api -o jsonpath='{.spec.clusterIP}')
for node in $(kubectl get nodes -o name | head -3); do
  echo "== ${node#node/}"
  kubectl debug "$node" -q -it --image=busybox --profile=general -- \
    chroot /host ipvsadm -L -n -t "$SVC_IP:8080" 2>/dev/null | tail -n +4
done
# == node-01
#   -> 10.244.1.7:8080   Masq  1  118  4
#   -> 10.244.2.4:8080   Masq  1    0  0      <- zero, from THIS node
# == node-02
#   -> 10.244.1.7:8080   Masq  1    2  1
#   -> 10.244.2.4:8080   Masq  1   96  3      <- the other pod, same Service

# Neither node is wrong. "Least connections" is being computed per client node,
# so with N busy client nodes you get N independent local decisions, not one
# global one. Add a client outside the cluster, or a client behind a
# LoadBalancer that lands on a different node, and IPVS never sees it at all.
#
# Clean up the debug pods. `kubectl debug node/...` names them
# node-debugger-<node>-<suffix> and sets no label, so match on the name.
kubectl get pods -o name | grep '^pod/node-debugger-' | xargs -r kubectl delete

# The two things people actually want from a scheduler have supported answers
# that survive this migration, and neither of them is an IPVS scheduler:
#   sticky clients   -> Service .spec.sessionAffinity: ClientIP
#   keep it local    -> Service .spec.internalTrafficPolicy: Local
kubectl get svc -A -o json | jq -r '
  .items[] | select(.spec.sessionAffinity == "ClientIP")
  | "\(.metadata.namespace)/\(.metadata.name) sessionAffinity=ClientIP"'
IPVS-SchedulerWas er auf einem Knoten tutWas das für den Cluster bedeutet
rr (der Standard)Round Robin über die Endpoint-Liste dieses Knotens.Funktional das, was der iptables- und der nftables-Modus mit ihrer zufälligen Backend-Auswahl ohnehin tun. Hier geht nichts verloren.
lc, wlc, sed, nqWenigste aktive Verbindungen, gezählt von diesem Knoten.Kein clusterweites Least Connections. N ausgelastete Client-Knoten treffen N unabhängige lokale Entscheidungen, und Verkehr von außerhalb steckt in keiner davon.
sh, dhHash über die Quell- oder Zieladresse.Wird häufig für Session-Affinität gehalten. Das unterstützte Gegenstück ist .spec.sessionAffinity: ClientIP, das je Service gilt und die Migration überlebt.
lblc, lblcrLokalitätsbasiert, wenigste Verbindungen.Lokalität heißt in Kubernetes .spec.internalTrafficPolicy: Local – etwas, das die API tatsächlich kennt und der Scheduler nicht sehen kann.
mh (Maglev)Konsistentes Hashing.kube-proxy setzt immer mh-port und aktiviert nie mh-fallback, in der Praxis verhält es sich also wie Quell-Hashing mit Ports.

Einen Scheduler je Service gibt es außerdem nicht und gab es nie. ipvs.scheduler ist ein einzelnes Feld in der kube-proxy-Konfiguration und gilt für jeden Service auf jedem Knoten – es ist nicht Teil der Service-API, und keine Annotation ändert daran etwas. Das sollte man einen Moment wirken lassen, denn es bedeutet, dass die beiden Dinge, wegen derer man tatsächlich zu den Schedulern greift, nämlich klebende Clients und Knotenlokalität, in der API unterstützte Antworten haben, die diese Migration unverändert überstehen: sessionAffinity: ClientIP und internalTrafficPolicy: Local.[svc][stp]

ModusStatus in 1.37KernelNutzt darunter iptablesWo er endet
iptablesDer Standard. Nicht abgekündigt.beliebigJa, per DefinitionHört irgendwann auf, der Standard zu sein; eine Entfernung ist nicht angekündigt.
ipvsVeraltet seit 1.35; Feature Gate in 1.37 hinzugekommenbeliebigJa – Masquerade, NodePort-Filterung, LoadBalancer-Source-Ranges, Reject-RegelnStandardmäßig aus in 1.40, Quellcode entfernt in 1.43.
nftablesStabil seit 1.33; der empfohlene Ersatz für ipvs5.13 oder neuerNeinWird in einem künftigen Release der Standard.
kernelspaceNur Windows-KnotenVon alldem nicht betroffen.

Können diese Knoten den nftables-Modus fahren

Nun die eine harte Bedingung. Der nftables-Modus setzt Linux 5.13 oder neuer voraus, und es gibt weder teilweise Unterstützung noch einen Rückfallpfad – darunter startet kube-proxy in diesem Modus schlicht nicht. Das ist die Prüfung, die darüber entscheidet, ob diese Migration eine geänderte ConfigMap oder ein Node-Image-Projekt ist, also machen Sie sie, bevor Sie irgendeinen Plan schreiben.[vips]

#!/usr/bin/env bash
# Precheck. nftables mode needs Linux 5.13 or newer; there is no partial
# support and no fallback - kube-proxy will not start in nftables mode below it.
set -euo pipefail

need_major=5 need_minor=13
fail=0

while read -r node kernel os; do
  ver=${kernel%%-*}                       # 6.8.0-51-generic -> 6.8.0
  IFS=. read -r maj min _ <<<"$ver"
  if (( maj > need_major )) || { (( maj == need_major )) && (( min >= need_minor )); }; then
    printf '  ok    %-22s %s\n' "$node" "$kernel"
  else
    printf '  TOO OLD %-20s %-24s %s\n' "$node" "$kernel" "$os"
    fail=1
  fi
done < <(kubectl get nodes -o custom-columns=\
'NAME:.metadata.name,KERNEL:.status.nodeInfo.kernelVersion,OS:.status.nodeInfo.osImage' \
  --no-headers)

(( fail == 0 )) && echo "all nodes can run nftables mode" \
                || echo "some nodes cannot: rebuild the image or stay on iptables mode"

# On a node that reports too old, the answer is almost never "patch the kernel".
# It is "this node image is end of life". Check what the distro offers before
# planning anything: an Ubuntu 20.04 node on the HWE kernel is fine, the same
# release on the GA kernel is not.
uname -r
nft --version
Node-ImageStandardmäßig ausgelieferter Kernelnftables-Modus
RHEL 9, Rocky Linux 9, AlmaLinux 95.14Ja
RHEL 106.12Ja
RHEL 8, CentOS 74.18 und älterNein – hier steht ein Neubau des Node-Image an
Ubuntu 24.04 LTS6.8Ja
Ubuntu 22.04 LTS5.15Ja
Ubuntu 20.04 LTS5.4 GA, 5.15 mit HWENur mit dem HWE-Kernel
Debian 126.1Ja
Debian 115.10Nein
Amazon Linux 20236.1Ja

Wenn einige Knoten die Grenze reißen, lautet die ehrliche Lesart meist, dass das Node-Image am Ende seines Lebens ist, und nicht, dass der Kernel gepatcht werden müsste – die richtige Antwort ist ein Neubau. Ist der vor 1.40 nicht zu schaffen, ist der Ausweichweg der iptables-Modus und nicht das Feature Gate: das iptables-Backend ist nicht abgekündigt, seine Performance hat sich nach Einführung des ipvs-Modus deutlich verbessert, und Upstream empfiehlt es für genau diesen Fall gegenüber ipvs. Für alle, die den Zeitpunkt abwägen, gehört ein Punkt aus der Abkündigung dazu: jeder Kernel, der für den nftables-Modus zu alt ist, verlässt bis Ende 2026 den Langzeitsupport – der Kernel-Einwand hat also selbst ein Ablaufdatum.[kernrel][kep5495]

Vier Dinge verhalten sich danach anders

Vier Verhaltensweisen ändern sich, und drei davon sind unter einer Überschrift dokumentiert – Migration vom iptables- in den nftables-Modus –, die ipvs-Nutzer nie zu lesen hatten. Sie gelten hier unverändert, weil sie Eigenschaften des Ziels und nicht der Quelle sind.[vips]

Verhalteniptables- / ipvs-Modusnftables-ModusWas zu tun ist
Adressen, auf denen NodePorts lauschenAlle lokalen Adressen, sofern nicht eingeschränktStandardmäßig --nodeport-addresses primaryPrüfen, welche Adressen tatsächlich genutzt werden, und die Option ausdrücklich setzen, falls Sie mehr als die primäre brauchen.
NodePort auf 127.0.0.1Funktioniert im iptables-Modus standardmäßigNicht verfügbar; in 1.37 hinter einem Alpha-Gate zurückgeholtLesen Sie kubeproxy_iptables_localhost_nodeports_accepted_packets_total, bevor Sie entscheiden, dass Sie es nicht brauchen.
Lokale Firewallkube-proxy fügt je NodePort Accept-Regeln hinzuTut nichtsErlauben Sie den NodePort-Bereich in Ihrer eigenen Host-Firewall.
conntrack-Reset-Fehler vor 6.1Workaround wird installiertStandardmäßig nicht installiertkubeproxy_iptables_ct_state_invalid_dropped_packets_total prüfen; ist er ungleich null, --conntrack-tcp-be-liberal mitnehmen.
Eigene Regeln auf kube-proxys ChainsKUBE-SERVICES, KUBE-SEP-* existieren in iptablesDer Zustand liegt in table ip kube-proxy / table ip6 kube-proxyDas war nie API. Hängen Sie stattdessen Ihre eigene Tabelle auf derselben netfilter-Priorität ein.
  • NodePorts lauschen nicht mehr auf jeder lokalen Adresse. Im iptables- und im ipvs-Modus ist ein Service vom type: NodePort auf allen lokalen IPs erreichbar, sofern Sie das nicht eingeschränkt haben. Der nftables-Modus verwendet standardmäßig --nodeport-addresses primary, also die primäre IPv4- und/oder IPv6-Adresse des Knotens aus dem Node-Objekt und sonst nichts. Alles, was einen NodePort über eine zweite NIC, eine Management-Adresse oder eine schwebende VIP erreicht, hört auf zu funktionieren. Setzen Sie die Option ausdrücklich, wenn Sie die alte Reichweite brauchen – 0.0.0.0/0 stellt sie wieder her –, aber prüfen Sie vorher, denn der Standard ist der, den Upstream für den eigentlich gewollten hält.
  • NodePorts über localhost sind ein eigener Fall, und 1.37 hat ihn verändert. Eine Verbindung zu 127.0.0.1:<nodePort> funktionierte im iptables-Modus und im nftables-Modus überhaupt nicht. Seit Kubernetes 1.37 kann sie funktionieren, hinter dem Alpha-Gate KubeProxyNFTablesLocalhostNodePorts und mit nodePortAddresses auf primary,localhost. Bevor Sie entscheiden, ob Sie das brauchen, lesen Sie den Zähler: kube-proxy zählt die auf Loopback-NodePorts angenommenen Pakete die ganze Zeit über mit.
  • kube-proxy öffnet Ihre Firewall nicht mehr für Sie. Der iptables-Modus fügt je NodePort Accept-Regeln hinzu, unter der Annahme, dass eine übereifrige lokale Firewall sie sonst blockieren würde. Gegen eine nftables-basierte Firewall kann dieser Ansatz nicht funktionieren, also tut der nftables-Modus hier gar nichts. Wenn Sie eine Host-Firewall betreiben, muss sie den NodePort-Bereich nun selbst erlauben – was man durchaus für die richtige Aufteilung halten kann und was trotzdem eine Änderung ist.
  • Der conntrack-Workaround wird nicht mehr standardmäßig installiert. Kernel vor 6.1 haben einen Fehler, der langlebige TCP-Verbindungen zu Service-IPs zurücksetzen kann. Der iptables-Modus installiert dagegen einen Workaround; von dem stellte sich später heraus, dass er eigene Probleme verursacht, weshalb der nftables-Modus ihn weglässt. Ob Sie ihn brauchen, ist messbar und keine Ermessensfrage – der Zähler steht in den Metriken –, und wenn ja, ist --conntrack-tcp-be-liberal der unterstützte Weg zurück zum alten Verhalten.
  • Alles von Ihnen, das auf kube-proxys Chains passte, ist jetzt kaputt. Wenn ein Firewall-Skript, ein CNI-Hook oder eine Monitoring-Regel KUBE-SERVICES oder KUBE-SEP-* namentlich ansprach: diese Chains sind weg. Der Zustand von kube-proxy liegt jetzt in table ip kube-proxy und table ip6 kube-proxy. Unterstützt war das nie – Upstream hat dazu einen Blogartikel, der genau so heißt –, aber es lohnt sich, vor dem Rollout danach zu suchen und nicht danach, denn es scheitert lautlos.
# The behaviour difference most likely to page you. In ipvs and iptables mode,
# NodePort Services are reachable on every local address unless you said
# otherwise. nftables mode defaults to --nodeport-addresses primary: the node's
# primary IPv4 and/or IPv6 address from the Node object, and nothing else.
#
# So the question to answer before the switch is: does anything reach a NodePort
# on a secondary address, a VIP, a management NIC, or on loopback?

# What is currently configured, if anything:
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep -i nodePortAddresses
# (empty means "all local addresses")

# Which NodePorts exist at all, and who might be pointed at them:
kubectl get svc -A -o json | jq -r '
  .items[] | select(.spec.type == "NodePort" or .spec.type == "LoadBalancer")
  | .spec.ports[]? | select(.nodePort)
  | "\(.nodePort)\t\(.protocol)"' | sort -u

# Localhost NodePorts are the sharp edge: health checks, sidecars and a
# surprising number of monitoring agents connect to 127.0.0.1:<nodePort>.
# kube-proxy counts them for you, and a non-zero value means something out
# there depends on it.
kubectl -n kube-system exec ds/kube-proxy -- \
  wget -qO- http://127.0.0.1:10249/metrics \
  | grep kubeproxy_iptables_localhost_nodeports_accepted_packets_total
# kubeproxy_iptables_localhost_nodeports_accepted_packets_total 41822

# Kubernetes 1.37 gives that case a way out, as an alpha feature gate. If you
# need it, you need it on 1.37+ and you need both halves:
#   featureGates:
#     KubeProxyNFTablesLocalhostNodePorts: true
#   nodePortAddresses: ["primary", "localhost"]

# And the conntrack workaround: iptables mode installs one for a pre-6.1 kernel
# bug that resets long-lived TCP connections. nftables mode does not, by default.
# Non-zero here means you are relying on it - carry --conntrack-tcp-be-liberal.
kubectl -n kube-system exec ds/kube-proxy -- \
  wget -qO- http://127.0.0.1:10249/metrics \
  | grep kubeproxy_iptables_ct_state_invalid_dropped_packets_total

Das Muster ist bei allen fünf dasselbe: keines davon ist ein Startfehler. kube-proxy kommt hoch, meldet sich gesund, bedient den größten Teil des Verkehrs korrekt, und ein einzelner Pfad hört auf zu funktionieren. Deshalb ist die Vorabprüfung weiter unten mehr wert als das Rollout selbst, und deshalb bleibt der erste Knoten einen Arbeitstag lang allein.[reset][ctsysctl]

Der Wechsel, Knoten für Knoten

Die Änderung umfasst drei Zeilen. Die Disziplin steckt darin, den ipvs-Block zu löschen, statt ihn neben dem neuen Modus stehen zu lassen – tote Konfiguration ist der Grund, aus dem der Nächste schließt, der Cluster laufe noch auf ipvs. Auf mit kubeadm gebauten Clustern kommt eines hinzu: diese ConfigMap wird von kubeadm upgrade neu erzeugt, eine nur mit kubectl gemachte Änderung kann beim nächsten Upgrade also stillschweigend verschwinden. Machen Sie sie zusätzlich in der Cluster-Konfiguration.[kubeadm]

# The change is three lines, and the discipline is in what you delete.
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' > kube-proxy.conf.bak
cp kube-proxy.conf.bak kube-proxy.conf

# --- before ----------------------------------------------------------------
#   mode: ipvs
#   ipvs:
#     scheduler: "lc"
#     strictARP: true
#     syncPeriod: 30s
#
# --- after -----------------------------------------------------------------
#   mode: nftables
#   nftables:
#     minSyncPeriod: 1s
#     syncPeriod: 30s
#
# Delete the whole ipvs block rather than leaving it. In nftables mode it is
# dead configuration: scheduler has no equivalent and no effect, and strictARP
# was only ever there to stop the node answering ARP for the ClusterIPs that
# ipvs mode bound onto kube-ipvs0 - an interface nftables mode never creates.
# Leaving it behind is how the next person concludes the cluster is still ipvs.

# Apply. Note that on a kubeadm cluster this ConfigMap is regenerated by
# `kubeadm upgrade`, so make the same change in the cluster configuration or
# the next upgrade will quietly put ipvs back.
kubectl -n kube-system create configmap kube-proxy \
  --from-file=config.conf=kube-proxy.conf \
  --dry-run=client -o yaml | kubectl apply -f -

Rollen Sie es danach so aus, wie Sie alles ausrollen würden, dem die Datenebene auf jedem Knoten gehört: ein Knoten, dann ein Pool, dann die Flotte. Ein Canary ist hier ungewöhnlich billig, weil der Zustand vollständig pro Knoten liegt und bei jedem Start aus dem API-Server neu aufgebaut wird: es gibt nichts Gemeinsames, das beschädigt werden könnte, und ein Knoten, der den neuen Modus nicht fahren kann, scheitert beim Start mit der Kernel-Version in der Meldung.[drain]

# Do not restart the DaemonSet across the fleet. kube-proxy owns the data plane
# on every node it runs on; a bad rollout is a cluster-wide outage, and this one
# is cheap to canary because the state is per node.

# Pause the DaemonSet so the ConfigMap change does not roll on its own.
kubectl -n kube-system patch ds kube-proxy \
  -p '{"spec":{"updateStrategy":{"rollingUpdate":{"maxUnavailable":1}}}}'

# One node. Cordon it, move the workloads off, restart only that pod.
NODE=node-07
kubectl cordon "$NODE"
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data --timeout=5m
kubectl -n kube-system delete pod \
  --field-selector "spec.nodeName=$NODE" -l k8s-app=kube-proxy

# Watch it come up in the new mode. A node that cannot run nftables mode fails
# here, loudly, with the kernel version in the message - which is the correct
# place to find that out.
kubectl -n kube-system logs -f --tail=40 \
  "$(kubectl -n kube-system get pod -l k8s-app=kube-proxy \
     --field-selector "spec.nodeName=$NODE" -o name)"

kubectl uncordon "$NODE"

# Give it real traffic and a working day before the second node. The failures
# this migration produces are not startup failures; they are "one client on a
# secondary interface stopped reaching a NodePort", and that takes a shift to
# surface. Then a pool, then the fleet.

Was ipvs auf dem Knoten zurücklässt

Beim Aufräumen unterscheidet sich diese Migration von jedem früheren Moduswechsel bei kube-proxy, und zwar zum Guten. Früher versuchte kube-proxy, hinter den anderen Modi aufzuräumen, und hörte damit auf – KEP-2448 hat diese Logik entfernt –, weil die Backends iptables, ipvs und userspace teilweise in dieselben Chains schrieben und das Aufräumen der Regeln des einen Modus die Regeln des laufenden Modus mit löschte. Der nftables-Modus teilt nichts: sein gesamter Zustand liegt in seinen eigenen zwei Tabellen. Deshalb ist der Wechsel in diese Richtung so entworfen, dass er die alten Regeln beim Start entfernt, und deshalb ist das Aufräumen von Hand ungefährlich, wenn es nötig wird.[kep2448][ipvsadm]

# kube-proxy in nftables mode is designed to remove the iptables and ipvs rules
# it finds on startup - the modes do not share state, which is exactly why this
# direction is safe when switching between the iptables-family modes was not.
# Verify rather than assume, on the first node, before the second one.

ipvsadm -L -n | tail -n +4 | wc -l         # want 0
ip link show kube-ipvs0 2>/dev/null        # want "does not exist"
ipset list -name | grep -c '^KUBE-'        # want 0
iptables-save -t nat | grep -c '^-A KUBE-' # want 0 (or only your own rules)

# If something survived - an older kube-proxy, a node that was rebooted mid-way,
# a third party that wrote into those chains - clear it explicitly. Every one of
# these is safe once kube-proxy is confirmed running in nftables mode on the node.
ipvsadm --clear
ip link delete kube-ipvs0                  # recreated only by ipvs mode
for s in $(ipset list -name | grep '^KUBE-'); do ipset destroy "$s"; done

# The stale kube-ipvs0 addresses are the ones that actually hurt. Left in place
# with no IPVS rules behind them, the node still claims those ClusterIPs and
# still answers ARP for them, and traffic that lands there is dropped rather
# than redirected. That is a black hole that looks like an application problem.

# The strictARP sysctls are set at runtime and do not revert on their own. They
# are harmless, but if you want the node back to stock:
sysctl -w net.ipv4.conf.all.arp_ignore=0
sysctl -w net.ipv4.conf.all.arp_announce=0

# There is also a supported flush. It is documented as cleaning up iptables and
# ipvs rules, so it is the right tool for this direction and the wrong one for
# the other. Run it with kube-proxy stopped on that node.
kube-proxy --cleanup

Ein Überbleibsel verdient mehr Aufmerksamkeit als die übrigen. kube-ipvs0 ist ein Dummy-Interface, auf das der ipvs-Modus jede ClusterIP – und jede LoadBalancer-IP – als /32 auf dem Knoten bindet, und das ist der ganze Grund, warum MetalLB im Layer-2-Modus strictARP als Voraussetzung für ipvs-Cluster dokumentiert. Der nftables-Modus erzeugt kein solches Interface, die Voraussetzung entfällt also und die Einstellung wird zu toter Konfiguration. Überlebt das Interface den Wechsel aber mitsamt den Adressen und ohne IPVS-Regeln dahinter, beansprucht der Knoten diese ClusterIPs weiterhin und beantwortet ARP für sie, während er den Verkehr verwirft. Das ist ein schwarzes Loch auf einem Knoten, das sich als sporadischer Anwendungsfehler zeigt, und es ist der beste Grund, das Aufräumen nachzuweisen statt anzunehmen.[metallb][chains]

# Everything kube-proxy now owns lives in two tables of its own, one per IP
# family, which is the property that makes the whole thing inspectable.
nft list tables
# table ip kube-proxy
# table ip6 kube-proxy

# The Service map, which is the equivalent of what `ipvsadm -L -n` used to show.
# Note that it is a map lookup rather than a rule chain: this is the performance
# argument for the new backend, and it is visible in the output.
nft list table ip kube-proxy | head -40

# One Service end to end:
SVC_IP=$(kubectl get svc -n prod api -o jsonpath='{.spec.clusterIP}')
nft list table ip kube-proxy | grep -A3 "$SVC_IP"

# Live rule changes, which is the closest thing to watching kube-proxy think:
nft monitor rules

# What kube-proxy does NOT own any more - and must not, if the cleanup worked:
nft list ruleset | grep -c 'KUBE-SVC\|KUBE-SEP'   # want 0
ipvsadm -L -n | tail -n +4 | wc -l               # want 0

# A standing caution that predates all of this: kube-proxy's chains and tables
# are not API. If something of yours matched on KUBE-SERVICES by name, it is
# broken now, and it was unsupported before. Hook your own table into the same
# netfilter priorities instead of writing into kube-proxy's.

Nachweisen statt hoffen

Die Prüfung hat bei dieser Migration eine bestimmte Gestalt, diktiert von den Fehlerbildern: fast alles, was schiefgeht, lässt kube-proxy laufen und gesund erscheinen. Zu prüfen, ob der Pod Running ist, beweist also überhaupt nichts. Das folgende Skript prüft den Modus, den kube-proxy tatsächlich gewählt hat, und nicht den aus der ConfigMap, dass kein ipvs-Zustand überlebt hat, dass das Dummy-Interface weg ist, dass die Regelsynchronisierung gelingt – und tut dann das Eine, was keine Inspektion leisten kann, nämlich eine echte Verbindung zu einem echten Service von einem Pod auf diesem Knoten aus zu öffnen.[dbgsvc]

#!/usr/bin/env bash
# Run on a migrated node, from a machine with kubectl and cluster access.
# Exits non-zero on anything that would be silently wrong. The failures this
# migration produces do not stop kube-proxy, so "the pod is Running" proves
# nothing at all.
set -uo pipefail
NODE=${1:?usage: verify.sh <node>}
NS=${NS:-default}
rc=0
say() { printf '%-46s %s\n' "$1" "$2"; }
chk() { if [[ $2 == "$3" ]]; then say "$1" "ok"; else say "$1" "FAIL ($2 != $3)"; rc=1; fi; }

POD=$(kubectl -n kube-system get pod -l k8s-app=kube-proxy \
      --field-selector "spec.nodeName=$NODE" -o jsonpath='{.items[0].metadata.name}')

# 1. the mode kube-proxy actually chose, not the one in the ConfigMap
mode=$(kubectl -n kube-system logs "$POD" | grep -om1 'Using .* Proxier' | awk '{print $2}')
chk "proxy mode" "$mode" "nftables"

# 2. no ipvs state left on the node
left=$(kubectl debug "node/$NODE" -q --image=busybox --profile=general -- \
       chroot /host sh -c 'ipvsadm -L -n 2>/dev/null | tail -n +4 | wc -l' 2>/dev/null | tr -d ' ')
chk "ipvs virtual servers remaining" "${left:-0}" "0"

# 3. the dummy interface is gone, so no stale ClusterIP black holes
iface=$(kubectl debug "node/$NODE" -q --image=busybox --profile=general -- \
        chroot /host sh -c 'ip link show kube-ipvs0 >/dev/null 2>&1 && echo present || echo absent' \
        2>/dev/null | tr -d ' ')
chk "kube-ipvs0" "${iface:-absent}" "absent"

# 4. sync is succeeding, which is the counter that replaces "is it up"
m=$(kubectl -n kube-system exec "$POD" -- wget -qO- http://127.0.0.1:10249/metrics)
fails=$(awk '/^kubeproxy_sync_proxy_rules_nftables_sync_failures_total/ {s+=$2} END{print s+0}' <<<"$m")
chk "nftables sync failures" "$fails" "0"
awk '/^kubeproxy_sync_proxy_rules_last_timestamp_seconds/ {print "  last successful sync:", $2}' <<<"$m"

# 5. and the part no inspection can establish: a real connection to a real
#    Service, from a pod on this node, plus a NodePort from off-box.
kubectl -n "$NS" run "nftcheck-$$" --rm -i --restart=Never \
  --overrides="{\"spec\":{\"nodeName\":\"$NODE\"}}" \
  --image=curlimages/curl -- \
  curl -sS -o /dev/null -w '%{http_code}\n' --max-time 5 \
  http://kubernetes.default.svc.cluster.local:443 >/dev/null 2>&1 \
  && say "in-cluster Service connect" "ok" \
  || { say "in-cluster Service connect" "FAIL"; rc=1; }

exit $rc

Danach sind drei Zähler für zwei Wochen einen Alarm wert. Beachten Sie, was in der Liste fehlt: die Metrik-Referenz von Kubernetes führt iptables-spezifische und nftables-spezifische Zähler und für ipvs überhaupt nichts. Was auch immer Sie heute an Monitoring für den ipvs-Modus haben, Sie haben es sich selbst aus ipvsadm gebaut – eine kleine, konkrete Veranschaulichung des Wartungsarguments, das hinter der ganzen Abkündigung steht.[metrics]

MetrikWas sie Ihnen sagtAlarm wert
kubeproxy_sync_proxy_rules_nftables_sync_failures_totalkube-proxy kann das Regelwerk nicht schreiben, die Datenebene driftet also vom API-Server weg.Jeder Anstieg, dauerhaft.
kubeproxy_sync_proxy_rules_nftables_cleanup_failures_totalEs kann Regeln nicht entfernen, die es für veraltet hält – meist Reste des alten Modus oder etwas anderes, das in dieselben Tabellen schreibt.Jeder Anstieg in den zwei Wochen nach dem Wechsel.
kubeproxy_sync_proxy_rules_last_timestamp_secondsWann die Regeln des Knotens zuletzt zum API-Server passten. Das ist der Zähler, der den stillen Fehler fängt.Mehr als fünf Minuten in der Vergangenheit.
kubeproxy_iptables_localhost_nodeports_accepted_packets_totalIrgendetwas verbindet sich über Loopback auf einen NodePort.Ungleich null vor dem Wechsel, als Blocker.
kubeproxy_sync_proxy_rules_duration_secondsWie lange eine vollständige Synchronisierung dauert. Das Performance-Argument für die Migration, sofern Sie genug Services für eines haben.Vorher und nachher als Basislinie nehmen; kein Alarm.
# kube-proxy serves these on 10249 on every node. Three of them are worth an
# alert for the fortnight after the migration; the rest are for the postmortem.

# Sync is failing on this node - the data plane is drifting from the API server:
sum by (node) (rate(kubeproxy_sync_proxy_rules_nftables_sync_failures_total[5m])) > 0

# Cleanup is failing - usually leftovers from the previous mode, or something
# else writing into the same tables:
sum by (node) (rate(kubeproxy_sync_proxy_rules_nftables_cleanup_failures_total[5m])) > 0

# Rules are stale. This is the one that catches the silent failure, because
# kube-proxy stays Running while it happens:
time() - max by (node) (kubeproxy_sync_proxy_rules_last_timestamp_seconds) > 300

# And the pre-migration baseline worth keeping: sync duration before and after.
# In clusters with a few thousand Services the improvement is the point; in a
# cluster with forty Services there is nothing to see and that is fine too.
histogram_quantile(0.99, sum by (le) (rate(kubeproxy_sync_proxy_rules_duration_seconds_bucket[5m])))

# Note what is not in this list. The Kubernetes metrics reference has
# iptables-specific counters and nftables-specific counters and no ipvs-specific
# counter at all. Whatever observability you have for ipvs mode today, you built
# it yourself out of ipvsadm - which is its own argument about maintenance.

Zurückrollen – und was das einbringt

Das Rollback ist hier wirklich billig, was von den anderen Migrationen, die dieses Jahr auf denselben Knoten landen, nicht gilt. Es gibt keine Daten zu konvertieren, kein Format auf der Platte, das herabgestuft werden müsste, und keinen Zustand, der einen Neustart überdauert: die gesamte Änderung ist ein ConfigMap-Schlüssel plus Kernel-Zustand pro Knoten, den kube-proxy bei jedem Start aus dem API-Server neu aufbaut. Alten Schlüssel zurückschreiben, den Pod auf diesem Knoten löschen, und der Knoten ist in unter einer Minute dort, wo er war.[kep3866]

# Rollback here is genuinely cheap, which is not true of most migrations on
# these nodes. There is no data to convert and no format to downgrade: the whole
# of the change is one ConfigMap key and per-node kernel state that is rebuilt
# from the API server on every start.

kubectl -n kube-system create configmap kube-proxy \
  --from-file=config.conf=kube-proxy.conf.bak \
  --dry-run=client -o yaml | kubectl apply -f -

kubectl -n kube-system delete pod \
  --field-selector "spec.nodeName=$NODE" -l k8s-app=kube-proxy

# kube-proxy in ipvs mode removes the nftables rules it finds on startup. If it
# does not - and rollback matters most exactly when the new backend is
# misbehaving - the manual version is two commands, because every rule
# kube-proxy owns is inside its own tables:
nft delete table ip kube-proxy
nft delete table ip6 kube-proxy

# Then confirm the old data plane is actually back, rather than assuming:
ipvsadm -L -n | tail -n +4 | wc -l     # want non-zero again
ip -brief addr show kube-ipvs0         # the dummy interface returns

# Be honest about what the rollback bought. ipvs mode is off by default in
# Kubernetes 1.40 and the code is deleted in 1.43. A rollback ends a bad
# maintenance window; it does not move the date.

Zwei Einschränkungen, beide betreffen die Reichweite und nicht den Mechanismus. kube-proxy im ipvs-Modus ist darauf ausgelegt, die beim Start vorgefundenen nftables-Regeln zu löschen, aber ein Rollback zählt gerade dann am meisten, wenn sich das neue Backend danebenbenimmt – also sollte man die manuelle Fassung kennen: jede Regel, die kube-proxy gehört, liegt in seinen eigenen Tabellen, und zwei nft delete table-Befehle entfernen alles davon. Und seien Sie sich im Klaren darüber, was das Rollback tatsächlich einbringt. Es beendet ein missratenes Wartungsfenster. Es verschiebt 1.40 nicht, und 1.43 verschiebt es auch nicht.[skew]

Die andere Antwort: gar kein kube-proxy

Es gibt auf diese Abkündigung eine zweite Antwort, die man benennen sollte, weil sie für manche Cluster die bessere ist: kube-proxy gar nicht mehr betreiben. Cilium und Calico implementieren Kubernetes-Services beide direkt in eBPF, mit einem Hash-Map-Zugriff statt einer Regelliste, und beide dokumentieren den Betrieb mit entferntem kube-proxy statt neben ihm. Wenn Sie eines von beiden ohnehin einsetzen, kann die ipvs-Abkündigung die Einladung sein, eine Änderung zu machen, die ohnehin anstand.[cilium]

Es ist eine deutlich größere Änderung als die, die dieser Artikel beschreibt, und sie sollte auch so abgewogen werden. kube-proxy zu ersetzen verlagert die Service-Implementierung in das CNI und koppelt Ihre Datenebene damit an dessen Release-Takt, dessen Kernel-Anforderungen – die höher liegen als 5.13, nicht niedriger – und dessen Debugging-Werkzeuge, und das macht man nicht innerhalb eines Abkündigungsfensters unter Zeitdruck. Die ehrliche Reihenfolge lautet für die meisten Cluster: jetzt auf nftables wechseln, weil es eine geänderte ConfigMap ist, und die eBPF-Frage danach für sich und nach eigenem Zeitplan bewerten.[calico][netpol]

Die Reihenfolge, in der das gemacht wird

Verdichtet ist die Entscheidung viel kleiner als der Artikel. Sind Ihre Kernel 5.13 oder neuer, ist das eine Konfigurationsänderung mit sorgfältigem Rollout. Sind sie es nicht, ist es ein Node-Image-Projekt, und die Zwischenantwort heißt iptables-Modus und nicht Feature Gate. Und wenn Ihre kube-proxy-Konfiguration überhaupt keinen Modus nennt, ist das unabhängig von allem anderen hier die Sache, die diese Woche zu erledigen ist.[kep5495]

Wo Sie stehenWas zu tun ist
ipvs-Modus, jeder Knoten auf 5.13 oder neuerAuf nftables wechseln. Das ist der ganze Artikel, und es ist eine geänderte ConfigMap plus ein sorgfältiges Rollout.
ipvs-Modus, einige Knoten unter 5.13Diese Node-Images neu bauen. Ist das vor 1.40 nicht zu schaffen, stellen Sie sie auf den iptables-Modus um, statt das Feature Gate zu setzen – iptables ist nicht abgekündigt, und Upstream empfiehlt es bei alten Kerneln gegenüber ipvs.
iptables-Modus, kein PlanHeute mode: iptables ausdrücklich festlegen, damit kein Upgrade das ändern kann, und dann auf nftables wechseln, sobald Ihre Kernel-Untergrenze es erlaubt.
Überhaupt kein Modus gesetztDiese Woche festlegen, welchen auch immer Sie wollen. Das ist der Punkt mit einer Frist, die niemand in Ihrer Organisation gewählt hat.
Das CNI ersetzt kube-proxy bereitsNichts zu tun. Weisen Sie nach, dass kube-proxy wirklich nicht läuft, statt es anzunehmen, denn es gibt auch teilweise Ersetzungen.
  1. Legen Sie den Modus fest, welchen auch immer. Ist mode: in der kube-proxy-ConfigMap leer, tragen Sie ihn heute ausdrücklich ein. Das ist der einzige Punkt dieser Liste, der auch für Cluster ganz ohne ipvs gilt, und der mit einer Frist, die Sie nicht gewählt haben.
  2. Prüfen Sie die Kernel, bevor Sie einen Plan schreiben. Ein Befehl über die Flotte entscheidet, ob das ein Nachmittag oder ein Quartal wird. Knoten unterhalb von 5.13 brauchen ein neues Image und keinen Patch.
  3. Messen Sie die zwei Zähler, die über die scharfen Kanten entscheiden. NodePorts über localhost und die conntrack-Verwürfe im Zustand invalid lassen sich beide aus kube-proxys eigenen Metriken beantworten, bevor Sie irgendetwas ändern. Prüfen Sie im selben Durchgang, auf welchen Adressen NodePorts tatsächlich angesprochen werden.
  4. Ein Knoten, ein voller Arbeitstag, dann ein Pool. Die Regressionen hier sind keine Startfehler, sondern ein einzelner Client auf einem zweiten Interface, und das braucht eine Schicht, um sichtbar zu werden. Weisen Sie das Aufräumen von kube-ipvs0 und der ipsets auf diesem ersten Knoten von Hand nach.
  5. Löschen Sie den ipvs-Block und die Einstellung strictARP. Beide tun im nftables-Modus nichts, und sie stehen zu lassen ist der Grund, aus dem der Nächste schließt, die Migration habe nie stattgefunden. Machen Sie dieselbe Änderung dann dort, wo die ConfigMap erzeugt wird, sonst nimmt das nächste kubeadm upgrade sie zurück.

Das ist eine von mehreren Änderungen, die im selben Jahr auf denselben Knoten landen, und sie lesen sich zusammen besser: der Wechsel von Ingress NGINX zur Gateway API, die andere Hälfte der Netzwerkarbeit und mit derselben Rollout-Disziplin; die Migration von containerd 1.7 auf 2.x, die Runtime darunter mit ihrem eigenen Ruhestandsplan; und die Migration von cgroup v1 auf cgroup v2, die Änderung auf Knotenebene, die Kubernetes bereits verpflichtend gemacht hat. Wer abwägt, wie viel davon überhaupt nötig ist, findet in wann man Kubernetes nicht einsetzen sollte die andere Seite des Arguments.

Häufige Fragen

Wird der ipvs-Modus von kube-proxy in Kubernetes 1.37 entfernt?

Nein. Kubernetes 1.37 fügt das Feature Gate KubeProxyIPVS hinzu, das standardmäßig auf true steht – der ipvs-Modus verhält sich also genau wie zuvor. Die Termine, auf die es ankommt, sind 1.40, wo das Gate standardmäßig auf false kippt und kube-proxy im ipvs-Modus ohne Übersteuerung mit einem Fehler abbricht, und 1.43, wo pkg/proxy/ipvs gelöscht wird und das Gate nicht mehr hilft. Das Gate selbst verschwindet in 1.46.

Wie finde ich heraus, welchen Proxy-Modus mein Cluster verwendet?

Auf einem mit kubeadm gebauten Cluster mit kubectl -n kube-system get configmap kube-proxy -o jsonpath='{.data.config\.conf}' | grep 'mode:'. Kommt das leer zurück, hat der Cluster keinen Modus gewählt und fährt das, was kube-proxy gerade empfiehlt – in 1.37 also iptables, und in einem künftigen Release nftables. Auf einem Cluster ohne diese ConfigMap lesen Sie stattdessen die Argumente des DaemonSet oder führen auf einem Knoten ipvsadm -L -n aus.

Muss ich auf nftables migrieren, oder geht auch der iptables-Modus?

Der iptables-Modus ist ein legitimes Ziel. Er ist nicht abgekündigt, hat keine Kernel-Untergrenze, und seine Performance hat sich in den Jahren nach Einführung des ipvs-Modus deutlich verbessert – Upstream empfiehlt ihn für Systeme, die zu alt für den nftables-Modus sind, ausdrücklich gegenüber ipvs. nftables ist das bessere Ziel, wo der Kernel es zulässt, und dorthin bewegt sich auch der Standard, aber „von ipvs auf iptables“ ist für alte Node-Images eine echte Antwort und keine Ausrede.

Welche Kernel-Version braucht der nftables-Modus von kube-proxy?

Linux 5.13 oder neuer, und nur auf Linux-Knoten. Teilweise Unterstützung gibt es nicht: unterhalb davon startet kube-proxy im nftables-Modus nicht. In der Praxis fallen damit RHEL 8, CentOS 7, Debian 11 und Ubuntu 20.04 auf dem GA-Kernel heraus, während RHEL 9 und 10, Debian 12, Ubuntu 22.04 und 24.04 sowie Amazon Linux 2023 die Grenze erreichen. Prüfen Sie mit kubectl get nodes -o custom-columns=NAME:.metadata.name,KERNEL:.status.nodeInfo.kernelVersion statt nach Distributionsnamen.

Verliere ich den IPVS-Scheduler, den ich konfiguriert habe?

Sie verlieren die Einstellung und mit ziemlicher Sicherheit nicht das Verhalten, das Sie ihr zugeschrieben haben. ipvs.scheduler ist ein Feld für den gesamten Cluster und nicht je Service, und die IPVS-Tabelle jedes Knotens zählt nur Verbindungen, die dieser Knoten geöffnet hat – lc ist also Least Connections je Client-Knoten und nicht über den Cluster hinweg. Wollten Sie klebende Clients, ist das .spec.sessionAffinity: ClientIP am Service. Ging es um Knotenlokalität, ist es .spec.internalTrafficPolicy: Local. Beides bleibt von der Migration unberührt.

Brauche ich strictARP nach dem Wechsel auf den nftables-Modus noch?

Nein. strictARP existiert, weil der ipvs-Modus jede ClusterIP und jede LoadBalancer-IP auf das Dummy-Interface kube-ipvs0 bindet, wodurch der Knoten ARP für Adressen beantwortet, die MetalLB kontrollieren will. Der nftables-Modus erzeugt dieses Interface nie, das zugrunde liegende Problem ist damit weg und die Einstellung ist tote Konfiguration – löschen Sie sie mit dem restlichen ipvs-Block. Weisen Sie aber auf jedem migrierten Knoten nach, dass kube-ipvs0 wirklich verschwunden ist, denn ein übrig gebliebenes Interface mit veralteten Adressen verschluckt Verkehr spurlos.

Ist ein Moduswechsel auf einem laufenden Cluster sicher?

Der Wechsel zwischen einem Modus der iptables-Familie und nftables ist genau dafür entworfen: der nftables-Modus hält seinen gesamten Zustand in seinen eigenen zwei Tabellen und entfernt beim Start die vorgefundenen iptables- und ipvs-Regeln, und umgekehrt gilt dasselbe beim Rollback. Das unterscheidet sich ausdrücklich vom Wechsel zwischen iptables und ipvs, die sich Chains teilten – weshalb kube-proxys automatisches Aufräumen in KEP-2448 entfernt wurde. Es bleibt trotzdem eine Änderung an der Datenebene jedes betroffenen Knotens: cordon, drain, einen kube-proxy-Pod neu starten, prüfen und diesen Knoten einen Arbeitstag lang in Ruhe lassen.

Was geht bei dieser Migration am häufigsten kaputt?

Die Erreichbarkeit von NodePorts. Der nftables-Modus nutzt standardmäßig --nodeport-addresses primary, sodass ein NodePort, der über eine zweite NIC, eine Management-Adresse, eine schwebende VIP oder über 127.0.0.1 angesprochen wurde, nicht mehr antwortet – während kube-proxy gesund bleibt und alles andere weiterläuft. Beide Fälle sind vorab messbar, aus kube-proxys eigenen Metriken und aus der aktuellen Einstellung nodePortAddresses.

Sollte ich kube-proxy stattdessen durch Cilium oder Calico mit eBPF ersetzen?

Das ist eine echte Option und für manche Cluster die bessere, aber es ist eine viel größere Änderung als ein Moduswechsel: sie verlagert die Service-Implementierung in das CNI und koppelt Ihre Datenebene an dessen Releases, Kernel-Anforderungen und Debugging-Werkzeuge. Das unter dem Druck einer Abkündigung zu entscheiden, ist die falsche Reihenfolge. Der Wechsel auf nftables ist eine geänderte ConfigMap und nimmt die Frist heraus; die eBPF-Frage bewerten Sie danach, nach eigenem Zeitplan.

Wie rolle ich zurück, wenn sich der nftables-Modus danebenbenimmt?

Den vorherigen ConfigMap-Schlüssel wiederherstellen und den kube-proxy-Pod auf diesem Knoten löschen; der alte Modus baut seinen Zustand beim Start aus dem API-Server neu auf, der Knoten ist also in unter einer Minute zurück. Läuft kube-proxys eigenes Aufräumen nicht – und genau dann zählt ein Rollback am meisten –, liegt jede Regel, die ihm gehört, in seinen eigenen Tabellen, sodass nft delete table ip kube-proxy und nft delete table ip6 kube-proxy alles davon entfernen. Das Rollback beendet ein missratenes Wartungsfenster; es verschiebt weder 1.40 noch 1.43.

Eine Anmerkung auf Release-Ebene, denn die Bilanz für 1.37 sieht anders aus, als fast überall zu lesen ist: Was tatsächlich Pods in ContainerCreating hängen lassen kann, ist SELinuxMount auf GA - der cgroup-v1-Fehler kam in 1.35, die Static-Pod-Einschränkung in 1.34, und die containerd-Klippe liegt noch vor Ihnen, in 1.38. was beim Upgrade auf Kubernetes 1.37 wirklich kaputtgeht trennt die drei Spalten und liefert das Audit, das vor dem Upgrade gehört und nicht danach.

Quellen

Ausschließlich Primärquellen, geprüft am 27. August 2026. Wo die Kubernetes-Dokumentation und eine Sekundärquelle bei dieser Abkündigung über eine Versionsnummer uneins sind, gelten die Dokumentation und das KEP – der Zeitplan wurde bereits einmal überarbeitet.

  1. Kubernetes - Virtual IPs and Service Proxies: the reference page for every kube-proxy mode. It carries the deprecation notice for ipvs mode with the 1.40 and 1.43 dates, the kernel 5.13 requirement for nftables mode, the full list of IPVS schedulers and the ipvs.scheduler field they are set through, the four documented behaviour differences when migrating to nftables, and the sentence that matters most to clusters that are not on ipvs at all: the default mode is iptables in 1.37 and a future release will change it to nftables
  2. Kubernetes v1.37: Garhwal - the release announcement of 26 August 2026. One of the 67 enhancements is a deprecation and it is this one. The deprecation section states the timeline, gives the jsonpath one-liner for finding out which mode a cluster is running, and says outright that ipvs mode continues to use iptables underneath because the kernel IPVS API alone cannot implement Kubernetes Services
  3. KEP-5495: Deprecate ipvs mode in kube-proxy - the enhancement proposal itself, and the only document that carries all five stages. Stage 2 in 1.37 adds the KubeProxyIPVS feature gate; stage 3 in 1.40 flips it off by default; stage 4 in 1.43 removes pkg/proxy/ipvs; the cleanup stage in 1.46 removes the gate. It also records why: SIG Network has no maintainers familiar with the ipvs backend, and the kernels too old for nftables mode will be out of LTS by the end of 2026
  4. KEP-3866: Add an nftables-based kube-proxy backend - the design document for the mode you are migrating to, including the section titled "The ipvs mode of kube-proxy will not save us", the reasoning behind switching modes being safe in this direction when it was not between the iptables-family modes, and the two nft commands that remove every rule kube-proxy owns
  5. KEP-265: IPVS load balancing mode in Kubernetes - the original 2017 proposal, worth reading now mainly to see which of its promises were kept and which were quietly not
  6. KEP-2448: Remove kube-proxy automatic clean-up logic - why kube-proxy stopped trying to tidy up after the other modes, and therefore why the cleanup step in this migration is something you do rather than something that happens
  7. kube-proxy command line reference: --proxy-mode, --cleanup, --nodeport-addresses, --conntrack-tcp-be-liberal, --ipvs-scheduler, --ipvs-strict-arp and the rest. Note that --cleanup is documented as cleaning up iptables and ipvs rules, which is the direction that matters here
  8. KubeProxyConfiguration API reference: the schema of the config.conf that lives in the kube-proxy ConfigMap, including the mode field, the ipvs and nftables sections, and which options are read in which mode
  9. Kubernetes feature gates: where KubeProxyIPVS and KubeProxyNFTablesLocalhostNodePorts are listed with their stage and default, and the reference for how a gate is passed to a component that is not the API server
  10. Kubernetes metrics reference: the exact names of the kube-proxy counters used in this article, including kubeproxy_sync_proxy_rules_nftables_sync_failures_total, kubeproxy_iptables_ct_state_invalid_dropped_packets_total and kubeproxy_iptables_localhost_nodeports_accepted_packets_total. Also, by omission, the fact that there is no ipvs-specific counter anywhere in the list
  11. Kubernetes deprecation policy: what a deprecation of a component flag or behaviour actually commits the project to, which is the frame for reading the KEP-5495 stages as dates rather than intentions
  12. Kubernetes releases: the supported branches and their end-of-life dates, which is how you turn "1.40" and "1.43" into calendar quarters for your own cluster
  13. Kubernetes version skew policy: how far kube-proxy is allowed to lag the API server and the kubelet, which bounds how long a partially migrated fleet can stay partially migrated
  14. Kubernetes v1.35: Timbernetes - the release where stage 1 of this deprecation landed and kube-proxy started logging a warning on startup in ipvs mode. If nobody in your organisation noticed, that is the point
  15. Kubernetes v1.36: Haru - the release in between, useful for placing the deprecation on the same timeline as the other node-level changes of 2026
  16. Kubernetes v1.37 sneak peek: the pre-announcement of the same deprecation, published four weeks before the release
  17. Kubernetes - Service: the API that all of this implements, and the reference for sessionAffinity, which is the feature people mistakenly believe the IPVS sh scheduler is providing
  18. Kubernetes - EndpointSlices: the objects kube-proxy actually watches, and the reason rule-sync cost scales with endpoint churn rather than with Service count alone
  19. Kubernetes - Service internal traffic policy: the supported way to keep traffic on the local node, which is the thing IPVS locality-based schedulers are sometimes reached for instead
  20. Kubernetes - Cluster networking: where kube-proxy sits relative to the CNI plugin, which decides whether any of this applies to your cluster at all
  21. Kubernetes - Debug Services: the official checklist for a Service that does not answer, and the first thing to run when a node comes back on a new proxy mode
  22. Kubernetes - Safely drain a node: the cordon, drain and uncordon sequence this migration slots into, one node at a time
  23. Kubernetes - Upgrading kubeadm clusters: for kubeadm-built clusters, the place the kube-proxy DaemonSet and its ConfigMap come from, and the reason a config change can be reverted by the next upgrade if it is not also made in the cluster configuration
  24. Kubernetes blog - Kubernetes's iptables chains are not API: the standing warning that anything of yours which hooks into kube-proxy's own chains is unsupported. It is the single best predictor of what will break when the chains are replaced by nftables tables
  25. Kubernetes blog - IPVS-based in-cluster load balancing deep dive: the 2018 introduction to the mode being retired, including the kube-ipvs0 dummy interface and the ipset usage that this article tells you to go and clean up
  26. Kubernetes blog - kube-proxy subtleties, debugging an intermittent connection reset: the original write-up of the conntrack invalid-state problem whose workaround nftables mode does not install by default
  27. kubernetes/kubernetes, pkg/proxy/ipvs: the directory KEP-5495 stage 4 deletes. Worth a look if you want to see for yourself how much iptables the ipvs mode is driving
  28. kubernetes/kubernetes, pkg/proxy/nftables: the implementation you are moving to, and the authority on which table and chain names to expect on a migrated node
  29. nftables wiki: the syntax reference for reading what kube-proxy now writes, in particular sets, maps and verdict maps, which are the features the iptables API cannot express and the reason the new backend is faster
  30. nft(8) manual page: list, delete, monitor and the ruleset commands used in the verification section
  31. The netfilter project's nftables page: the upstream statement of what nftables replaces and why development moved there
  32. Linux kernel documentation - nf_conntrack sysctls: nf_conntrack_tcp_be_liberal, which is what --conntrack-tcp-be-liberal sets, and the surrounding timeouts kube-proxy also manages
  33. kernel.org - active kernel releases: the longterm branches and their projected end-of-life dates, which is how to check the KEP's claim that every kernel too old for nftables mode leaves LTS by the end of 2026
  34. ipvsadm(8): the tool for reading and clearing the IPVS table that kube-proxy leaves behind, including -L -n for inspection and -C for the flush used in the cleanup step
  35. MetalLB installation: the source of the strict ARP requirement for kube-proxy in ipvs mode. It is an ipvs-only requirement because it works around an ipvs-only behaviour, which is why it stops applying after this migration
  36. Cilium - Kubernetes without kube-proxy: the other answer to this deprecation, which is to stop running kube-proxy at all and let an eBPF data plane implement Services
  37. Calico - enabling the eBPF data plane: the same answer from the other major CNI, including the requirement to disable kube-proxy rather than run both

War das hilfreich?