تجاوز إلى المحتوى
← المدونة

صار لوضع ipvs تاريخ حذف محدّد

شحنت Kubernetes 1.37 إهمالًا واحدًا فقط، وهو وضع ipvs في kube-proxy. مُطفأ افتراضيًا في 1.40، ومحذوفة شيفرته في 1.43. هنا التبديل إلى nftables، وحدّ النواة الأدنى، والسلوكيات الأربعة التي تتغيّر، والمصيدة التي تطال عناقيد لا تشغّل ipvs أصلًا.

·24 دقيقة قراءة
  • Kubernetes
  • الشبكات
  • لينكس
  • nftables

وصلت Kubernetes v1.37 في 26 أغسطس 2026 ومعها 67 تحسينًا، واحد منها بالضبط هو إهمال — وهو وضع ipvs في kube-proxy. عنوان أصغر من جدولة المجموعات أو شهادات الـ Pods، وبند تشغيلي أكبر منهما بكثير، لأنه الشيء الوحيد في هذا الإصدار الذي ينتهي بحذف شيفرة من مستوى بيانات يعمل في الإنتاج الآن. والجدول مكتوب سلفًا: بوابة ميزة في 1.37، ومُطفأة افتراضيًا في 1.40، وحذف pkg/proxy/ipvs في 1.43.

صورة غلاف من ثلاث لوحات، موصوفة بحسب موضع كل لوحة في الصورة لا بحسب ترتيب القراءة. اللوحة اليسرى من الصورة بعنوان «mode: ipvs» وتُظهر ثلاث طبقات متراكبة: خوادم IPVS الافتراضية في الأعلى، ثم واجهة kube-ipvs0 الوهمية التي تحمل كل عنوان ClusterIP بصيغة /32 في الوسط، ثم طبقة أعرض منهما في الأسفل مكتوب عليها iptables + ipset، وإلى جانبها ملاحظة حمراء تقول إن الشيفرة المصدرية تُحذف في 1.43. العمود الأوسط خط زمني رأسي من خمس محطات: 1.35 تحذير عند الإقلاع، و1.37 بوابة KubeProxyIPVS، و1.40 البوابة تصير false افتراضيًا، و1.43 حذف pkg/proxy/ipvs، و1.46 حذف البوابة نفسها. اللوحة اليمنى من الصورة بعنوان «mode: nftables» وتُظهر صندوقين مكتوبًا عليهما table ip kube-proxy و table ip6 kube-proxy، وتحتهما سطر يقول إنه يتطلّب نواة Linux 5.13، ثم شريط أخضر مكتوب عليه «الافتراضي القادم».
ما الذي يُتقاعَد فعلًا: ثلاث طبقات من حالة النواة، واحدة منها فقط هي IPVS — يحلّ محلّها جدولا nftables وحدٌّ أدنى للنواة.

لهذا الترحيل صورتان: صورة يكون فيها تعديلًا من سطر واحد في ConfigMap، وصورة يكون فيها ثلاثاءً سيّئًا، والفارق بينهما كلّه في ما تفحصه أولًا. وما يلي هو الصورة كاملة: مراحل الـ KEP الخمس وأيّها تواريخ لا نوايا، وكيف تعرف ما يشغّله عنقودك حقًّا (بما في ذلك الجواب الأسوأ من ipvs)، ولماذا لم يكن هذا الوضع يومًا هو المخرج من iptables كما يُتذكَّر، ولماذا لا تؤدّي مجدولات IPVS الوظيفة التي تُستبقى من أجلها، وحدّ النواة الأدنى وأي صور العقد تتجاوزه، والتغيّرات السلوكية الأربعة الموثّقة، ونشر على مستوى العقدة الواحدة، والحالة التي يجب تنظيفها يدويًا، ونص تحقّق يعيد رمز خروج غير صفري، وتراجع من أمرين اثنين بصدق.

إهمال واحد في 1.37، وهذا هو

لا يقول أيٌّ من الأعراض أدناه كلمة إهمال، ولهذا يُلاحَظ هذا الأمر متأخرًا — عادةً على يد من يتولّى الترقية إلى 1.40، بعد ثلاثة إصدارات من أول ظهور للتحذير. الوضع لا يتدهور. يظل يعمل عملًا تامًّا حتى الإصدار الذي لا يكون فيه موجودًا، والتحذيرات في ما بين ذلك تذهب إلى سجلّ لا يبحث فيه أحد.[rel137]

ما تراهما يعنيه فعلًاأين يُعالَج
يسجّل kube-proxy The ipvs proxier is now deprecated عند كل إقلاعالمرحلة الأولى من KEP-5495، وقد شُحنت في Kubernetes 1.35. لا شيء معطوب؛ الساعة بدأت قبل إصدارين.المراحل الخمس
mode: فارغ في ConfigMap الخاص بـ kube-proxyالعنقود لم يختر مستوى بيانات. سيغيّر المنبع الافتراضي الموصى به من iptables إلى nftables، والعنقود غير المثبَّت يتبعه.أي وضع
بعد الترقية إلى 1.40 يخرج kube-proxy بخطأ يسرد الأوضاع الصالحةالمرحلة الثالثة. صارت بوابة KubeProxyIPVS افتراضيًا false ولم يُعِدها أحد.المراحل الخمس
توقّف NodePort عن الإجابة على واجهة ثانوية مباشرةً بعد التبديلوضع nftables يبدأ من --nodeport-addresses primary. وهذا أشيع انحدار في هذا الترحيل بفارق كبير.ما الذي يتغيّر
حركة المرور إلى عنوان ClusterIP واحد تُسقَط، على عقدة واحدة فقطعناوين عالقة على kube-ipvs0 متبقّية بلا قواعد IPVS خلفها. لا تزال العقدة تجيب على ARP لعنوان لم تعد تخدمه.البقايا

السطر الثاني من ذلك الجدول يستحق قراءتين، لأنه لا علاقة له بـ IPVS إطلاقًا وهو يطال عناقيد لم تستعمله يومًا. الوضع الافتراضي في Kubernetes 1.37 لا يزال iptables، وتقول الوثائق صراحةً إن إصدارًا قادمًا سيغيّر ذلك الافتراضي إلى nftables. فإن كان إعداد kube-proxy لديك لا يسمّي وضعًا، فأنت لست على iptables بقرار — أنت على ما يوصي به المنبع في تلك اللحظة، ولديك استبدال لمستوى البيانات مجدوَل على تقويم شخص آخر.[vips]

توصية المنبع نفسه، وهي أرخص ما في هذا المقال: تجنّبًا لتغيّر خلفية الوكيل في العنقود على نحو غير متوقّع أثناء ترقية، ينبغي أن تتأكّد من أن كل عناقيدك لديها إعداد kube-proxy يحدّد صراحةً الوضع الذي يجب استعماله. هذا تغيير من خمس دقائق، ويستحق أن يُجرى اليوم أيًّا كان الوضع الذي ستستقرّ عليه.

المراحل الخمس، وأيٌّ منها تواريخ

يضع KEP-5495 هذا في خمس مراحل، والانضباط المفيد هو أن تفصل ما هو إعلان عمّا هو تاريخ. المرحلتان الأولى والثانية جرتا بالفعل ولم تغيّرا شيئًا في طريقة عمل أي عنقود. المرحلة الثالثة تغيّر قيمة افتراضية، أي أنها تغيّر ما يحدث لعنقود لم يفعل فيه أحد شيئًا. والمرحلة الرابعة تحذف الشيفرة، وهي الوحيدة التي لا رجعة فيها.[kep5495]

المرحلةKubernetesما الذي يتغيّرما الذي يطلبه منك
11.35يحذّر kube-proxy عند الإقلاع في وضع ipvs؛ وتُوسَم الوثائق بأنه مهجور. ونُقلت إصلاحات nftables إلى 1.33 و1.34 كي تستطيع العناقيد الأقدم الترحيل أيضًا.أن تنتبه. لا شيء غير ذلك.
21.37تُضاف بوابة الميزة KubeProxyIPVS: في مرحلة GA، وقيمتها الافتراضية true. والسلوك دون تغيير.أن تخطّط. هذا آخر إصدار هادئ.
31.40تنقلب البوابة إلى false افتراضيًا. و kube-proxy الذي يُشغَّل في وضع ipvs دون تجاوزها يخرج بخطأ يسرد الأوضاع الصالحة.أن تكون قد انتهيت — أو أن تضبط البوابة فتشتري ثلاثة إصدارات فرعية.
41.43يُحذف pkg/proxy/ipvs من الشجرة. ولم تعد بوابة الميزة قادرة على إعادته؛ وتغادر بقية الإشارات إليه الوثائق.لا شيء. لم يعد هناك وضع ipvs.
51.46تُحذف بوابة الميزة نفسها.لا يهمّ إلا الأدوات التي تستبطن البوابات.

سطران من ذلك الجدول يستحقّان تعليقًا. مرحلة التنظيف في 1.46 موجودة في الـ KEP وغائبة عن كل تغطية هذا الإهمال تقريبًا، وهي لا تهمّ إلا إن كنت تكتب أدوات تستبطن بوابات الميزات — فالبوابة حتى ذلك الحين بوابة حقيقية مُدرَجة في مرحلة GA. أما السبب الذي أعلنته SIG Network لفعل هذا كلّه فيستحق أن يُعرف، لأنه ليس الأداء: ليس لدى المجموعة مشرفون على دراية بشيفرة خلفية ipvs، وهي تحيل منذ مدة كل من يبلّغ عن علّة في ipvs إلى الانتقال إلى nftables. وخلفية لا يصلحها أحد هي عبء مهما قالت قياسات أدائها في الماضي.[depol][k8srel]

في أي وضع يعمل هذا العنقود فعلًا

ابدأ ممّا هو مضبوط فعلًا، لا ممّا يقوله دليل التشغيل، لأن هذين مستندان مختلفان في أي عنقود أقدم من سنتين تقريبًا. هناك ثلاثة أجوبة، والثالث هو المثير للاهتمام: ipvs، أو iptables، أو لا شيء على الإطلاق.[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

ثم اعثر على التحذير، فهو في سجلّاتك منذ Kubernetes 1.35 وهو أرخص دليل على أن هذا الأمر يخصّك. ويستحق أن تحدّد موضع بوابة الميزة المضافة في 1.37 في الجولة نفسها، لا لأنك تحتاج إلى ضبطها — فهي مفعّلة افتراضيًا ولا تغيّر شيئًا في هذا الإصدار — بل لأنها الذراع التي ستُعرض عليك في 1.40، ومن الأفضل أن تعرف مسبقًا أنها تمدّ الموعد النهائي ثلاثة إصدارات فرعية لا أن تلغيه.[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'

ما هو وضع ipvs، في مقابل ما يُتذكَّر عنه

يستحق الأمر دقّة في وصف ما يُتقاعَد، لأن الرواية الشائعة خاطئة على نحو يغيّر القرار. أُضيف وضع ipvs في Kubernetes 1.8 هربًا من كلفة قائمة قواعد تتناسب طرديًا مع عدد الخدمات. وقد فعل ذلك. أما ما لم يفعله قط فهو أن يحلّ محلّ iptables: فواجهة IPVS في النواة وحدها لا تستطيع التعبير عن كامل واجهة Service في Kubernetes — قرارات الـ masquerade، وترشيح NodePort، ونطاقات المصدر في LoadBalancer، وسلوك الرفض لخدمة بلا نقاط طرفية — ولذلك يقود هذا الوضع iptables و ipset من تحت جدول IPVS. وتقول رسالة إصدار v1.37 هذا بالنص. عُدّ القواعد على عقدتك أنت إن كنت تفضّل ألّا تصدّق أحدًا.[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.

النصف الثاني من الرواية الشائعة هو المجدولات، وهي السبب الذي يُبقي معظم العناقيد التي لا تزال على ipvs عليه. ضبط أحدهم scheduler: "lc" قبل سنوات، وصار مفهومًا منذئذ أن العنقود يوزّع الحمل بأقلّ الاتصالات. وهو لا يفعل، والسبب بنيوي لا علّة برمجية. يعمل kube-proxy على كل عقدة، ولكل نسخة منه جدول IPVS خاص بها لا يحصي إلا الاتصالات التي فتحتها هذه العقدة. أي أن «أقلّ الاتصالات» تُحسب لكل عقدة عميلة على حدة: مع عشرين عقدة مشغولة لديك عشرون قرارًا محليًا مستقلًا لا قرار عالمي واحد، وحركة المرور القادمة من خارج العنقود ليست في أي من تلك الحسابات. وقد نبّهت SIG Network إلى سوء الفهم هذا صراحةً حين كتبت الإهمال.[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"'
scheduler في IPVSما يفعله على عقدة واحدةما يعنيه ذلك للعنقود
rr (الافتراضي)توزيع دوري على قائمة النقاط الطرفية لدى هذه العقدة.عمليًا هو ما يفعله وضعا iptables و nftables أصلًا باختيار خلفية عشوائيًا. لا شيء يُفقد هنا.
lc و wlc و sed و nqأقلّ الاتصالات النشطة، كما تحصيها هذه العقدة.ليست أقلّ اتصالات على مستوى العنقود. فـ N عقدة عميلة مشغولة تتّخذ N قرارًا محليًا مستقلًا، وحركة المرور من خارج العنقود ليست في أيٍّ منها.
sh و dhتجزئة على عنوان المصدر أو الوجهة.كثيرًا ما يُخلَط بينها وبين لصق الجلسة. والمكافئ المدعوم هو .spec.sessionAffinity: ClientIP، وهو لكل خدمة وينجو من الترحيل.
lblc و lblcrأقلّ الاتصالات مع مراعاة المحلّية.المحلّية في Kubernetes هي .spec.internalTrafficPolicy: Local، وهو ما تعرفه الواجهة فعلًا ولا يستطيع الـ scheduler رؤيته.
mh (Maglev)تجزئة متّسقة.يضبط kube-proxy دائمًا mh-port ولا يفعّل mh-fallback أبدًا، فيتصرّف عمليًا كتجزئة مصدر مع المنافذ.

ولا يوجد كذلك scheduler لكل خدمة، ولم يوجد قط. فـ ipvs.scheduler حقل واحد في إعداد kube-proxy ينطبق على كل خدمة على كل عقدة — وهو ليس جزءًا من واجهة Service، ولا يغيّره أي وسم توضيحي. وهذا يستحق وقفة، لأنه يعني أن الشيئين اللذين يُلجأ إلى المجدولات فعلًا من أجلهما، وهما لصق العميل بخلفية بعينها ومحلّية العقدة، لهما جوابان مدعومان في الواجهة ينجوان من هذا الترحيل دون أن يُمسّا: sessionAffinity: ClientIP و internalTrafficPolicy: Local.[svc][stp]

الوضعحالته في 1.37النواةيستعمل iptables من تحتهأين ينتهي
iptablesالافتراضي. غير مهجور.أي نواةنعم، بحكم التعريفيتوقّف عن كونه الافتراضي في وقت ما؛ ولم يُعلَن أي حذف.
ipvsمهجور منذ 1.35؛ وأُضيفت بوابة ميزة في 1.37أي نواةنعم — الـ masquerade، وترشيح NodePort، ونطاقات مصدر LoadBalancer، وقواعد الرفضمُطفأ افتراضيًا في 1.40، ومحذوفة شيفرته في 1.43.
nftablesمستقرّ منذ 1.33؛ وهو البديل الموصى به لـ ipvs5.13 أو أحدثلايصير الافتراضي في إصدار قادم.
kernelspaceعقد Windows فقطلا يمسّه شيء من هذا كلّه.

هل تستطيع هذه العقد تشغيل وضع nftables

والآن القيد الصلب الوحيد. يتطلّب وضع nftables نواة Linux 5.13 أو أحدث، ولا يوجد دعم جزئي ولا تراجع تلقائي — دون ذلك لن يعمل kube-proxy في هذا الوضع أصلًا. وهذا هو الفحص الذي يقرّر ما إذا كان هذا الترحيل تعديلًا في ConfigMap أم مشروعًا لإعادة بناء صور العقد، فأجرِه قبل كتابة أي خطة.[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
صورة العقدةالنواة التي تشحنها افتراضيًاوضع nftables
RHEL 9 و Rocky Linux 9 و AlmaLinux 95.14نعم
RHEL 106.12نعم
RHEL 8 و CentOS 74.18 وأقدملا — هذه إعادة بناء لصورة العقدة
Ubuntu 24.04 LTS6.8نعم
Ubuntu 22.04 LTS5.15نعم
Ubuntu 20.04 LTS5.4 في الإصدار العام، و5.15 مع HWEعلى نواة HWE فقط
Debian 126.1نعم
Debian 115.10لا
Amazon Linux 20236.1نعم

إن لم تتجاوز بعض العقد هذا الحدّ، فالقراءة الصادقة عادةً أن صورة العقدة انتهى عمرها لا أن النواة تحتاج ترقيعًا، والإصلاح الصحيح إعادة بناء. وإن تعذّرت إعادة البناء قبل 1.40، فالبديل هو وضع iptables لا بوابة الميزة: فخلفية iptables غير مهجورة، وقد تحسّن أداؤها تحسّنًا كبيرًا بعد إدخال وضع ipvs، والمنبع يوصي بها على ipvs لهذه الحالة بالذات. ويستحق أن يُضاف لمن يزن التوقيت: يلاحظ نصّ الإهمال أن كل نواة أقدم من أن تشغّل وضع nftables تغادر الدعم طويل الأمد بنهاية 2026، وهي الحجّة التي تقول إن الاعتراض على النواة له تاريخ انتهاء خاص به.[kernrel][kep5495]

أربعة سلوكيات تختلف بعد الانتقال

تتغيّر أربعة سلوكيات، ثلاثة منها موثّقة تحت عنوان — الانتقال من وضع iptables إلى nftables — لا سبب لدى مستخدمي ipvs لأن يكونوا قد قرأوه. وهي تنطبق هنا انطباقًا تامًّا، لأنها خصائص الوجهة لا خصائص المصدر.[vips]

السلوكوضع iptables / ipvsوضع nftablesما العمل حياله
عناوين إصغاء NodePortكل العناوين المحلية، ما لم تُضيَّق--nodeport-addresses primary افتراضيًادقّق أي العناوين مستعملة فعلًا، ثم اضبط الخيار صراحةً إن كنت تحتاج أكثر من العنوان الأساسي.
NodePort على 127.0.0.1يعمل في وضع iptables افتراضيًاغير متاح؛ وأُعيد في 1.37 خلف بوابة ألفااقرأ kubeproxy_iptables_localhost_nodeports_accepted_packets_total قبل أن تقرّر أنك لا تحتاجه.
الجدار الناري المحلييضيف kube-proxy قواعد قبول لكل NodePortلا يفعل شيئًااسمح بمدى NodePort في جدارك الناري على المضيف.
علّة إعادة تعيين conntrack قبل 6.1الحلّ البديل مثبَّتغير مثبَّت افتراضيًاافحص kubeproxy_iptables_ct_state_invalid_dropped_packets_total؛ وإن كان غير صفري فاحمل معك --conntrack-tcp-be-liberal.
قواعدك أنت على سلاسل kube-proxyKUBE-SERVICES و KUBE-SEP-* موجودة في iptablesالحالة تسكن في table ip kube-proxy / table ip6 kube-proxyلم تكن واجهة برمجية يومًا. علّق جدولك أنت عند أولوية netfilter نفسها بدلًا من ذلك.
  • تتوقّف منافذ NodePort عن الإصغاء على كل عنوان محلي. في وضعَي iptables و ipvs تكون خدمة type: NodePort قابلة للوصول على كل عناوين IP المحلية ما لم تكن قد ضيّقتها. أما وضع nftables فيبدأ من --nodeport-addresses primary، أي عنوان العقدة الأساسي من نوع IPv4 و/أو IPv6 كما هو في كائن Node ولا شيء غيره. فكل ما يصل إلى NodePort عبر بطاقة شبكة ثانوية أو عنوان إدارة أو عنوان VIP عائم يتوقّف عن العمل. اضبط الخيار صراحةً إن كنت تحتاج المدى القديم — و 0.0.0.0/0 يعيده — لكن دقّق أولًا، لأن القيمة الافتراضية هي ما يظن المنبع أن الناس أرادوه فعلًا.
  • منافذ NodePort على localhost حالة منفصلة، وقد غيّرتها 1.37. كان الاتصال بـ 127.0.0.1:<nodePort> يعمل في وضع iptables ولا يعمل في وضع nftables إطلاقًا. ومنذ Kubernetes 1.37 صار ممكنًا، خلف البوابة الألفا KubeProxyNFTablesLocalhostNodePorts ومع ضبط nodePortAddresses على primary,localhost. وقبل أن تقرّر ما إذا كان هذا يهمّك، اقرأ العدّاد: فـ kube-proxy يحصي منذ البداية الحزم المقبولة على منافذ NodePort عبر الاسترجاع الداخلي.
  • يتوقّف kube-proxy عن فتح جدارك الناري نيابةً عنك. يضيف وضع iptables قواعد قبول لكل NodePort، على افتراض أن جدارًا ناريًا محليًا مفرطًا في الصرامة كان سيحجبها. وهذه المقاربة لا يمكن أن تنجح أمام جدار ناري مبني على nftables، ولذلك لا يفعل وضع nftables شيئًا هنا إطلاقًا. فإن كان لديك جدار ناري على المضيف، فعليه الآن أن يسمح بمدى NodePort بنفسه — وهو ما كان ينبغي أن يكون عليه الحال دائمًا على الأرجح، ولا يزال تغييرًا.
  • حلّ conntrack البديل غير مثبَّت افتراضيًا. في النوى الأقدم من 6.1 علّة قد تُعيد تعيين اتصالات TCP طويلة العمر الموجَّهة إلى عناوين الخدمات. يثبّت وضع iptables حلًّا بديلًا؛ ثم تبيّن لاحقًا أن ذلك الحلّ يسبّب مشكلات خاصة به، فتركه وضع nftables. وحاجتك إليه أمر يُقاس لا أمر يُقدَّر — فالعدّاد موجود في المقاييس — وإن كنت تحتاجه فإن --conntrack-tcp-be-liberal هو الطريق المدعوم لاستعادة السلوك.
  • كل ما لديك كان يطابق سلاسل kube-proxy صار معطوبًا. إن كان نص جدار ناري أو خطّاف CNI أو قاعدة مراقبة يشير إلى KUBE-SERVICES أو KUBE-SEP-* بالاسم، فتلك السلاسل اختفت: حالة kube-proxy تسكن الآن في table ip kube-proxy و table ip6 kube-proxy. لم يكن هذا مدعومًا يومًا — وللمنبع تدوينة قائمة بهذا العنوان بالضبط — لكن الأجدر أن تبحث عنه قبل النشر لا بعده، لأنه يفشل بصمت.
# 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

النمط في الخمسة واحد: ليس أيٌّ منها فشلًا عند الإقلاع. يقلع kube-proxy، ويبلّغ أنه سليم، ويخدم معظم حركة المرور خدمةً صحيحة، ثم يتوقّف مسار واحد بعينه عن العمل. لهذا يساوي الفحص القبْلي أدناه أكثر من النشر نفسه، ولهذا تبقى العقدة الأولى وحدها ليوم عمل كامل.[reset][ctsysctl]

التبديل، عقدةً بعد عقدة

التعديل ثلاثة أسطر. والانضباط في حذف كتلة ipvs بدل تركها إلى جانب الوضع الجديد، لأن الإعداد الميّت هو الطريقة التي يستنتج بها من يأتي بعدك أن العنقود لا يزال على ipvs. وشيء واحد يجب فحصه على العناقيد المبنيّة بـ kubeadm: هذا الـ ConfigMap يُعاد توليده عند kubeadm upgrade، فالتغيير الذي يُجرى بـ kubectl وحده قد يُنقض بصمت عند الترقية التالية. أجرِه في إعداد العنقود كذلك.[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 -

ثم انشره كما تنشر أي شيء يملك مستوى البيانات على كل عقدة، أي عقدة واحدة، ثم مجمّعًا، ثم الأسطول. وهذا التغيير رخيص التجريب على نحو غير معتاد، لأن الحالة كلها محلية بالعقدة ويُعاد بناؤها من خادم الواجهة عند كل إقلاع: لا شيء مشترك ليفسد، والعقدة التي لا تستطيع تشغيل الوضع الجديد تفشل عند الإقلاع ورقم إصدار النواة في الرسالة.[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.

ما الذي يخلّفه ipvs وراءه

التنظيف هو الموضع الذي يختلف فيه هذا الترحيل عن كل تغيير سابق لوضع kube-proxy، ويختلف فيه إلى الأفضل. كان kube-proxy يحاول أن ينظّف وراء الأوضاع الأخرى ثم توقّف — أزال KEP-2448 ذلك المنطق — لأن خلفيات iptables و ipvs و userspace كانت جميعًا تكتب في بعض السلاسل نفسها، فتنظيف قواعد وضعٍ ما كان يحذف قواعد الوضع العامل أيضًا. أما وضع nftables فلا يشارك شيئًا: حالته كلها في جدوليه الخاصّين. لهذا صُمِّم التبديل في هذا الاتجاه ليحذف القواعد القديمة عند الإقلاع، ولهذا يكون التنظيف اليدوي آمنًا حين يلزم.[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

بقيّة واحدة تستحق انتباهًا أكثر من غيرها. kube-ipvs0 واجهة وهمية يستعملها وضع ipvs ليربط كل عنوان ClusterIP — وكل عنوان LoadBalancer — بصيغة /32 على العقدة، وهو السبب كلّه في أن MetalLB في وضع الطبقة الثانية توثّق strictARP شرطًا لعناقيد ipvs. ووضع nftables لا ينشئ واجهة كهذه، فيسقط ذلك الشرط ويصير الإعداد ميّتًا. لكن إن نجت الواجهة من التبديل والعناوين لا تزال عليها ولا قواعد IPVS خلفها، فإن العقدة تظل تدّعي تلك العناوين وتجيب على ARP من أجلها بينما تُسقط حركة المرور. وهذا ثقب أسود على عقدة واحدة يظهر بمظهر عطل متقطّع في التطبيق، وهو أفضل سبب منفرد للتحقّق من التنظيف بدل افتراضه.[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.

أن تتحقّق بدل أن تأمل

للتحقّق شكل محدّد في هذا الترحيل تمليه أنماط الأعطال: كل ما يعطب تقريبًا يترك kube-proxy عاملًا وسليمًا. فالتأكّد من أن الـ Pod في الحالة Running لا يثبت شيئًا إطلاقًا. النص أدناه يفحص الوضع الذي اختاره kube-proxy فعلًا لا الذي في الـ ConfigMap، وأن لا حالة ipvs نجت، وأن الواجهة الوهمية اختفت، وأن مزامنة القواعد تنجح — ثم يفعل الشيء الوحيد الذي لا يستطيعه أي فحص، وهو فتح اتصال حقيقي بخدمة حقيقية من Pod على تلك العقدة.[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

وبعد ذلك، ثلاثة عدّادات تستحق تنبيهًا لأسبوعين. ولاحظ ما ينقص من القائمة: مرجع مقاييس Kubernetes فيه عدّادات خاصة بـ iptables وعدّادات خاصة بـ nftables ولا شيء إطلاقًا لـ ipvs. فأي مراقبة لديك اليوم لوضع ipvs بنيتَها أنت بيدك من ipvsadm — وهذا توضيح صغير وملموس لحجّة الصيانة التي وراء هذا الإهمال كلّه.[metrics]

المقياسما الذي يخبرك بهيستحق تنبيهًا
kubeproxy_sync_proxy_rules_nftables_sync_failures_totalkube-proxy لا يستطيع كتابة مجموعة القواعد، أي أن مستوى البيانات ينحرف عن خادم الواجهة.أي ارتفاع، وبصفة دائمة.
kubeproxy_sync_proxy_rules_nftables_cleanup_failures_totalلا يستطيع حذف قواعد يعتقد أنها عالقة — وهي عادةً بقايا الوضع القديم، أو شيء آخر يكتب في الجداول نفسها.أي ارتفاع خلال الأسبوعين التاليين للتبديل.
kubeproxy_sync_proxy_rules_last_timestamp_secondsمتى طابقت قواعد العقدة خادم الواجهة آخر مرة. وهذا هو الذي يمسك العطل الصامت.أكثر من خمس دقائق في الماضي.
kubeproxy_iptables_localhost_nodeports_accepted_packets_totalشيء ما يتّصل بـ NodePort عبر الاسترجاع الداخلي.قيمة غير صفرية قبل التبديل، بوصفها مانعًا.
kubeproxy_sync_proxy_rules_duration_secondsكم تستغرق مزامنة كاملة. وهي حجّة الأداء لهذا الترحيل، إن كان لديك من الخدمات ما يكفي لحجّة.خذ خطّ أساس قبل وبعد؛ بلا تنبيه.
# 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.

التراجع، وما الذي يشتريه فعلًا

التراجع هنا رخيص بصدق، وهو ما لا يصحّ في بقيّة عمليات الترحيل التي تهبط على هذه العقد هذا العام. لا بيانات تُحوَّل، ولا صيغة على القرص تُخفَّض، ولا حالة تبقى بعد إعادة تشغيل: التغيير كلّه مفتاح واحد في ConfigMap زائد حالة نواة محلية بالعقدة يعيد kube-proxy بناءها من خادم الواجهة عند كل إقلاع. أعِد المفتاح القديم، واحذف الـ Pod على تلك العقدة، وتعود العقدة إلى ما كانت عليه في أقل من دقيقة.[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.

تحفّظان، وكلاهما عن النطاق لا عن الآلية. صُمِّم kube-proxy في وضع ipvs ليحذف قواعد nftables التي يجدها عند الإقلاع، لكن التراجع يهمّ أكثر ما يهمّ حين تسيء الخلفية الجديدة التصرّف بالضبط، فيستحق أن تعرف النسخة اليدوية: كل قاعدة يملكها kube-proxy داخل جدوليه الخاصّين، وأمرا nft delete table يحذفانها كلها. وكن واضحًا في ما يشتريه التراجع فعلًا. إنه ينهي نافذة صيانة سيّئة. وهو لا يحرّك 1.40، ولا يحرّك 1.43.[skew]

الجواب الآخر: ألّا يوجد kube-proxy أصلًا

لهذا الإهمال جواب ثانٍ يستحق أن يُسمّى، لأنه لبعض العناقيد الجواب الأفضل: أن تتوقّف عن تشغيل kube-proxy. يطبّق كلٌّ من Cilium و Calico خدمات Kubernetes مباشرةً في eBPF، ببحث في جدول تجزئة بدل قائمة قواعد، ويوثّق كلاهما التشغيل مع إزالة kube-proxy لا إلى جانبه. فإن كنت تشغّل أحدهما أصلًا، فقد يكون إهمال ipvs دعوةً إلى إجراء تغيير كنت ستجريه على أي حال.[cilium]

وهو تغيير أكبر بكثير من الذي يصفه هذا المقال، وينبغي أن يُوزن على هذا الأساس. استبدال kube-proxy ينقل تطبيق الخدمات إلى الـ CNI، فيقرن مستوى بياناتك بوتيرة إصدارات ذلك المشروع، وبمتطلّباته من النواة — وهي أعلى من 5.13 لا أدنى — وبأدوات تنقيحه، وليس شيئًا يُفعل داخل نافذة إهمال تحت ضغط الوقت. والتسلسل الصادق لمعظم العناقيد هو: انتقل إلى nftables الآن لأنه تعديل في ConfigMap، ثم قيّم مسألة eBPF بمعاييرها الخاصة وعلى جدولها الخاص.[calico][netpol]

الترتيب الذي يُنفَّذ به هذا كلّه

مضغوطًا، القرار أصغر بكثير من المقال. إن كانت نواك 5.13 أو أحدث، فهذا تغيير إعداد مع نشر متأنٍّ. وإن لم تكن، فهو مشروع صور عقد، والجواب المرحلي هو وضع iptables لا بوابة الميزة. وإن كان إعداد kube-proxy لديك لا يسمّي وضعًا أصلًا، فهذا هو ما يُصلَح هذا الأسبوع بصرف النظر عن كل ما سبق.[kep5495]

أين أنتما العمل
وضع ipvs، وكل العقد على 5.13 أو أحدثبدّل إلى nftables. هذا هو المقال كلّه، وهو تعديل في ConfigMap زائد نشر متأنٍّ.
وضع ipvs، وبعض العقد دون 5.13أعِد بناء صور تلك العقد. وإن تعذّر ذلك قبل 1.40، فانقلها إلى وضع iptables بدل ضبط بوابة الميزة — فـ iptables غير مهجور والمنبع يوصي به على ipvs للنوى القديمة.
وضع iptables، بلا خطةثبّت mode: iptables صراحةً اليوم كي لا تستطيع ترقية تغييره، ثم انتقل إلى nftables حين يسمح حدّ النواة لديك.
لا وضع مضبوط إطلاقًاثبّته هذا الأسبوع، أيًّا كان الذي تريده. هذا هو البند الذي له موعد نهائي لم يختره أحد في مؤسّستك.
الـ CNI يستبدل kube-proxy أصلًالا شيء يُفعل. تأكّد من أن kube-proxy لا يعمل فعلًا بدل افتراض ذلك، لأن الاستبدالات الجزئية موجودة.
  1. ثبّت الوضع، أيًّا كان. إن كان mode: فارغًا في ConfigMap الخاص بـ kube-proxy، فاضبطه صراحةً اليوم. هذا البند الوحيد في القائمة الذي ينطبق على عناقيد لا ipvs فيها إطلاقًا، وهو البند الذي له موعد نهائي لم تختره أنت.
  2. افحص النوى قبل أن تكتب خطة. أمر واحد على الأسطول كلّه يقرّر ما إذا كان هذا عمل بعد ظهيرة أم عمل ربع سنة. العقد دون 5.13 تحتاج صورة جديدة لا ترقيعًا.
  3. قِس العدّادين اللذين يحسمان الحوافّ الحادّة. منافذ NodePort على localhost، وإسقاطات conntrack بسبب الحالة غير الصالحة، كلاهما قابل للإجابة من مقاييس kube-proxy نفسها قبل أن تغيّر أي شيء. ودقّق عناوين إصغاء NodePort في الجولة نفسها.
  4. عقدة واحدة، ويوم عمل كامل، ثم مجمّع. الانحدارات هنا ليست أعطال إقلاع؛ إنها عميل واحد على واجهة ثانوية، وهذا يحتاج وردية كاملة ليظهر. وتحقّق يدويًا من تنظيف kube-ipvs0 ومجموعات ipset على تلك العقدة الأولى.
  5. احذف كتلة ipvs وإعداد strictARP. لا يفعل أيٌّ منهما شيئًا في وضع nftables، وتركهما هو الطريقة التي يستنتج بها من يأتي بعدك أن الترحيل لم يحدث قط. ثم أجرِ التغيير نفسه حيث يُولَّد الـ ConfigMap، وإلّا نقضه kubeadm upgrade التالي.

هذا واحد من عدة تغييرات تهبط على العقد نفسها في السنة نفسها، وهي تُقرأ مجتمعةً خيرًا من متفرّقة: الانتقال من Ingress NGINX إلى Gateway API، وهو النصف الآخر من عمل الشبكات ويشترك معه في انضباط النشر نفسه؛ والترحيل من containerd 1.7 إلى 2.x، أي الـ runtime الذي تحته وله جدول تقاعد خاص به؛ والانتقال من cgroup v1 إلى cgroup v2، وهو التغيير على مستوى العقدة الذي جعلته Kubernetes إلزاميًا بالفعل. وإن كنت تزن كم تحتاج من هذا كلّه أصلًا، فإن متى لا تستخدم Kubernetes هو الوجه الآخر للحجّة.

أسئلة شائعة

هل حُذف وضع ipvs من kube-proxy في Kubernetes 1.37؟

لا. تضيف Kubernetes 1.37 بوابة الميزة KubeProxyIPVS وقيمتها الافتراضية true، فيتصرّف وضع ipvs تمامًا كما كان. والتواريخ المهمّة هي 1.40 حين تنقلب البوابة إلى false افتراضيًا فيخرج kube-proxy في وضع ipvs بخطأ ما لم تتجاوزها، ثم 1.43 حين يُحذف pkg/proxy/ipvs فلا تعود البوابة تنفع في شيء. أما البوابة نفسها فتُحذف في 1.46.

كيف أعرف أي وضع وكيل يستعمله عنقودي؟

على عنقود مبنيّ بـ kubeadm: kubectl -n kube-system get configmap kube-proxy -o jsonpath='{.data.config\.conf}' | grep 'mode:'. فإن عاد فارغًا فالعنقود لم يختر وضعًا وهو يعمل على ما يوصي به kube-proxy في تلك اللحظة — وهو iptables في 1.37 وسيصير nftables في إصدار قادم. وعلى عنقود لا يستعمل ذلك الـ ConfigMap، اقرأ وسائط الـ DaemonSet بدلًا من ذلك، أو شغّل ipvsadm -L -n على عقدة.

هل يجب أن أنتقل إلى nftables، أم يمكنني استعمال وضع iptables؟

وضع iptables وجهة مشروعة. فهو غير مهجور، وليس له حدّ أدنى للنواة، وقد تحسّن أداؤه تحسّنًا كبيرًا في السنوات التي تلت إدخال وضع ipvs — والمنبع يوصي به صراحةً على ipvs للأنظمة الأقدم من أن تشغّل وضع nftables. و nftables هو الهدف الأفضل حيث تسمح النواة، وهو الاتجاه الذي تسير إليه القيمة الافتراضية، لكن «من ipvs إلى iptables» جواب حقيقي لصور العقد القديمة لا تهرّبًا من القرار.

ما إصدار النواة الذي يحتاجه وضع nftables في kube-proxy؟

نواة Linux 5.13 أو أحدث، وعلى عقد لينكس فقط. ولا يوجد دعم جزئي: لن يعمل kube-proxy في وضع nftables دون ذلك. وهذا عمليًا يستبعد RHEL 8 و CentOS 7 و Debian 11 و Ubuntu 20.04 على النواة العامة، ويجيز RHEL 9 و RHEL 10 و Debian 12 و Ubuntu 22.04 و Ubuntu 24.04 و Amazon Linux 2023. افحص بـ kubectl get nodes -o custom-columns=NAME:.metadata.name,KERNEL:.status.nodeInfo.kernelVersion لا باسم التوزيعة.

هل سأفقد الـ scheduler الذي ضبطته في IPVS؟

ستفقد الإعداد، ولن تفقد على الأرجح السلوك الذي ظننت أنه يمنحك إياه. فـ ipvs.scheduler حقل واحد للعنقود كلّه لا لكل خدمة، وجدول IPVS في كل عقدة لا يحصي إلا الاتصالات التي فتحتها تلك العقدة — أي أن lc هو أقلّ الاتصالات لكل عقدة عميلة لا عبر العنقود. فإن كان مرادك لصق العميل بخلفية بعينها، فذلك .spec.sessionAffinity: ClientIP على الخدمة. وإن كان محلّية العقدة، فذلك .spec.internalTrafficPolicy: Local. وكلاهما لا يمسّه هذا الترحيل.

هل ما زلت أحتاج strictARP بعد الانتقال إلى وضع nftables؟

لا. وُجد strictARP لأن وضع ipvs يربط كل عنوان ClusterIP وكل عنوان LoadBalancer على الواجهة الوهمية kube-ipvs0، فتجيب العقدة على ARP لعناوين يحاول MetalLB التحكّم فيها. ووضع nftables لا ينشئ تلك الواجهة إطلاقًا، فتزول المشكلة الأصلية ويصير الإعداد ميّتًا — احذفه مع بقيّة كتلة ipvs. لكن تحقّق فعلًا من أن kube-ipvs0 اختفت على كل عقدة مُرحَّلة، لأن واجهة متبقّية بعناوين عالقة ستبتلع حركة المرور.

هل التبديل بين الأوضاع آمن على عنقود يعمل؟

التبديل بين وضع من عائلة iptables ووضع nftables مصمَّم ليكون كذلك: يحتفظ وضع nftables بكل حالته في جدوليه الخاصّين، ويحذف قواعد iptables و ipvs التي يجدها عند الإقلاع، والعكس صحيح عند التراجع. وهذا مختلف تحديدًا عن التبديل بين iptables و ipvs، فقد كانا يتشاركان السلاسل، ولهذا حُذف تنظيف kube-proxy التلقائي في KEP-2448. ويبقى مع ذلك تغييرًا في مستوى البيانات على كل عقدة يمسّها، فاعزل العقدة، وأفرغها، وأعِد تشغيل Pod واحد من kube-proxy، وتحقّق، ثم اترك تلك العقدة وحدها يوم عمل كامل.

ما الذي يعطب في هذا الترحيل أكثر من غيره؟

الوصول إلى NodePort. يبدأ وضع nftables من --nodeport-addresses primary، فـ NodePort كان يُوصَل إليه عبر بطاقة شبكة ثانوية أو عنوان إدارة أو عنوان VIP عائم أو عبر 127.0.0.1 يتوقّف عن الإجابة — بينما يبقى kube-proxy سليمًا ويستمر كل شيء آخر في العمل. والحالتان كلتاهما قابلتان للقياس مسبقًا من مقاييس kube-proxy نفسها ومن قيمة nodePortAddresses الحالية.

هل أستبدل kube-proxy بـ Cilium أو Calico على eBPF بدلًا من ذلك؟

خيار حقيقي، وهو لبعض العناقيد الخيار الأفضل، لكنه تغيير أكبر بكثير من تبديل وضع: فهو ينقل تطبيق الخدمات إلى الـ CNI ويقرن مستوى بياناتك بإصدارات ذلك المشروع ومتطلّباته من النواة وأدوات تنقيحه. وأن تقرّره تحت ضغط الإهمال هو ترتيب مقلوب. الانتقال إلى nftables تعديل في ConfigMap يُلغي الموعد النهائي؛ وقيّم مسألة eBPF بعد ذلك، على جدولها الخاص.

كيف أتراجع إن أساء وضع nftables التصرّف؟

أعِد مفتاح الـ ConfigMap السابق واحذف Pod الخاص بـ kube-proxy على تلك العقدة؛ فالوضع القديم يعيد بناء حالته من خادم الواجهة عند الإقلاع، وتعود العقدة في أقل من دقيقة. وإن لم يعمل تنظيف kube-proxy نفسه — وهي بالضبط الحالة التي يهمّ فيها التراجع — فكل قاعدة يملكها داخل جدوليه الخاصّين، فـ nft delete table ip kube-proxy و nft delete table ip6 kube-proxy يحذفانها كلها. التراجع ينهي نافذة صيانة سيّئة؛ وهو لا يحرّك 1.40 ولا 1.43.

ملاحظة على مستوى الإصدار، لأن دفتر حساب 1.37 ليس كما تصفه معظم التغطية: التغيير الذي قد يترك الحاويات فعليًا في ContainerCreating هو وصول SELinuxMount إلى GA، بينما جاء فشل cgroup v1 في 1.35، وقيد الـ static pods في 1.34، أما حافة containerd فما زالت أمامك في 1.38. ما ينكسر فعليًا عند الترقية إلى Kubernetes 1.37 يفصل الأعمدة الثلاثة ويقدّم التدقيق الذي يجب تنفيذه قبل الترقية لا بعدها.

المصادر

مصادر أوّلية فقط، فُحصت في 27 أغسطس 2026. وحيث تختلف وثائق Kubernetes مع كتابة ثانوية على رقم إصدار في هذا الإهمال، فالوثائق والـ KEP هما الجديران بالثقة — فقد نُقّحت مراحل هذا الإهمال مرة واحدة بالفعل.

  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

Was this useful?