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

ليس كل هذا جديدًا في 1.37.

صدر Kubernetes 1.37 في 26 أغسطس 2026. معظم التغييرات المنسوبة إليه هبطت في 1.34 و1.35، وواحد منها لا يزال يبعد إصدارًا كاملًا عند 1.38، والتغيير الوحيد القادر فعلًا على ترك Pods عالقة في ContainerCreating لا يكاد يُذكر. هنا الجرد كاملًا، والتدقيق الذي يسبق الترقية، ودليل التنفيذ.

·24 دقيقة قراءة
  • Kubernetes
  • SELinux
  • الترقيات
  • المنصّات

صدر Kubernetes 1.37 في 26 أغسطس 2026، وللتغطية التي دارت حوله شكلٌ يستحق الانتباه. تُنسب إلى هذا الإصدار تغييرات عدّة لا تخصّه: رفض kubelet الإقلاع فوق cgroup v1 (وقد انقلبت تلك القيمة الافتراضية في 1.35)، وفقدان الـ static pods مراجعها إلى Secret و ConfigMap (وهو مفعّل افتراضيًا منذ 1.34)، وإزالة وضع ipvs من kube-proxy (وهو يحمل إشعار إهمال منذ 1.35، والإزالة مستهدَفة عند 1.43). وثمّة تغيير واحد يُروى مقلوبًا في الاتجاه المعاكس: containerd 1.x لا يزال يعمل مع kubelet من 1.37، والحافة عند 1.38. وفي المقابل، التغيير الوحيد في هذا الإصدار القادر فعلًا على ترك Pods عالقة في ContainerCreating يوم الترقية — بلوغ SELinuxMount مرتبة GA وتفعيله افتراضيًا — لا ينال أكثر من فقرة، لأنه غير مرئي لكل من لا تشغّل عناقيدهم SELinux.

صورة غلاف من ثلاثة أعمدة بعنوان «Kubernetes 1.37: فرز الجرد». العمود الأول بعنوان «هبط قبل ذلك» يسرد رفض kubelet الإقلاع على عقد cgroup v1 منذ 1.35، وفقدان الـ static pods مراجعها إلى Secret و ConfigMap منذ 1.34، ووضع ipvs في kube-proxy يحمل إشعار إهمال منذ 1.35. العمود الثاني بعنوان «جديد فعلًا في 1.37» يسرد بلوغ SELinuxMount مرتبة GA وتفعيله افتراضيًا مع علامة تحذير تقول «قد تعلق الـ Pods في ContainerCreating»، وبوابة الميزة KubeProxyIPVS موسومة بالإهمال، وتخرّج metrics.k8s.io إلى v1. العمود الثالث بعنوان «لا يزال أمامك» يسرد إسقاط مسار CRI الاحتياطي لـ containerd 1.x في 1.38، وصيرورة بوابة ipvs false افتراضيًا في 1.40، وإزالة دعم ipvs في 1.43. وفي شريط أسفل الصورة عبارة «صدر في 26 أغسطس 2026».
فرز الجرد: ما كان قد هبط قبل 26 أغسطس 2026، وما هو جديد فعلًا في 1.37، وما لا يزال أمامك في 1.38 وما بعدها.

هذا التمييز ليس تنطّعًا، فكل عمود منه يستدعي عملًا مختلفًا. إن فاجأك تغيير cgroup على 1.37، فأنت متأخّر بإصدارين أصلًا، والعلاج إعادة إقلاع لكل عقدة لا تراجعٌ عن الترقية. وإن أعدت بناء runtime الحاويات لديك في نوبة هلع لأن أحدهم أخبرك أن containerd 1.x توقّف عن العمل، فتكون قد أنفقت نافذة صيانة كاملة على موعد لا يزال يبعد إصدارًا — موعد حقيقي، لكنه ليس اليوم. وإن فاجأك تغيير SELinux على 1.37، فلديك Pods لن تقلع، والتدقيق الذي كان سيكشفها يصير أصعب كثيرًا بعد أن تتجاوز الترقية. لذلك يفعل هذا المقال شيئين: يفصل الجرد بأمانة، ثم يتعمّق في المواضع التي تحتاج عملًا — تدقيق SELinux ومنفذ الخروج منه كاملَين، والجدولان الحقيقيان لـ ipvs و containerd، ودليل تنفيذ يضع الخطوات غير القابلة للعكس في مواضعها الصحيحة.

الأعطال، وإلى أي إصدار ينتمي كلٌّ منها

ابدأ بما ستراه فعلًا، فلا واحد من هذه الأعطال يعلن سببه. Pod يجلس في ContainerCreating إلى الأبد على عقدة، بينما Pod مطابق له يعمل بلا مشكلة على عقدة أخرى — ذلك تعارض في تسمية SELinux، والفيصل فيه أيّهما وصل إلى وحدة التخزين أولًا. عقدة تعود من ترقية ولا يقلع kubelet عليها إطلاقًا — ذلك شبه مؤكّد cgroup v1، وهو أمر صحيح منذ 1.35. ووكيل مراقبة يعمل static pod منذ 2021 يفشل على عقدة واحدة بالضبط، وهي التي رقّيتها للتو — تلك هي الإشارة إلى Secret التي ما كان يُفترض أن يقدر على استعمالها أصلًا، والقيمة الافتراضية التي سلبتها منه هبطت في 1.34.[sneak]

ما تراهما يعنيه عادةًأين يُعالَج
Pod واحد عالق في ContainerCreating على عقدة واحدة، وPod مطابق له يعمل في مكان آخرتعارض في تسمية SELinux على وحدة تخزين مشتركة. الـ Pod الذي وصل إليها أولًا يمسك بالسياق الوحيد للتركيبSELinuxMount
kubelet لا يقلع إطلاقًا بعد الترقيةcgroup v1 بلا التجاوز failCgroupV1: false. وهذا صحيح منذ 1.35cgroup v1
وكيل مراقبة كان static pod منذ سنوات يفشل على العقدة المرقّاة وحدهاحقل secretRef أو configMapRef في بيان static pod. ممنوع افتراضيًا منذ 1.34، فهو يعضّ العناقيد التي تخطّت إصداراتالـ static pods
kube-proxy يسجّل تحذير إهمال وكل شيء يواصل العملوضع ipvs، مهجور منذ 1.35. و1.37 تضيف البوابة KubeProxyIPVS؛ والإزالة مستهدَفة عند 1.43ipvs
الـ Pods تعمل، والمقياس cri_losing_support غير صفري على بعض العقدcontainerd 1.x خلف المسار الاحتياطي لمشغّل cgroup عبر CRI. لا يزال مدعومًا على 1.37؛ والمسار الاحتياطي يُسقَط في 1.38containerd
خادم الـ API يسجّل تحذيرًا كلما طُبِّقت Service فيها externalIPsليست 1.37 إطلاقًا — هُجرت ExternalIPs في Service عند 1.36. لم يُزَل شيء بعد، ولا شيء يتوقّفالجرد
kubectl top و HPA يواصلان العمل بعد الترقيةصحيح. تتخرّج metrics.k8s.io إلى v1 ويبقى الإصداران مقدَّمينmetrics.k8s.io

النمط الجدير بالاستيعاب أن ترقية الإصدار تُظهر كل تغيير جرى منذ الإصدار الذي كنت واثقًا عنده، لا تغييرات الإصدار الذي تنتقل إليه فحسب. العناقيد لا تُرقّى إصدارًا فرعيًا واحدًا في كل مرة عمليًا؛ بل تجلس على 1.34 عامًا كاملًا ثم تتحرّك. وExternalIPs في Service هي المثال الحيّ على ذلك: هُجرت في 1.36، حيث بدأ خادم الـ API يحذّر عند كل استعمال لها، والواصلون إلى 1.37 من إصدارات أقدم يلاقون ذلك التحذير لأول مرة. ولم يُزَل منها شيء بعد — إذ يُتوقّع أن يُطفأ دعم kube-proxy لها افتراضيًا في 1.40 على أقرب تقدير، مع إزالة لا تسبق 1.43 — وهذا بالضبط كيف يتحوّل تغيير يبعد ثلاثة إصدارات إلى انقطاع: يصل التحذير في إصدار لم يقرأ أحد ملاحظاته. ولهذا كان القسم الثاني من هذا المقال جردًا لا ملخّصَ ملاحظات إصدار.[extip]

الجرد: الجديد في 1.37، مقابل ما هبط قبله

هذه هي المحاسبة الصادقة. العمود الأيمن هو ما يسرده فريق الإصدار نفسه في نظرته المسبقة إلى 1.37؛ والأعمدة الأخرى هي المنشأ الحقيقي للتغيير، أو الوجهة التي لا يزال يسير إليها. وكل ما يقع في صفوف «أبكر» شيء كان ينبغي أن تكون قد عالجته، وإن لم تفعل فنافذة الترقية هي التي ستخبرك؛ وكل ما يقع في صفوف «لاحقًا» تاريخٌ يوضع في تقويم، لا عملٌ لهذا الأسبوع.[sneak]

التغييريُنسب عادةً إلىيحدث فعلًا فيماذا يفعل يوم الترقية إلى 1.37
SELinuxMount يبلغ GA، مفعّلًا افتراضيًا1.371.37قد يترك Pods في ContainerCreating حيث يتشارك Pods بتسميتين مختلفتين وحدة تخزين واحدة ويكون مشغّل CSI قد أعلن قبوله. وهذا هو البند الذي يحتاج عملًا
إضافة بوابة الميزة KubeProxyIPVS موسومةً بالإهمال1.371.37لا شيء. البوابة موجودة كي يصير بالإمكان إطفاء ipvs افتراضيًا لاحقًا
kubectl run --filename/-f مهجور1.371.37تحذير فحسب، على راية كانت متجاهَلة أصلًا. يمسّ النصوص البرمجية لا العناقيد
metrics.k8s.io تتخرّج إلى v11.371.37لا شيء ينكسر. v1 وv1beta1 كلاهما مقدَّم خلال الانتقال
الـ static pods لا يجوز أن تشير إلى Secrets أو ConfigMaps1.371.34لا جديد — إلا إن كنت قد تخطّيت إصدارات، فعندها يكسر تلك الـ static pods كسرًا مباشرًا على العقدة التي رقّيتها للتو
kubelet يرفض الإقلاع على cgroup v11.371.35لا جديد. إن عضّك هنا فالعقدة تحمل failCgroupV1: false منذ 1.35
إهمال وضع ipvs في kube-proxy«أُزيل في 1.37»1.35 (تحذير فحسب)سطر واحد في سجل الإقلاع، كما هو منذ إصدارين. البوابة تصير false افتراضيًا عند 1.40؛ والإزالة عند 1.43
إهمال ExternalIPs في Service«أُزيل في 1.36»1.36 (تحذير فحسب)تحذير من خادم الـ API. الإطفاء الافتراضي لا يسبق 1.40؛ والإزالة لا تسبق 1.43
إسقاط المسار الاحتياطي لـ CRI مع containerd 1.x1.36، وأحيانًا 1.371.38لا شيء بعد. containerd 1.x لا يزال يعمل على 1.37 خلف المسار الاحتياطي، وcri_losing_support يعدّ العقد المعتمدة عليه

أنفع جملة في هذا المقال: إن كانت عقدك لا تشغّل SELinux في وضع الفرض، فالقسم الأكبر هنا لا ينطبق عليك إطلاقًا. يتخطّى kubelet مسار شيفرة SELinux كاملًا حين تكون SELinux غير متاحة أو معطّلة في النواة. تحقّق من ذلك أولًا — يكفيه أمر واحد — لأنه يحدّد ما إذا كان هذا نصف يوم عمل تدقيقي أم قراءةً في عشر دقائق.

اثنان من هذه البنود يستحقّان ملاحظة عن كيفية التحقّق منهما بنفسك بدل الأخذ بكلام أحد. ينشر مرجع بوابات الميزات حالة SELinuxMount وSELinuxChangePolicy وKubeProxyIPVS في كل إصدار على حدة، وهو أسرع طريق للتحقّق من ادّعاء عن أيّ إصدار قلب أيّ قيمة افتراضية. ولمدوّنة فريق الإصدار في كل نسخة قائمة إهمال خاصة بها؛ وقراءة ثلاث منها تستغرق عشرين دقيقة وهي استعمال لنافذة الترقية أفضل من معظم ما يُوضع فيها.[gates]

أين تقف فعلًا

قبل أن يصير أيٌّ من هذا قابلًا للتنفيذ تحتاج أربعة أرقام، وهي مستقلّة بعضها عن بعض: إصدار kubelet لكل عقدة، وهل SELinux في وضع الفرض على تلك العقدة، وأيّ إصدار من cgroup تشغّله، وفي أيّ وضع يعمل kube-proxy. وفي أسطول بأي حجم لن تكون الأجوبة موحّدة — فمجمّعات العقد تتقدّم وفق جداولها الخاصة، وسياسة انحراف الإصدارات تسمح صراحةً بأن يتأخّر kubelet عن خادم الـ API، أي أن الأسطول المختلط تركيبة مدعومة لا علامة إهمال.[skew]

# Four numbers decide how much of this article applies to you, and they are
# independent of each other. Ask for all four rather than assuming.

# 1. Control plane and kubelet versions. Node pools drift; this is normal.
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,'\
'RUNTIME:.status.nodeInfo.containerRuntimeVersion,'\
'KERNEL:.status.nodeInfo.kernelVersion,OS:.status.nodeInfo.osImage'
# NODE     KUBELET   RUNTIME              KERNEL          OS
# node-01  v1.36.4   containerd://2.3.2   6.8.0-51        Ubuntu 24.04.3 LTS
# node-02  v1.35.9   containerd://1.7.28  5.15.0-118      Ubuntu 22.04.5 LTS   <- two problems

# 2. Is SELinux actually in play? If the answer is "no" on every node, the
#    largest section of this article does not apply to you at all.
for n in $(kubectl get nodes -o name); do
  printf '%-22s ' "${n#node/}"
  kubectl debug "$n" -q -it --image=busybox --profile=general -- \
    chroot /host sh -c 'getenforce 2>/dev/null || echo "not installed"' 2>/dev/null
done
# node-01  Enforcing        <- section "SELinux" applies
# node-02  not installed    <- it does not

# 3. Which cgroup version. v2 shows cgroup2fs; v1 shows tmpfs.
kubectl debug node/node-01 -q -it --image=busybox --profile=general -- \
  chroot /host stat -fc %T /sys/fs/cgroup
# cgroup2fs

# 4. Which kube-proxy mode. This is the one people are most often wrong about,
#    because the answer usually predates everyone currently on the team.
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep -E '^\s*mode:'
#   mode: "ipvs"

# Clean up the debug pods. `kubectl debug node/...` names them
# node-debugger-<node>-<suffix> and applies no label, so there is nothing to
# select on - match the name, or they accumulate silently.
kubectl get pods -o name | grep '^pod/node-debugger-' | xargs -r kubectl delete

والرقم الآخر المهمّ هو كم بقي من مدرج للإصدار الذي أنت عليه. تدعم Kubernetes الإصدارات الفرعية الثلاثة الأحدث، أربعة عشر شهرًا لكلٍّ منها تقريبًا، ويكون الشهران الأخيران في وضع الصيانة حيث لا تصل سوى إصلاحات الأمان الحرجة. هذا الجدول هو سبب أن الترقيات ليست اختيارية، ويستحق أن يكون أمامك حين يقترح أحدهم تأجيل هذه الترقية.[k8srel]

الإصدارتاريخ الصدوروضع الصيانة مننهاية العمر
1.3427 أغسطس 202527 أغسطس 202627 أكتوبر 2026 — بقي شهران
1.3517 ديسمبر 202528 ديسمبر 202628 فبراير 2027
1.3622 أبريل 202628 أبريل 202728 يونيو 2027
1.3726 أغسطس 2026≈ أغسطس 2027≈ أكتوبر 2027

SELinuxMount يبلغ GA: التغيير الذي يوقف الـ Pods

هذا هو القسم الذي يبرّر المقال. يبلغ SELinuxMount مرتبة GA في 1.37 ويُفعَّل افتراضيًا. التغيير مكسبٌ في الأداء وآليّته أنيقة: بدل أن يمشي runtime الحاويات على وحدة التخزين ويعيد تسمية كل inode فيها — وهو أمر بطيء فعلًا على نظام ملفات كبير أو بعيد — يركّب kubelet وحدة التخزين بالخيار -o context=<label> فتطبّق النواة التسمية على كل inode في ذلك التركيب في زمن ثابت. والمشكلة نتيجة مباشرة للآلية. التركيب الواحد لا يحمل سوى سياق SELinux واحد بالضبط. في ظلّ إعادة التسمية التعاودية كان بإمكان Pods بتسميتين مختلفتين أن تتشارك وحدة تخزين؛ أما مع تركيب بسياق فلا تستطيع، وسيجلس أحدهما في ContainerCreating حتى يزول الآخر.[selblog][kep1710]

الشرطأين تتحقّق منهإن لم يتحقّق
نظام تشغيل العقدة يدعم SELinux وهي في وضع الفرضgetenforce على العقدةلا شيء يتغيّر. يتخطّى kubelet مسار SELinux كاملًا
SELinuxMountReadWriteOncePod مفعّلةبلغت GA وصارت غير مشروطة من 1.36لا ينطبق على إصدار مدعوم
SELinuxMount وSELinuxChangePolicy مفعّلتانبوابتا ميزات. SELinuxMount في بيتا ومطفأة في 1.36، وفي GA ومفعّلة في 1.37تُطبَّق التسميات تعاوديًا عبر الـ runtime، كما كان
الـ Pod يكشف seLinuxOptions.level على الأقلsecurityContext في الـ Pod أو الحاويةيسنِد الـ runtime مستوًى عشوائيًا بعد التركيب ويعيد التسمية تعاوديًا بأي حال
مشغّل CSI يضبط seLinuxMount: truekubectl get csidrivers -o custom-columns=…بلا تغيير. غير المضبوط ليس true. وفي الشجرة لا يدعم الخيار سوى fc وiscsi وrbd
spec.securityContext.seLinuxChangePolicy غير مضبوط أو MountOptionمواصفة الـ PodRecursive هو منفذ الخروج الصريح ويُبقي السلوك القديم

نطاق الأثر أضيق ممّا يبدو من هذا الوصف، ويستحق أن يُحسب بدقّة بدل افتراض الأسوأ. خمسة شروط يجب أن تتحقّق جميعًا قبل أن يتغيّر سلوك وحدة تخزين واحدة، والشرط الذي يغفله الناس هو مشغّل CSI: لا يستعمل kubelet تركيبًا بسياق إلا إذا أعلن المشغّل أنه يقبله، بضبط seLinuxMount: true على كائن CSIDriver الخاص به. والمشغّل الذي يترك الحقل غير مضبوط — وغير المضبوط ليس true — يبقى على السلوك التعاودي القديم ولا يتأثّر بالانقلاب إطلاقًا. أما أنواع وحدات التخزين المدمجة التي تدعم خيار التركيب فهي fc وiscsi وrbd؛ وكل ما عداها في الشجرة يعيد التسمية تعاوديًا بأي حال.[csidriver]

# The blast radius of the SELinuxMount change is not "clusters with SELinux".
# It is the intersection of three things, and all three have to be true before
# a single pod is at risk.

# (a) SELinux enforcing on the node. Checked above. If not, stop here.

# (b) A CSI driver that has opted in. The kubelet only uses the mount option
#     when the driver declares it can take one. Drivers that do not set this
#     keep the old recursive relabel and are unaffected by the flip.
kubectl get csidrivers \
  -o custom-columns='DRIVER:.metadata.name,SELINUXMOUNT:.spec.seLinuxMount'
# DRIVER                    SELINUXMOUNT
# ebs.csi.aws.com           true      <- volumes on this driver change behaviour
# efs.csi.aws.com           false     <- unchanged
# csi.trident.netapp.io     <none>    <- unset is not true; unchanged

# The in-tree volume types that support the mount option are fc, iscsi and rbd.
# Everything else in tree relabels recursively regardless.

# (c) Two pods with different SELinux labels sharing one volume. This is the
#     part you cannot infer from a manifest, because the label is often
#     assigned by the runtime rather than written down. The cheapest proxy is
#     to find the volumes that more than one workload mounts at all:
kubectl get pods -A \
  -o jsonpath='{range .items[*]}{range .spec.volumes[?(@.persistentVolumeClaim)]}'\
'{.persistentVolumeClaim.claimName}{"\t"}{end}{.metadata.namespace}{"\n"}{end}' \
  | awk -F'\t' 'NF>1' | sort | uniq -c | sort -rn | awk '$1>1'
#   3 shared-media   default
#   2 build-cache    ci

# That list is a starting point, not an answer. The answer comes from the
# controller below, which knows the labels.

# One more thing worth knowing before you panic: a pod that mounts a volume
# through different subPaths used to be able to share it across labels too.
# That case also stops working, and it is rare enough that upstream says it has
# never been seen in practice.

نمطا مشاركة ينكسران، وواحد منهما فقط يقع في الواقع. الأول هو تشارك Pods وحدةَ تخزين عبر مسارات subPath مختلفة بتسميات مختلفة، وهو ما يصفه المنبع بأنه نادر جدًا ويقول إنه لم يره عمليًا قط. والثاني هو Pod مميّز وآخر غير مميّز يتشاركان وحدة تخزين — وهو غير شائع كذلك، لكنه مرصود في تطبيقات حقيقية، وهو ما ينبغي أن تبحث عنه. وقصة الترقية في الـ KEP نفسه تستحق القراءة إن كنت أنت من سيوقّع على هذا، فهي أقرب شيء إلى دليل تنفيذ رسمي.[kepstory3]

التدقيق الذي يجب أن يسبق الترقية

شحنت Kubernetes 1.36 متحكّمًا لهذه المشكلة بالذات، ولم يشغّله أحد تقريبًا، لأنه اختياري ولأن ما يحذّر منه لم يكن قد وقع بعد. يعمل selinux-warning-controller داخل kube-controller-manager خلف --controllers=*,selinux-warning-controller، ويراقب كل Pod في العنقود، ويبلّغ عن كل زوج يتشارك وحدة تخزين على نحو لن يسمح به SELinuxMount. ويبلّغ عنها حتى وإن كانت الـ Pods على عقد مختلفة، لسبب وجيه هو أن المجدول قد يجمعها غدًا.[k8s136]

# Kubernetes v1.36 shipped a controller whose entire job is to find these
# conflicts before the upgrade turns them into stuck pods. It is off by
# default and it is the single most useful thing in this article.
#
# It runs inside kube-controller-manager. `*` keeps every default controller
# and adds this one; listing it alone would disable all the others.

# kubeadm clusters: edit the static pod manifest on each control plane node.
sudo vi /etc/kubernetes/manifests/kube-controller-manager.yaml
#   spec:
#     containers:
#     - command:
#       - kube-controller-manager
#       - --controllers=*,bootstrapsigner,tokencleaner,selinux-warning-controller
#                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^
# The kubelet restarts the pod when the file changes. Give it a minute.

# Note the second requirement, which is easy to miss: you must NOT have
# explicitly disabled the SELinuxChangePolicy feature gate. It is GA and on by
# default, so this only bites clusters carrying an old --feature-gates line.
kubectl -n kube-system get pod -l component=kube-controller-manager \
  -o jsonpath='{.items[*].spec.containers[*].command}' | tr ',' '\n' \
  | grep -E 'feature-gates|controllers='

# Confirm it is running before you trust its silence:
kubectl -n kube-system logs -l component=kube-controller-manager --tail=200 \
  | grep -i 'selinux'
# ... "Starting controller" controller="selinux-warning-controller"

# Enabling it has one privacy consequence worth stating: the metric it emits
# carries namespace names as labels, so it can leak namespace names to anyone
# who can read kube-controller-manager metrics. Upstream's assumption is that
# only cluster administrators can.

ثم اقرأ المقياسين معًا، فكلٌّ منهما يجيب عن سؤال مختلف ولا يكفي أحدهما وحده. مقياس المتحكّم selinux_warning_controller_selinux_volume_conflict يحمل أسماء الـ Pods المتعارضة وفضاءات أسمائها كتسميات — وتلك هي قائمة عملك. أما مقياس kubelet volume_manager_selinux_volume_context_mismatch_warnings_total فلا يحمل أي تسمية باسم Pod، لكنه العدّ الصادق للـ Pods التي ستفشل فعلًا، ويُطلَق بينما SELinuxMount لا يزال معطّلًا. وهذه الجملة الأخيرة هي كل المقصود من فعل ذلك على 1.36: عدّاد التحذيرات يكون غير صفري بينما كل شيء لا يزال يعمل. وبعد الترقية يظهر القياس نفسه باسم ..._errors_total، وتكون الـ Pods عندئذٍ عالقة لا مهدَّدة.[selblog]

# There are two metrics and they answer two different questions. You need both:
# one tells you WHICH pods, the other tells you HOW MANY will actually fail.

# --- (1) From kube-controller-manager: which pods conflict, by name ---------
# Reported even when the pods are on different nodes, because the scheduler
# may put them together tomorrow.
kubectl get --raw /metrics 2>/dev/null | true   # via your metrics stack, or:
kubectl -n kube-system exec -it \
  "$(kubectl -n kube-system get pod -l component=kube-controller-manager \
     -o name | head -1)" -- \
  curl -sk https://127.0.0.1:10257/metrics \
  | grep '^selinux_warning_controller_selinux_volume_conflict'
# selinux_warning_controller_selinux_volume_conflict{
#   pod1_name="my-other-pod",pod1_namespace="default",
#   pod1_value="system_u:object_r:container_file_t:s0:c0,c1",
#   pod2_name="my-pod",pod2_namespace="default",
#   pod2_value="system_u:object_r:container_file_t:s0:c0,c2",
#   property="SELinuxLabel"} 1

# --- (2) From the kubelet: how many pods would actually fail ---------------
# Emitted while SELinuxMount is still DISABLED, which is exactly the window
# you are in before the upgrade. It has no pod-name label - that is what the
# controller above is for - but it is the honest count.
kubectl get --raw "/api/v1/nodes/node-01/proxy/metrics" \
  | grep '^volume_manager_selinux_volume_context_mismatch_warnings_total'
# volume_manager_selinux_volume_context_mismatch_warnings_total{...} 2

# After the upgrade the same measurement lives under a different name, and by
# then the pods are already stuck rather than merely at risk:
#   volume_manager_selinux_volume_context_mismatch_errors_total

# The whole point of doing this on v1.36 is that the warnings metric is
# non-zero while everything still works. Read it while it is still cheap.

لكل حِمل تسمّيه المقاييس أمامك خياران: أن تصلح المشاركة، أو أن تُخرج ذلك الـ Pod من المسار الجديد. ومنفذ الخروج حقل في الـ Pod، هو spec.securityContext.seLinuxChangePolicy، وهو واجهة مستقرّة منذ 1.36 — أي أنك تستطيع تطبيقه الآن، على إصدارك الحالي، بلا أي تغيّر سلوكي، وسيفعل الصواب لحظة هبوط الترقية. وهذا أرخص تأمين في هذا الإصدار كلّه. وضبطه على Recursive يُبقي السلوك القديم لذلك الـ Pod ويكلّفك مكسب الأداء، وهو بالنسبة إلى حِمل كان يتشارك وحدة تخزين عبر تسميات مختلفة مقايضةٌ كنت تعقدها أصلًا.[seccontext][mutadm]

# For every workload the metrics named, you have two options: fix the sharing,
# or opt that pod out of the mount-option path. The opt-out is a Pod field and
# it is stable API as of Kubernetes v1.36.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: legacy-shared-cache
spec:
  template:
    spec:
      securityContext:
        # Three values:
        #   unset       - follow the cluster default. On v1.37 with SELinuxMount
        #                 enabled, that means mount options.
        #   MountOption - use mount options explicitly. Only valid while the
        #                 SELinuxMount feature gate is on.
        #   Recursive   - the pre-1.37 behaviour: the runtime relabels every
        #                 file. Slower on large volumes, and it is what lets two
        #                 differently labelled pods share one volume.
        seLinuxChangePolicy: Recursive
      containers:
        - name: app
          image: registry.internal.example.com/app:1.4.2
---
# Applying this to every affected workload by hand does not scale, and both
# the SELinux blog and the KEP say so. Prefer a policy. In-tree:

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingAdmissionPolicy
metadata:
  name: selinux-recursive-optout
spec:
  matchConstraints:
    resourceRules:
      - apiGroups:   [""]
        apiVersions: ["v1"]
        operations:  ["CREATE"]
        resources:   ["pods"]
  # Scope this to the namespaces the metrics actually named. A cluster-wide
  # opt-out works, but it also throws away the performance win for every
  # workload that was never at risk.
  matchConditions:
    - name: only-flagged-namespaces
      expression: "request.namespace in ['default', 'ci']"
  failurePolicy: Fail
  reinvocationPolicy: IfNeeded
  mutations:
    - patchType: ApplyConfiguration
      applyConfiguration:
        expression: >
          Object{ spec: Object.spec{
            securityContext: Object.spec.securityContext{
              seLinuxChangePolicy: "Recursive" } } }
seLinuxChangePolicyالسلوك على 1.36السلوك على 1.37متى تستعمله
غير مضبوط (الافتراضي)إعادة تسمية تعاودية — SELinuxMount مطفأة افتراضيًاخيار التركيب، إن تحقّقت بقية الشروطالافتراضي لكل ما لا يتشارك وحدات تخزين عبر تسميات مختلفة
MountOptionصالح فقط مع تفعيل بوابة الميزةخيار التركيب، صراحةًنادرًا ما يُحتاج. الافتراضي يفعل هذا أصلًا على 1.37
Recursiveإعادة تسمية تعاوديةإعادة تسمية تعاودية — منفذ الخروجهذا هو العلاج. طبّقه على كل حِمل سمّته مقاييس التعارض، قبل الترقية

وتطبيق ذلك الحقل يدويًا على كل Deployment و StatefulSet متأثّر لا يتوسّع خارج عنقود صغير، وتقول مدوّنة SELinux ذلك صراحةً، مسمّيةً MutatingAdmissionPolicy والـ mutating webhooks و Kyverno و Gatekeeper طرقًا لفعله بالجملة. ونصيحة واحدة في النطاق: قاوم إغراء تطبيق Recursive على العنقود كلّه كإجراء احترازي شامل. سينجح، وسيرمي التحسين في الأداء عن كل حِمل لم يكن مهدَّدًا أصلًا — وهم في معظم العناقيد جميع الأحمال تقريبًا. اقصره على فضاءات الأسماء التي سمّتها المقاييس فعلًا.[kyverno]

الـ static pods ومراجعها إلى Secret: هذا حدث في 1.34

هذا هو العطل الأرجح أن يُلقى بذنبه على 1.37 من قِبل من رقّى قادمًا من 1.33. الـ static pods يديرها kubelet من دليل على القرص لا تُنشَأ عبر خادم الـ API، فما كان يُفترض أن تقدر على قراءة كائنات الـ API إطلاقًا؛ لكن عِلّة سمحت لها بالإشارة إلى Secrets و ConfigMaps عبر حقول مثل configMapRef وsecretRef. والقيد الذي يغلق هذه العِلّة هو بوابة الميزة PreventStaticPodAPIReferences، وقيمتها الافتراضية مفعّلة منذ 1.34 — أي أن أي عنقود مرّ فعلًا بـ 1.34 و1.35 و1.36 قد وقع له هذا سلفًا وانكسرت الـ Pods المتأثّرة عنده. وقد أعلنت النظرة المسبقة إلى 1.37 أن البوابة نفسها تُزال، وأن منفذ الخروج يزول معها؛ غير أن مرجع بوابات الميزات المنشور مع 1.37 لا يزال يدرج PreventStaticPodAPIReferences بوابةً في مرتبة بيتا بقيمة افتراضية true، أي أن منفذ الخروج قد يكون تقنيًا لا يزال قائمًا. خطّط على أنه ليس كذلك. فقد كان هذا عِلّة لا ميزة، ولن يعود، والبحث في أدلّة الـ static pods أرخص من اكتشاف الأمر عقدةً عقدة.[staticpod][iss140226]

# Static pods are managed by the kubelet from a directory on disk, not by the
# API server. They were never supposed to be able to read API objects; a bug
# let them, through envFrom.configMapRef, envFrom.secretRef, valueFrom and
# volume references. The gate that closes it - PreventStaticPodAPIReferences -
# has defaulted to true since v1.34, so this is only "new in 1.37" for a
# cluster that skipped releases. The v1.37 sneak peek says the gate was
# removed in this release; the shipped v1.37 feature-gates reference still
# lists it as Beta/true. Plan as if the opt-out is gone: it closed a defect,
# and it is not coming back. Find the references before the upgrade.

# Where the manifests live. Do not assume /etc/kubernetes/manifests: read it
# from the kubelet's own configuration.
sudo grep -E '^staticPodPath:' /var/lib/kubelet/config.yaml
# staticPodPath: /etc/kubernetes/manifests

# The audit, per node. Any hit is a pod that will fail to start on v1.37.
sudo grep -rnE 'configMapRef|secretRef|configMapKeyRef|secretKeyRef|(configMap|secret):' \
  /etc/kubernetes/manifests/
# /etc/kubernetes/manifests/node-exporter.yaml:24:            secretRef:
# /etc/kubernetes/manifests/node-exporter.yaml:25:              name: scrape-creds

# Fleet-wide, without logging into every box. Note that the control plane's
# own static pods (kube-apiserver, etcd, kube-scheduler,
# kube-controller-manager) are generated by kubeadm and do not use these
# references, so a clean result there is expected rather than reassuring.
for n in $(kubectl get nodes -o name); do
  printf '%-22s ' "${n#node/}"
  kubectl debug "$n" -q -it --image=busybox --profile=general -- \
    chroot /host sh -c \
    'grep -rlE "configMapRef|secretRef|configMapKeyRef|secretKeyRef" \
       /etc/kubernetes/manifests/ 2>/dev/null | tr "\n" " " || true' 2>/dev/null
  echo
done

# The fix is to stop being a static pod, or stop needing the reference:
#   * A DaemonSet is the right answer for almost everything that is a static
#     pod today for historical reasons. It can read Secrets normally.
#   * If it has to stay static, put the value in the manifest, or bind-mount a
#     file from the host and read it from there. Both are worse than a
#     DaemonSet, and both work.

والعلاج في الغالب الأعمّ أن تكفّ عن كونها static pod. كثير جدًا من الـ static pods موجود لأسباب تاريخية — كانت الطريقة لتشغيل وكيل على مستوى العقدة قبل أن تبلغ الـ DaemonSets قدرتها الحالية — و DaemonSet يقرأ الـ Secrets بشكل طبيعي، ويحصل على تحديثات متدرّجة، ويظهر في الأماكن التي ينظر الناس فيها. وحيث يجب أن يبقى شيء static فعلًا، فالخياران أن تضع القيمة مباشرةً في البيان أو أن تربط ملفًا من المضيف وتقرأه من هناك. كلاهما أسوأ من DaemonSet وكلاهما يعمل. ولاحظ بشكل منفصل أن kubectl run --filename/-f مهجور في هذا الإصدار أيضًا، بحجّة أن الـ Pod الناتج عنه كان يُبنى دائمًا من وسائط سطر الأوامر وحدها — وذلك يمسّ النصوص البرمجية لا العناقيد.[iss138671]

kube-proxy ipvs: مهجور منذ 1.35، لا مُزال

والآن التغيير الأكثر مبالغةً في التبليغ عنه. وضع ipvs في kube-proxy يحمل إشعار إهمال منذ 1.35، لا منذ 1.37. وما تضيفه 1.37 هو بوابة الميزة KubeProxyIPVS، وهي نفسها موسومة بالإهمال، إلى جانب التحذير الذي يسجّله kube-proxy عند الإقلاع. لا شيء يتوقّف، ولا تنقلب بوابة ميزة، ولا تتأثّر أي حركة مرور. والتعليل خلف الإهمال يستحق الفهم لأنه يفسّر لماذا لم يوجد إصلاح قط: واجهة ipvs في النواة لا تستطيع التعبير عن كل ما تحتاجه Service في Kubernetes، فظلّ وضع ipvs يسقط إلى iptables تحته في أجزاء من العمل. ويقولها KEP-3866 بلا مواربة في عنوان قسم: وضع ipvs في kube-proxy لن ينقذنا.[kep5495][kep3866]

الإصدارما يحدث لوضع ipvsما عليك فعله
1.35مهجور. kube-proxy يسجّل تحذيرًا عند الإقلاعلا شيء — لكن هنا بدأت الساعة
1.37تُضاف بوابة الميزة KubeProxyIPVS، وهي نفسها موسومة بالإهماللا شيء. خطّط للانتقال ولا تستعجله
1.38 – 1.39لا يزال يعمل، ولا يزال يحذّرانتقل إلى وضع nftables في نافذة تختارها أنت
1.40يُتوقّع أن تصير البوابة KubeProxyIPVS false افتراضيًاأعد التفعيل عبر البوابة، أو كن قد انتهيت قبل ذلك
1.43إزالة الدعم كليًالم يبقَ ما يُفعل — هذا هو الموعد النهائي

والوجهة هي وضع nftables، وهو في مرتبة GA منذ 1.33 والوضع الموصى به لعقد لينكس. والعناقيد لا تنتقل من تلقاء نفسها؛ عليك أن تضبط mode: "nftables" صراحةً. ومتطلّب النواة حقيقي لكنه غير مرهق — فكل نواة أقدم من أن تدعم وضع nftables تغادر الدعم طويل الأمد بنهاية 2026 — وقد نُقلت إصلاحات nftables إلى فرعَي 1.33 و1.34 تحديدًا كي يستطيع مستخدمو ipvs على الإصدارات الأقدم الانتقال دون ترقية Kubernetes أولًا.[nftblog]

# What actually happens on v1.37 if you run ipvs mode: kube-proxy logs the
# same deprecation warning it has logged since v1.35. That is all. Nothing
# stops working, no feature gate flips, and no traffic is affected. What v1.37
# adds is the KubeProxyIPVS gate, itself marked deprecated - the switch that
# will later be used to turn the mode off by default.
kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=50 | grep -i deprecat
# W0826 ... "ipvs mode of kube-proxy is deprecated and will be removed in a
#            future release; see KEP-5495"

# The reason is worth knowing, because it explains why there is no fixing it:
# the kernel's ipvs API cannot express everything a Kubernetes Service needs,
# so ipvs mode has always fallen back to iptables underneath for parts of the
# job. It was never the clean escape from iptables it was sold as.

# The destination is nftables mode, GA since v1.33. Clusters never migrate on
# their own - you have to set it.

# --- migrating, on a kubeadm cluster ---------------------------------------
# 1. Check the kernel. nftables mode wants a reasonably modern kernel; every
#    kernel too old for it leaves LTS by the end of 2026.
kubectl get nodes -o jsonpath='{range .items[*]}{.status.nodeInfo.kernelVersion}{"\n"}{end}' \
  | sort -u

# 2. Change the mode in the ConfigMap.
kubectl -n kube-system get configmap kube-proxy -o yaml > /tmp/kube-proxy.bak.yaml
kubectl -n kube-system patch configmap kube-proxy --type merge -p \
  "$(printf '{"data":{"config.conf":%s}}' \
     "$(kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' \
        | sed 's/^\(\s*mode:\).*/\1 "nftables"/' | jq -Rs .)")"

# 3. Roll the DaemonSet one node at a time and watch, rather than all at once.
kubectl -n kube-system rollout restart daemonset/kube-proxy
kubectl -n kube-system rollout status daemonset/kube-proxy --timeout=10m

# 4. Verify from the data plane, not the control plane. A Service that
#    resolves but does not connect is the failure mode here.
kubectl run nft-check --rm -it --restart=Never --image=busybox -- \
  sh -c 'wget -qO- --timeout=5 http://kubernetes.default.svc/healthz || echo FAILED'

# Rolling back is the same edit in reverse; keep /tmp/kube-proxy.bak.yaml.
# Do this as its own change, on its own day. Bundling a proxy-mode migration
# into a version upgrade means that when connectivity breaks you will not know
# which one did it.

نصيحة واحدة في الترتيب، تُقال بحرارة: نفّذ الانتقال في وضع الوكيل في نافذة صيانة مستقلّة، في يوم مستقلّ، لا مضمومًا إلى ترقية الإصدار. كلا التغييرين يمسّ مستوى البيانات، وحين ينكسر الاتصال في نافذة ضمّتهما معًا ستقضي الانقطاع في تحديد أيّهما فعلها بدل إصلاحه. ولا ضغط زمني هنا يبرّر الجمع — فسياسة الإهمال تضمن لميزة في مرتبة بيتا فما فوق مدرجًا طويلًا، ومعايير التخرّج في الـ KEP نفسه تضع بوابة الميزة عند القيمة الافتراضية false في 1.40 والإزالة عند 1.43.[kubeproxycfg][deprecpolicy]

cgroup v1: هذا حدث في 1.35

قصة cgroup v1 هي الأكثر خطأً في النسبة، وضبطها يغيّر ما ستفعله حيالها. الإعداد failCgroupV1 في kubelet قيمته الافتراضية true منذ Kubernetes 1.35. فالعقدة التي لا تزال على cgroup v1 ترفض إقلاع kubelet عليها منذ ذلك الحين، إلا أن يكون أحدهم أضاف failCgroupV1: false — وهو ما فعله كثيرون، على عجل، أثناء ترقية 1.35، ثم لم يعودوا إليه قط. فالسؤال المفيد في الطريق إلى 1.37 ليس «هل سينكسر هذا» بل «من الذي لا يزال يحمل التجاوز، وإلى متى».[kep5573][kubeletcfg]

# This is the change most often misattributed to v1.37. The kubelet setting
# `failCgroupV1` has defaulted to TRUE since Kubernetes v1.35. A node still on
# cgroup v1 has therefore been refusing to start its kubelet since v1.35,
# unless somebody added the override - which many people did, in a hurry, and
# then forgot.

# So the useful question on the way to v1.37 is not "will this break" but
# "who is still carrying the override?"
for n in $(kubectl get nodes -o name); do
  printf '%-22s ' "${n#node/}"
  kubectl debug "$n" -q -it --image=busybox --profile=general -- \
    chroot /host sh -c \
    'printf "cgroup=%s override=%s\n" \
       "$(stat -fc %T /sys/fs/cgroup)" \
       "$(grep -c failCgroupV1 /var/lib/kubelet/config.yaml 2>/dev/null)"' 2>/dev/null
done
# node-01  cgroup=cgroup2fs override=0     <- fine
# node-02  cgroup=tmpfs     override=1     <- v1, running on borrowed time

# The override itself, for reference. It is a stopgap and upstream says so:
#   apiVersion: kubelet.config.k8s.io/v1beta1
#   kind: KubeletConfiguration
#   failCgroupV1: false

# v1.37 still honours it. KEP-5573 removes cgroup v1 support outright in a
# later release, and no date has been committed to, so the honest planning
# assumption is "the next one that suits SIG Node" rather than a fixed month.

# What you lose in the meantime is not theoretical. In-place pod resizing and
# tiered memory protection - both features people are actively asking for -
# depend on cgroup v2 and simply do not work on a v1 node.

# Switching the node is a kernel command line change and a reboot:
#   systemd.unified_cgroup_hierarchy=1
# ...and then the container runtime's cgroup driver has to agree with the
# kubelet's. That is a longer job than it looks, which is why it has its own
# article.

ولا تزال 1.37 تحترم التجاوز، وتُزيل KEP-5573 دعم cgroup v1 من أصله في إصدار لاحق بلا تاريخ ملتزَم به — فافتراض التخطيط الصادق هو «أيّ إصدار يناسب SIG Node» لا شهرٌ تضعه في خطة. وما تتنازل عنه في هذه الأثناء ليس نظريًا: تغيير حجم الـ Pod في مكانه وحماية الذاكرة المتدرّجة كلاهما يعتمد على cgroup v2 ولا يعمل ببساطة على عقدة من الإصدار الأول، وهما ميزتان تطلبهما الفرق فعلًا. والتحويل نفسه تغييرٌ في سطر أوامر النواة وإعادة إقلاع، إضافةً إلى جعل مشغّل cgroup في runtime الحاويات متوافقًا مع مشغّل kubelet — وهو عمل أكبر ممّا يبدو وله مقاله الخاص.[cgroups]

containerd 1.x: الحافة عند 1.38، لا الآن

وهذا البند يُروى مقلوبًا في الاتجاه الآخر، وخطؤه يكلّفك نافذة صيانة لم تكن بحاجة إلى إنفاقها. دعم containerd 1.x لم يُزَل في 1.36، وهو غير مُزال في 1.37. فـ kubelet من 1.37 لا يزال يعمل معه: فمع تفعيل KubeletCgroupDriverFromCRI يسأل kubelet الـ runtime عن مشغّل cgroup الخاص به عبر استدعاء CRI المسمّى RuntimeConfig، ولا ينفّذ containerd 1.y ذلك الاستدعاء، فيسقط kubelet بهدوء إلى قيمة --cgroup-driver عنده مع زيادة المقياس cri_losing_support. وكان هذا المسار الاحتياطي مجدولًا للزوال في 1.37 ثم أُجّل إصدارًا واحدًا، صراحةً لمواءمة نافذة دعم containerd 1.7 نفسها. وتقول وثائق runtimes في Kubernetes ذلك بوضوح الآن: في 1.38 يُسقَط المسار الاحتياطي، وتفشل إصدارات containerd الأقدم أمام kubelet الأحدث.[k8s134][ctrrel]

# The change most often stated backwards. containerd 1.x was NOT removed in
# v1.36, and it still runs against a v1.37 kubelet. What it runs on is a
# fallback: the kubelet asks the runtime for its cgroup driver over the
# RuntimeConfig CRI RPC, containerd 1.y does not implement that RPC, and the
# kubelet falls back to its own --cgroup-driver value. That fallback was
# scheduled to go in v1.37 and was deferred one release to align with
# containerd v1.7's support window, so it disappears in v1.38.

# The audit is a metric, not a spreadsheet. Every node relying on the fallback
# increments this, so scrape it rather than walking nodes by hand.
kubectl get --raw /metrics | grep '^cri_losing_support'
# cri_losing_support{version="1.38.0"} 1

# Then confirm which nodes, and with what:
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,'\
'RUNTIME:.status.nodeInfo.containerRuntimeVersion' | sort -k3
# NODE     KUBELET   RUNTIME
# node-02  v1.37.0   containerd://1.7.28   <- works today, fails on 1.38
# node-01  v1.37.0   containerd://2.3.2

crictl version
# RuntimeName:        containerd
# RuntimeVersion:     v2.3.2
# RuntimeApiVersion:  v1

# Two things make this less comfortable than "one release of runway" sounds:
# containerd 1.7's own extended support ends in September 2026, and
# containerd's published Kubernetes support matrix has no row for 1.37 yet -
# the last row is 1.36 (2.3.0+, 2.2.0+). Book the migration before 1.38, in
# its own window. Landing two runtime-level changes together means that when a
# node comes back wrong you will be bisecting instead of fixing.

فالموعد إذن حقيقي، وهو يبعد إصدارًا واحدًا بالضبط — وضعٌ أفضل ممّا يوحي به الهلع، وأسوأ ممّا يوحي به عدم فعل شيء. وأمران يزيدانه حدّة. أولهما أن ساعة جانب الـ runtime تنفد أولًا: فـ containerd 1.7 هو فرع الدعم طويل الأمد الذي يتوقّف عليه هذا كلّه، وينتهي دعمه الممتد في سبتمبر 2026، أي الآن. وثانيهما أن مصفوفة دعم Kubernetes لدى containerd نفسه لا تحوي حاليًا أي صفّ لـ Kubernetes 1.37 إطلاقًا — فآخر صفّ منشور هو 1.36، ويدرج 2.3.0+ و2.2.0+ — أي أن كل من يعطيك إصدار containerd «معتمَدًا» لأجل 1.37 يستنتج لا يستشهد. وعمليًا: اجمع المقياس cri_losing_support، فهو يُطلَق أصلًا من كل عقدة تحتاج المسار الاحتياطي وهو تدقيق أفضل من المرور على العقد يدويًا، ثم أكّد بـ containerRuntimeVersion. ونفّذ ترحيل الـ runtime قبل 1.38 وفي نافذته الخاصة. فإنزال تغييرين على مستوى الـ runtime معًا يعني أنك حين تعود عقدة على غير وجهها ستكون تُنصّف بدل أن تُصلح.[runtimes]

metrics.k8s.io تبلغ v1 أخيرًا

الخبر السارّ في هذا الإصدار، وهو سارّ فعلًا. تتخرّج metrics.k8s.io إلى v1 بعد قرابة تسع سنوات في مرتبة بيتا. هذه هي الواجهة التي يقوم عليها kubectl top وتقوم عليها مقاييس المعالج والذاكرة في HorizontalPodAutoscaler، ما يجعلها من أوسع الواجهات استعمالًا في Kubernetes وأمرًا غريبًا أن تُترك في بيتا كل تلك المدة. والتخرّج اعترافٌ بالاستقرار لا إدخالٌ لتغيير: لا فروق وظيفية متوقّعة، وتبقى v1 وv1beta1 معًا مقدَّمتين خلال الانتقال.[kep5207][metricspipe]

# The good news in this release. metrics.k8s.io graduates to v1 after nearly
# nine years in beta. Both versions stay served during the transition, so
# there is nothing to do on upgrade day - this is a thing you can adopt on
# your own schedule rather than a thing that happens to you.

kubectl get --raw /apis/metrics.k8s.io | jq -r '.versions[].groupVersion'
# metrics.k8s.io/v1
# metrics.k8s.io/v1beta1

# What consumes it: `kubectl top`, and the HorizontalPodAutoscaler's cpu and
# memory metrics. Both keep working without changes.
kubectl top nodes
kubectl top pods -A --sort-by=memory | head

# Where it matters is code you own. Anything that talks to the API directly -
# a custom autoscaler, a capacity report, a dashboard backend - should move
# off v1beta1 while both are available rather than after one is not.
kubectl get --raw /apis/metrics.k8s.io/v1/nodes | jq '.items[0]'

# Find the clients still on the beta path, from the API server's own counters:
kubectl get --raw /metrics \
  | grep 'apiserver_requested_deprecated_apis\|metrics.k8s.io.*v1beta1' | head

# There is no removal date for v1beta1 yet. Kubernetes' deprecation policy
# guarantees a beta API at least nine months or three releases after
# deprecation, so this is a housekeeping item, not a deadline.

فلا شيء يُفعل يوم الترقية، وهذا هو المقصود — هذا شيء تتبنّاه وفق جدولك أنت لا شيء يقع عليك. وحيث يهمّ فعلًا هو الشيفرة التي تملكها: أداة توسيع مخصّصة، أو تقرير سعة، أو خلفية لوحة معلومات، أو أي شيء يخاطب الـ API مباشرةً ينبغي أن ينتقل عن مسار بيتا بينما الاثنان متاحان لا بعد أن يزول أحدهما. ولا يوجد تاريخ إزالة لـ v1beta1 بعد، وسياسة الإهمال تضمن لواجهة بيتا تسعة أشهر أو ثلاثة إصدارات على الأقل، فعامله على أنه ترتيبُ بيت لا موعد نهائي.[hpa]

بقية الإصدار، باختصار

ثلاثة أشياء أخرى في هذا الإصدار تستحق المعرفة وإن كان أيٌّ منها لن يمسّ ترقية. الثلاثة تخرّجات لا إزالات، وأوّلها هو الجدير بالمتابعة.[kep4960]

  • kubelet داخل فضاء أسماء مستخدمين — الوضع بلا جذر — يبلغ مرتبة بيتا. جرت العادة أن تعمل مكوّنات العقدة بصلاحية الجذر على المضيف. وهذا يتيح لها العمل بمستخدم غير مميّز على المضيف مع بقائها ظاهرةً كجذر داخل فضاء أسماء مستخدمين في لينكس، ما يحدّ من نطاق أثر أي ثغرة في مكوّن عقدة. ومرتبة بيتا هنا تعني أن البوابة مفعّلة افتراضيًا، لكن تفعيلها وحده لا يجعل kubelet يعمل داخل فضاء أسماء مستخدمين — فخلفها إعدادات على المضيف — كما أن التغيير لم يرد في إعلان الإصدار. عامله على أنه شيء تجرّبه على عقدة اختبار لا شيء وقع لك. وهي كذلك ليست الميزة نفسها التي تخصّ فضاءات أسماء المستخدمين للـ Pods، وتلك بلغت بيتا في 1.35 و GA في 1.36.
  • إعادة تسمية SELinux لوحدات تخزين ReadWriteOncePod كانت قد بلغت GA في 1.36 أصلًا. تلك هي SELinuxMountReadWriteOncePod، وهي بوابة أضيق من SELinuxMount التي يدور حولها هذا المقال، وهي سبب أن بعض العناقيد تركّب بخيار سياق منذ فترة دون أن ينكسر شيء — فوحدات RWOP لا يمكن تشاركها بالتعريف، ومن ثمّ لا ينشأ فيها التعارض الذي يصفه هذا المقال.
  • مراقبة صحة وحدات التخزين تُستأنف عند مرتبة ألفا. هبط تنفيذ أوّليّ في 1.21 ولم يتخرّج قط؛ وتعيد KEP-1432 ضبطه خلف البوابة CSIVolumeHealth وتُدخل أربعة استدعاءات CSI — ControllerListVolumeHealth وControllerGetVolumeHealth وNodeGetVolumeHealth وNodeGetStorageHealth — تبلّغ في PersistentVolumeClaim.status.healthStatus وPod.status.volumeHealth وCSINode.status.storageHealth. والمفردات صغيرة عن قصد وقابلة للقراءة آليًا: Inaccessible وDataLoss وDegraded، مع reason وmessage على الشرط للتفصيل الخاص بكل مشغّل.

ومرتبة ألفا تعني مطفأة افتراضيًا وغير صالحة للإنتاج، لكن عمل مراقبة الصحة يستحق التتبّع إن سبق أن اضطررت إلى مقابلة تركيب معلّق بلوحة معلومات مزوّد تخزين لتعرف ما الخطب. فهذا أول جواب قابل للقراءة آليًا تقدّمه Kubernetes عن ذلك السؤال.[kep1432][userns]

الترقية، بالترتيب

الترتيب أهمّ من الأوامر. كل ما هو صعب فعلًا في هذه الترقية صعبٌ قبلها، والخطوة الوحيدة التي تصير أغلى كثيرًا بعدها هي تدقيق SELinux — فهي تأتي أولًا، بفارق أسابيع لا دقائق.[kubeadmup]

# The order matters more than the commands, and the SELinux audit has to come
# first because it is the only step that is materially harder after the
# upgrade than before it.

# --- WEEKS BEFORE, on v1.36 ------------------------------------------------
# 1. Turn on selinux-warning-controller, read both metrics, apply the opt-outs.
# 2. Audit static pods for Secret and ConfigMap references. Move them to
#    DaemonSets where you can.
# 3. Get every node onto containerd 2.x and cgroup v2, if any are not.
#    Both of these are already overdue rather than upcoming.
# 4. Decide about ipvs - and then do it in a DIFFERENT window.

# --- THE DAY ---------------------------------------------------------------
# Control plane first, one node at a time. Nothing here is 1.37-specific;
# it is the standard kubeadm sequence and it is standard because it works.
sudo apt-mark unhold kubeadm && sudo apt-get update \
  && sudo apt-get install -y kubeadm='1.37.0-*' && sudo apt-mark hold kubeadm

sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.37.0        # first control plane node
# sudo kubeadm upgrade node               # every other control plane node

# Then the kubelet and kubectl on that same node:
sudo apt-mark unhold kubelet kubectl && sudo apt-get update \
  && sudo apt-get install -y kubelet='1.37.0-*' kubectl='1.37.0-*' \
  && sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet

# --- WORKER NODES, ONE AT A TIME -------------------------------------------
NODE=node-02
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data --timeout=15m
# ... upgrade kubeadm, run `kubeadm upgrade node`, upgrade kubelet, restart ...
kubectl uncordon "$NODE"

# Then STOP and look, before the next node. The failure this release can
# produce is a pod stuck in ContainerCreating, and it is per-node:
kubectl get pods -A --field-selector spec.nodeName="$NODE" \
  -o wide | grep -v Running | grep -v Completed

# The version skew policy is what makes the staged rollout legal: a kubelet
# may be up to three minor versions behind the API server, so a fleet halfway
# through this is a supported configuration rather than a risk in itself.

طمأنة واحدة عن فعل هذا تدريجيًا: تسمح سياسة انحراف الإصدارات بأن يعمل kubelet متأخّرًا حتى ثلاثة إصدارات فرعية عن خادم الـ API، فالأسطول الذي قطع نصف الطريق تركيبةٌ مدعومة لا خطر بذاته. خذ وقتك. رقِّ عقدة واحدة، وانظر إليها كما ينبغي، وعندها فقط تابع — لأن العطل الذي يستطيع هذا الإصدار إنتاجه عطلٌ لكل عقدة على حدة، ويظهر كـ Pod لا يقلع أبدًا لا كخطأ يوقظك أحدهم من أجله.[skew]

التراجع، وما لا يتراجع

يستحق التراجع جوابًا صريحًا لا مطمئنًا، وفي هذه الترقية للجواب ثلاثة أجزاء. التغييرات القابلة للعكس قابلة للعكس فعلًا: وضع kube-proxy هو ConfigMap وإعادة تشغيل DaemonSet، وseLinuxChangePolicy حقل في الـ Pod يتصرّف تصرّفًا واحدًا على 1.36 و1.37 — وهذا بالضبط سبب أن تطبيقه قبل الترقية لا يكلّف شيئًا ويشتري لك قصة التراجع كاملة.[kubeadmup]

# Rollback deserves a straight answer. Most of this release rolls back; one
# part of it does not roll back in the way people assume.

# --- What rolls back cleanly ----------------------------------------------
# The kube-proxy mode change: it is a ConfigMap and a DaemonSet restart.
kubectl -n kube-system apply -f /tmp/kube-proxy.bak.yaml
kubectl -n kube-system rollout restart daemonset/kube-proxy

# The seLinuxChangePolicy opt-out: it is a Pod field. Setting it to Recursive
# is safe on v1.36 and v1.37 alike, which is why applying it BEFORE the
# upgrade costs nothing and buys the whole rollback story.

# --- What does not ---------------------------------------------------------
# The control plane. `kubeadm upgrade` has no downgrade path: going back means
# restoring the etcd snapshot you took before you started, which means losing
# everything written to the cluster since. If you did not take one, you do not
# have a rollback - you have a forward fix.
sudo ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save "/var/backups/etcd-pre-1.37-$(date +%F).db"

# The static pod references. If a static pod was relying on a secretRef, the
# reference is gone on 1.37 and comes back on a downgrade - but the downgrade
# is the control plane operation above, so in practice the fix is forward:
# move it to a DaemonSet.

# --- The one that surprises people ----------------------------------------
# Downgrading the kubelet does NOT un-stick a pod that failed to mount because
# of an SELinux label conflict, because the pod that WON the volume is still
# holding it with its own context. Terminate one of the two, or set
# seLinuxChangePolicy: Recursive on both and let them share again. Version
# numbers are not the lever here; the Pod field is.

أما مستوى التحكّم فهو الجزء الذي لا يتراجع. لا يملك kubeadm upgrade مسار خفض إصدار؛ والعودة إلى الوراء تعني استعادة لقطة etcd التي أخذتها قبل أن تبدأ، وفقدان كل ما كُتب إلى العنقود منذئذٍ. وإن لم تكن قد أخذت واحدة، فليس لديك تراجع — لديك إصلاح إلى الأمام، وهو حديث آخر تخوضه في الثانية فجرًا. وثمّة عطل واحد يفاجئ الناس: خفض إصدار kubelet لا يحرّر Pod فشل تركيبه بسبب تعارض تسمية SELinux، لأن الـ Pod الذي ظفر بوحدة التخزين لا يزال ممسكًا بها بسياقه هو. والرافعة هناك هي حقل الـ Pod، لا رقم الإصدار.[selblog]

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

للتحقّق في هذه الترقية شكل محدّد، لأن العطل المميّز فيها يترك كل شيء أخضر. العقدة في حالة Ready. و kubelet سليم. ومستوى التحكّم بخير. وثمّة ببساطة Pod لا ينتهي إنشاؤه أبدًا، على عقدة واحدة، يخصّ فريقًا واحدًا. فالتأكّد من أن العنقود قائم لا يثبت شيئًا إطلاقًا — عليك أن تفحص ما يمكن أن يكون خطأً بصمت.[chlog]

#!/usr/bin/env bash
# Post-upgrade verification. Exits non-zero when something is wrong, so it can
# run between nodes in a pipeline rather than being read by a person at 3am.
set -uo pipefail
rc=0
fail() { printf '  FAIL  %s\n' "$*"; rc=1; }
pass() { printf '  ok    %s\n' "$*"; }

echo '== versions =='
kubectl version -o json | jq -r '.serverVersion.gitVersion'

echo '== every node Ready and on the expected version =='
bad=$(kubectl get nodes --no-headers | awk '$2!="Ready"{print $1}')
[ -z "$bad" ] && pass 'all nodes Ready' || fail "not Ready: $bad"

echo '== no pod stuck creating (the SELinux failure mode) =='
stuck=$(kubectl get pods -A --no-headers \
  | awk '$4=="ContainerCreating"{print $1"/"$2}')
[ -z "$stuck" ] && pass 'nothing in ContainerCreating' || fail "stuck: $stuck"

echo '== SELinux context mismatches that became real failures =='
for n in $(kubectl get nodes -o name); do
  v=$(kubectl get --raw "/api/v1/nodes/${n#node/}/proxy/metrics" 2>/dev/null \
      | awk '/^volume_manager_selinux_volume_context_mismatch_errors_total/{s+=$2} END{print s+0}')
  [ "$v" = "0" ] && pass "${n#node/}: 0 mismatch errors" \
                 || fail "${n#node/}: $v SELinux mismatch errors"
done

echo '== static pods all running =='
sp=$(kubectl get pods -A -o json \
  | jq -r '.items[] | select(.metadata.annotations["kubernetes.io/config.source"]=="file")
           | select(.status.phase!="Running") | .metadata.namespace+"/"+.metadata.name')
[ -z "$sp" ] && pass 'static pods Running' || fail "static pods not Running: $sp"

echo '== the resource metrics API answers on both versions =='
kubectl get --raw /apis/metrics.k8s.io | jq -e \
  '[.versions[].groupVersion] | index("metrics.k8s.io/v1")' >/dev/null \
  && pass 'metrics.k8s.io/v1 served' || fail 'metrics.k8s.io/v1 missing'
kubectl top nodes >/dev/null 2>&1 && pass 'kubectl top works' || fail 'kubectl top broken'

echo '== service traffic actually flows =='
kubectl run verify-net --rm -i --restart=Never --image=busybox --timeout=60s -- \
  sh -c 'wget -qO- --timeout=5 http://kubernetes.default.svc/healthz' >/dev/null 2>&1 \
  && pass 'in-cluster Service reachable' || fail 'in-cluster Service unreachable'

exit $rc

شغّله بين العقد لا في النهاية. النص يعيد رمز خروج غير صفري، أي أنه يصلح خطوةً في خط أنابيب بدل أن يقرأه إنسان في الثالثة فجرًا، والفحصان الجديران بالبقاء حتى بعد أن تصير الترقية خلفك هما مسح ContainerCreating وعدّاد عدم تطابق SELinux. كلاهما رخيص، وكلاهما يمسك صنفًا من المشكلات ينتظر لولاهما إلى أن يلاحظ أحدهم أن حِمله لم يعد أبدًا.[k8spatch]

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

مضغوطةً، هذه ترقية أصغر ممّا يوحي به المقال — بشرط أن تكون قد أنجزت العمل الذي كان يخصّ الإصدارين قبلها. وجدول القرار أدناه هو في حقيقته سؤال عن أيّهما تخطّيت.[sneak]

إن كان وضعك…فإن 1.37…والعمل…
على 1.36، ولا SELinux في أي مكان، و containerd 2.x، و cgroup v2ترقية اعتياديةتسلسل kubeadm المعتاد. ابحث في بيانات الـ static pods وامضِ
على 1.36، و SELinux في وضع الفرض، ومشغّلات CSI بـ seLinuxMount: trueالحالة التي تحتاج تدقيقًاشغّل متحكّم التحذير الآن، واقرأ المقياسين، وطبّق Recursive حيث يشيران، ثم رقِّ
على 1.34 أو 1.35، وتنوي القفز مباشرةً إلى 1.37إصداران أو ثلاثة من التغييرات دفعةً واحدةاقرأ ملاحظات كل نسخة تتخطّاها. قيد الـ static pods، والقيمة الافتراضية لـ cgroup، وتحذير ExternalIPs كلها هناك
أي عقدة لا تزال على cgroup v1ليست مشكلتك الأكثر إلحاحًاتلك العقدة تحمل تجاوزًا منذ 1.35. حوّلها، على حدة، قبل أن ترقّي
أي عقدة لا تزال على containerd 1.xسليمة اليوم، مكسورة في 1.38ليست عمل يوم الترقية. اجمع cri_losing_support، ثم احجز ترحيل الـ runtime بمفرده قبل أن تأخذ 1.38
تشغّل kube-proxy في وضع ipvsسطر في السجل، لا أكثرانتقل إلى nftables في يوم آخر. أمامك حتى 1.43، وضمّه يخفي سبب أي انقطاع
خدمة مُدارة (GKE أو EKS أو AKS)ما يجدوله المزوّدتدقيق SELinux يبقى مسؤوليتك — فالأحمال ومشغّلات CSI لك حتى حين لا تكون العقد لك
  1. أجب عن سؤال SELinux أولًا، بأمر واحد. إن لم تكن أي عقدة تشغّل SELinux في وضع الفرض، فتخطَّ القسم الأكبر هنا كاملًا وعامِل 1.37 على أنها ترقية اعتيادية. وإن كانت أي عقدة تفعل، فكل ما تحت هذا ينطبق.
  2. شغّل selinux-warning-controller على 1.36 واقرأ المقياسين معًا. المتحكّم يسمّي الـ Pods المتعارضة؛ و kubelet يعدّ التي ستفشل فعلًا. افعل هذا قبل أسابيع، فهو أصعب كثيرًا بعد الترقية وتكون الـ Pods عندها عالقة لا مهدَّدة فحسب.
  3. طبّق seLinuxChangePolicy: Recursive على الأحمال التي سمّتها المقاييس، بسياسة لا بيدك. هو واجهة مستقرّة على 1.36، ولا يغيّر شيئًا اليوم، ويفعل الصواب لحظة هبوط الترقية. اقصره على فضاءات الأسماء المعنيّة لا على العنقود كلّه.
  4. ابحث في دليل الـ static pods على كل عقدة عن secretRef وconfigMapRef. هذا هو السلوك الافتراضي منذ 1.34، فعلى عنقود مرّ بالإصدارات واحدًا واحدًا يكون قد وقع سلفًا؛ أما على عنقود قفز فهو ينتظره. انقل ما تجده إلى DaemonSet، وهو في الغالب الأعمّ ما كان ينبغي أن يكونه.
  5. سدّد دَين cgroup v1 من 1.35، وضع ترحيل containerd في التقويم قبل 1.38. العقدة التي على cgroup v1 تحمل تجاوزًا منذ إصدارين. والعقدة التي على containerd 1.x لا تزال تعمل اليوم وتتوقّف بعد إصدار واحد. ولكلٍّ منهما نافذته الخاصة — واترك الانتقال من ipvs إلى nftables ليوم آخر تمامًا.

اثنان من الديون أعلاه لهما دليلا تنفيذ خاصّان بهما، فليس أيٌّ منهما عمل خمس دقائق: الترحيل من containerd 1.7 إلى 2.x، وهو إزالة containerd 1.x منظورًا إليها من جهة الـ runtime، والانتقال من cgroup v1 إلى cgroup v2، وهو التدقيق والتحويل الكامنان خلف تجاوز failCgroupV1. وإن كانت نافذة الصيانة نفسها ستعيد بناء طبقة الدخول لديك، فإن الانتقال من ingress-nginx إلى Gateway API يغطّي ذلك الانتقال. وإن كان هذا هو الموضع الذي يسأل فيه أحدهم في الغرفة عمّا إذا كان الأمر كلّه يستحق، فإن متى لا تستخدم Kubernetes هو الوجه الآخر لتلك الحجّة، مقولًا بصدق.

أسئلة شائعة

متى صدر Kubernetes 1.37، وما الذي ينكسر فعلًا؟

صدر 1.37 في 26 أغسطس 2026. وتغيير واحد فيه فقط قادر على إيقاف حِمل: يبلغ SELinuxMount مرتبة GA ويُفعَّل افتراضيًا، وهو ما قد يترك Pods في ContainerCreating حيث يتشارك Pods بتسميتين مختلفتين وحدةَ تخزين على عقدة تعمل فيها SELinux بوضع الفرض مع مشغّل CSI أعلن قبوله. أما بقية الجديد فهادئة — تُضاف بوابة الميزة KubeProxyIPVS وتُوسَم بالإهمال فورًا، وتتخرّج metrics.k8s.io إلى v1 مع بقاء الإصدارين مقدَّمين، ويصير kubectl run --filename/-f مهجورًا، وذلك يمسّ النصوص البرمجية لا العناقيد. وكل ما عدا ذلك ممّا يُنسب إلى هذا الإصدار هبط قبله أو لم يهبط بعد: فشل kubelet على cgroup v1 من 1.35، وقيد Secret في الـ static pods من 1.34، ووضع ipvs مهجور منذ 1.35 ويُزال في 1.43، و containerd 1.x لا يزال يعمل — ومساره الاحتياطي في CRI يُسقَط في 1.38.

هل يزيل Kubernetes 1.37 وضع ipvs من kube-proxy؟

لا، ولم يهجره أيضًا — فذلك حدث في 1.35، و kube-proxy يسجّل تحذيرًا عند إقلاعه منذ ذلك الحين. وما تضيفه 1.37 هو بوابة الميزة KubeProxyIPVS، وهي نفسها موسومة بالإهمال، وهي المفتاح الذي سيُطفئ الوضع لاحقًا. لا شيء يتوقّف ولا تتأثّر أي حركة مرور. ويضع KEP-5495 الجدول الزمني: يُتوقّع أن تصير البوابة بقيمة false افتراضيًا عند 1.40، مع إزالة الدعم كليًا عند 1.43. وسبب الإهمال أن واجهة ipvs في النواة لا تستطيع التعبير عن كل ما تحتاجه Service في Kubernetes، فظلّ وضع ipvs يسقط إلى iptables تحته في أجزاء من العمل. والوجهة هي وضع nftables، وهو في GA منذ 1.33 — والعناقيد لا تنتقل من تلقاء نفسها أبدًا، فعليك أن تضبط mode: "nftables" بنفسك. نفّذ ذلك في نافذة صيانة منفصلة عن ترقية الإصدار.

لماذا علقت الـ Pods لدي في ContainerCreating بعد الترقية إلى 1.37؟

إن كانت العقدة تشغّل SELinux في وضع الفرض، فالسبب المرجّح تعارض في تشارك وحدة تخزين أدخله بلوغ SELinuxMount مرتبة GA. صارت وحدات التخزين تُركَّب بالخيار -o context=<label> بدل إعادة تسميتها تعاوديًا، والتركيب الواحد لا يحمل سوى سياق SELinux واحد بالضبط — فلم يعد بإمكان Pods بتسميتين مختلفتين تتشاركان وحدةً واحدة على عقدة واحدة أن تتعايشا. يجلس أحدهما في ContainerCreating حتى ينتهي الآخر. والحالة الكلاسيكية Pod مميّز وآخر غير مميّز يتشاركان وحدة تخزين. تأكّد من ذلك بقراءة مقياس kubelet volume_manager_selinux_volume_context_mismatch_errors_total على تلك العقدة. والعلاج ضبط spec.securityContext.seLinuxChangePolicy: Recursive على الـ Pods المتأثّرة — ولاحظ أن خفض إصدار kubelet لن يحرّر وحدة التخزين، لأن الـ Pod الذي ظفر بها لا يزال ممسكًا بالسياق.

كيف أعثر على تعارضات SELinux في وحدات التخزين قبل الترقية؟

فعّل selinux-warning-controller الذي شُحن في 1.36 بتمرير --controllers=*,selinux-warning-controller إلى kube-controller-manager، وتأكّد أنك لم تعطّل صراحةً بوابة الميزة SELinuxChangePolicy. ثم اقرأ مقياسين. يحمل selinux_warning_controller_selinux_volume_conflict أسماء الـ Pods المتعارضة وفضاءات أسمائها كتسميات — وتلك هي قائمة عملك، وهو يبلّغ عن التعارضات حتى وإن كانت الـ Pods على عقد مختلفة، لأن المجدول قد يجمعها لاحقًا. أما volume_manager_selinux_volume_context_mismatch_warnings_total، الذي يُطلقه kubelet بينما SELinuxMount لا يزال معطّلًا، فلا يحمل تسميةً باسم Pod لكنه يعطي العدّ الصادق للـ Pods التي ستفشل فعلًا. أنت بحاجة إلى الاثنين معًا. افعل ذلك على 1.36، بينما عدّاد التحذيرات غير صفري وكل شيء لا يزال يعمل.

ماذا يفعل seLinuxChangePolicy: Recursive فعلًا، وهل ضبطه الآن آمن؟

يُخرج ذلك الـ Pod من مسار خيار التركيب ويُبقي سلوك ما قبل 1.37: يمشي runtime الحاويات على وحدة التخزين ويعيد تسمية كل ملف فيها. والكلفة هي مكسب الأداء — فعلى وحدة كبيرة أو بعيدة تكون إعادة التسمية التعاودية بطيئة فعلًا — والفائدة أن Pods بتسميتين مختلفتين تستطيعان تشارك الوحدة من جديد. وهو واجهة Pod مستقرّة منذ 1.36، فضبطه اليوم لا يغيّر شيئًا في سلوك عنقودك الآن ويفعل الصواب لحظة هبوط الترقية. وذلك ما يجعله أرخص تأمين في هذا الإصدار. طبّقه عبر MutatingAdmissionPolicy أو محرّك سياسات بدل تحرير كل Deployment، واقصره على فضاءات الأسماء التي سمّتها مقاييس التعارض فعلًا لا على العنقود كلّه — فتطبيق Recursive بلا تمييز يرمي التحسين عن كل حِمل لم يكن مهدَّدًا أصلًا.

عنقودي لا يستعمل SELinux. هل ينطبق أيٌّ من هذا؟

لا شيء منه تقريبًا. حين تكون SELinux غير متاحة أو معطّلة في النواة، يتخطّى kubelet مسار شيفرة SELinux كاملًا، فيصير تغيير SELinuxMount بلا أثر عندك ويمكن تجاهل القسم الأكبر من هذا المقال. ويبقى منطبقًا قيد الـ static pods — ابحث في دليل الـ static pods على كل عقدة عن secretRef وconfigMapRef، فهذا هو السلوك الافتراضي منذ 1.34 ومن ثمّ لا يعضّ إلا العناقيد التي تخطّت إصدارات — إضافةً إلى دَين cgroup v2 من 1.35 إن لم تكن قد سدّدته، وترحيل containerd 2.x، وهو غير عاجل على 1.37 لكن يجب إنجازه قبل 1.38. لكن افحص getenforce على كل عقدة بدل الافتراض: فالأسطول المختلط الذي يشحن فيه مجمّع عقد واحد صورةً مفعّلة SELinux أشيع ممّا يتوقّع الناس.

هل توقف 1.37 إقلاع kubelet على cgroup v1؟

ذلك التغيير ليس من 1.37. الإعداد failCgroupV1 في kubelet قيمته الافتراضية true منذ 1.35، فالعقدة على cgroup v1 يفشل إقلاع kubelet عليها منذ ذلك الحين إلا أن يكون أحدهم أضاف failCgroupV1: false. ولا تزال 1.37 تحترم ذلك التجاوز. وتُزيل KEP-5573 دعم cgroup v1 من أصله في إصدار لاحق، لكن لم يُلتزم بأي تاريخ، فينبغي أن يكون افتراض التخطيط «أيّ إصدار يناسب SIG Node» لا شهرًا بعينه. وسبب عدم الجلوس على التجاوز أن تغيير حجم الـ Pod في مكانه وحماية الذاكرة المتدرّجة كليهما يتطلّب cgroup v2 ولا يعمل بدونه ببساطة. وتحويل عقدة يعني تغييرًا في سطر أوامر النواة (systemd.unified_cgroup_hierarchy=1)، وإعادة إقلاع، وجعل مشغّل cgroup في الـ runtime متوافقًا مع مشغّل kubelet.

هل أحتاج containerd 2.0 من أجل Kubernetes 1.37؟

لا — وهذا هو الادّعاء الأكثر قولًا بالمقلوب. containerd 1.x لا يزال يعمل مع kubelet من 1.37. وهو يفعل ذلك عبر مسار احتياطي: يسأل kubelet الـ runtime عن مشغّل cgroup لديه عبر استدعاء CRI المسمّى RuntimeConfig، ولا ينفّذ containerd 1.y ذلك الاستدعاء، فيسقط kubelet إلى قيمة --cgroup-driver عنده مع زيادة المقياس cri_losing_support. وكان هذا المسار مجدولًا للإزالة في 1.37 ثم أُجّل إصدارًا واحدًا لمواءمة نافذة دعم containerd 1.7، فهو يزول في 1.38 — وعندها تفشل إصدارات containerd الأقدم فعلًا أمام kubelet الأحدث. وثمّة تحفّظان يجعلان الأمر أقل راحةً ممّا يبدو: ينتهي الدعم الممتد لـ containerd 1.7 نفسه في سبتمبر 2026، ولا تحوي مصفوفة دعم Kubernetes المنشورة لدى containerd أي صفّ لـ 1.37 بعد، فلا يستطيع أحد اليوم أن يستشهد باقتران معتمَد رسميًا. دقّق بـ cri_losing_support وcontainerRuntimeVersion، واحجز الترحيل قبل أن تأخذ 1.38 — في نافذته الخاصة، لا مضمومًا إلى ترقية إصدار.

ما البديل عن static pod يقرأ Secret؟

DaemonSet، في كل حالة تقريبًا. الـ static pods يديرها kubelet من دليل على القرص لا تُنشَأ عبر خادم الـ API، فما كان يُفترض أن تعمل قراءة كائنات الـ API أصلًا — لكن عِلّة سمحت بها عبر حقول مثل configMapRef وsecretRef. والبوابة PreventStaticPodAPIReferences التي تغلق هذه العِلّة قيمتها الافتراضية مفعّلة منذ 1.34، فهذا ليس جديدًا في 1.37 — وإنما يبدو جديدًا للعناقيد التي قفزت عدّة إصدارات. وقد قالت النظرة المسبقة إلى 1.37 إن البوابة تُزال من أصلها؛ بينما لا يزال مرجع بوابات الميزات المنشور مع 1.37 يدرجها بوابةً في بيتا بقيمة افتراضية true. وفي الحالين، لا تبنِ خطتك على منفذ الخروج. ومعظم الـ static pods موجود لأسباب تاريخية، من زمن قبل أن تبلغ الـ DaemonSets قدرتها الحالية؛ و DaemonSet يقرأ الـ Secrets بشكل طبيعي، ويحصل على تحديثات متدرّجة، ويظهر حيث يبحث عنه الناس. وإن وجب أن يبقى شيء static فعلًا، فضع القيمة مباشرةً في البيان أو اربط ملفًا من المضيف واقرأه من هناك. كلاهما أسوأ من DaemonSet وكلاهما يعمل. اعثر عليها قبل الترقية بدل اكتشافها عقدةً عقدة.

هل أرقّي مباشرةً من 1.34 أو 1.35 إلى 1.37؟

تستطيع — فسياسة انحراف الإصدارات تسمح لـ kubelet بالتأخّر حتى ثلاثة إصدارات فرعية عن خادم الـ API، وkubeadm يتولّى مستوى التحكّم إصدارًا في كل مرة — لكن الخطر ليس في الميكانيكا بل في القراءة. تخطّي الإصدارات يعني أن كل إهمال في النسخ التي تخطّيتها يصل دفعةً واحدة، وهي موثّقة لكل إصدار على حدة لا تراكميًا. وقادمًا من 1.35 ترث كذلك تغييرات 1.36: تحذير إهمال ExternalIPs في Service، وبلوغ SELinuxMountReadWriteOncePod وSELinuxChangePolicy مرتبة GA، واستقرار فضاءات أسماء المستخدمين للـ Pods. وقادمًا من 1.34 ترث فوق ذلك انقلاب القيمة الافتراضية لـ failCgroupV1 في 1.35 — وهو ما يوقف kubelet تمامًا على عقدة بـ cgroup v1. اقرأ تدوينة فريق الإصدار لكل نسخة تتخطّاها. ثلاث منها تستغرق نحو عشرين دقيقة وهي استعمال لنافذة الترقية أفضل من معظم ما يُوضع فيها.

هل أستطيع التراجع عن ترقية 1.37؟

جزئيًا، والأجزاء مهمّة. تغيير وضع kube-proxy يتراجع بنظافة — فهو ConfigMap وإعادة تشغيل DaemonSet. وseLinuxChangePolicy حقل في الـ Pod يتصرّف تصرّفًا واحدًا على 1.36 و1.37، وهذا سبب أن تطبيقه مسبقًا لا يكلّف شيئًا. أما مستوى التحكّم فلا يتراجع: لا يملك kubeadm upgrade مسار خفض إصدار، فالعودة إلى الوراء تعني استعادة لقطة etcd التي أخذتها قبل البدء وفقدان كل ما كُتب منذئذٍ. خذ تلك اللقطة. وثمّة عطل واحد يفاجئ الناس: خفض إصدار kubelet لا يحرّر Pod فشل تركيبه بسبب تعارض SELinux، لأن الـ Pod الذي ظفر بوحدة التخزين لا يزال ممسكًا بسياق التركيب. والرافعة هي seLinuxChangePolicy على كلا الـ Pods، لا رقم الإصدار.

هل ستزول metrics.k8s.io v1beta1؟

ليس بعد، ولا بلا إشعار. تتخرّج metrics.k8s.io إلى v1 في 1.37 بعد قرابة تسع سنوات في بيتا، وتبقى v1 وv1beta1 معًا مقدَّمتين خلال الانتقال تحديدًا كي يجري التبنّي وفق وتيرتك. ولا تغييرات وظيفية متوقّعة — فالتخرّج اعتراف بالاستقرار لا إدخال له. وkubectl top و HorizontalPodAutoscaler يواصلان العمل بلا أي إجراء. والجدير بالفعل هو نقل الشيفرة التي تملكها — أداة توسيع مخصّصة، أو تقرير سعة، أو خلفية لوحة معلومات — عن مسار بيتا بينما الاثنان متاحان. وسياسة الإهمال في Kubernetes تضمن لواجهة بيتا تسعة أشهر أو ثلاثة إصدارات على الأقل بعد الإهمال، وv1beta1 لم يُعطَ تاريخ إزالة إطلاقًا، فهذا ترتيبُ بيت لا موعد نهائي.

المصادر

المصادر الأولية أولًا. النظرة المسبقة من فريق الإصدار وسجل تغييرات 1.37 هما التصريحان الموثوقان الوحيدان عمّا في هذا الإصدار؛ والـ KEPs هي المكان الوحيد الذي تُكتب فيه الجداول الممتدة عبر إصدارات، وهذا ما يجعلها الترياق لملخّص يقول «أُزيل في 1.37» عن شيء يُزال في 1.43. وحيث يصحّح هذا المقال شيئًا — أن عطل cgroup تغييرٌ من 1.35، وأن قيد الـ static pods تغييرٌ من 1.34، وأن ipvs مهجور منذ 1.35 لا مُزال في 1.37، وأن حافة containerd أمامك عند 1.38 لا خلفك عند 1.36 — فالخلاف مع التغطية الثانوية لا مع المشروع. وثمّة خلاف واحد داخل المشروع نفسه يستحق التسمية: تقول النظرة المسبقة إن البوابة PreventStaticPodAPIReferences أُزيلت في هذا الإصدار، بينما لا يزال مرجع بوابات الميزات المنشور مع 1.37 يدرجها. وصفحة المرجع هي الجديرة بالثقة في تحديد ما شُحن فعلًا.

  1. Kubernetes v1.37 Sneak Peek - the release team's own list of what is deprecated, removed and breaking in this release, published 31 July 2026. This is the document that separates "new in 1.37" from "still in progress", and it carries its own caveat that the information reflects the state of the release before the release date
  2. Kubernetes CHANGELOG-1.37.md - the authoritative record once the release is cut on 26 August 2026. Where this article and the changelog disagree after that date, the changelog is right and this page is a snapshot of the plan
  3. SELinux Volume Label Changes goes GA (and likely implications in v1.37) - the pre-announcement by the feature's own authors. It contains the five conditions for a mount-option relabel, the two conflict scenarios, the seLinuxChangePolicy opt-out, the selinux-warning-controller and the recommended upgrade path. This is the single most important source for this article
  4. KEP-1710: Speed up SELinux volume relabeling using mounts. The enhancement proposal behind SELinuxMount, including why a mount can hold only one context and therefore why volume sharing across differently labelled pods stops working
  5. KEP-1710, "Story 3: cluster upgrade" - the upgrade scenario written by the authors, which is the closest thing to an official runbook for this change
  6. Kubernetes - Configure a Security Context for a Pod or Container: seLinuxOptions, the seLinuxChangePolicy field, efficient SELinux volume relabeling and the selinux-warning-controller
  7. Kubernetes API reference - CSIDriver: the seLinuxMount field a driver has to set to true before the kubelet will mount its volumes with a context option. Drivers that do not set it keep the old recursive behaviour, which is why the blast radius of this change is driver-specific
  8. Kubernetes - Feature Gates: the per-release state of SELinuxMount, SELinuxChangePolicy, SELinuxMountReadWriteOncePod and KubeProxyIPVS. The table is the fastest way to check a claim about which release flipped which default
  9. KEP-5495: Deprecate ipvs mode in kube-proxy. The deprecation timeline this article quotes - warning now, feature gate defaulting to false by v1.40, removal by v1.43 - comes from its graduation criteria and nowhere else
  10. KEP-5495 README on GitHub: the same document at its source, including the graduation criteria table with the release numbers
  11. KEP-3866: nftables kube-proxy backend, including the section titled "The ipvs mode of kube-proxy will not save us" - the technical argument that ipvs mode never stopped depending on iptables underneath, which is the reason for the deprecation
  12. NFTables mode for kube-proxy - the introduction to the mode that replaces both iptables and ipvs, and the migration notes for moving to it
  13. Kubernetes - kube-proxy configuration (v1alpha1) reference: the `mode` field this article tells you to read and change, and the rest of the KubeProxyConfiguration surface
  14. KEP-5573: Remove CGroup v1 support. The staged removal plan behind the kubelet's failCgroupV1 setting, and the statement that the override is temporary
  15. Kubernetes - About cgroup v2: how to check which version a node is on, the requirements for cgroup v2, and the features that depend on it
  16. Kubernetes - kubelet configuration (v1beta1) reference: failCgroupV1 and the rest of the KubeletConfiguration fields this article edits
  17. Kubernetes - Static Pods: what they are, why the kubelet manages them directly rather than through the API server, and therefore why referencing API objects from one was never supposed to work
  18. kubernetes/kubernetes issue 140226 - the discussion behind prohibiting Secret and ConfigMap references from static pods, and the fate of the PreventStaticPodAPIReferences feature gate that allowed an opt-out. The gate has defaulted to on since v1.34; the v1.37 sneak peek says it was removed in this release while the shipped v1.37 feature-gates reference still lists it, so the reference page is the one to trust
  19. kubernetes/kubernetes issue 138671 - the deprecation of `kubectl run --filename/-f`, on the grounds that the pod it produces is always built purely from the command-line arguments
  20. KEP-5207: metrics.k8s.io API definition. The enhancement that graduates the resource metrics API to stable after nearly nine years in beta, and the statement that v1 and v1beta1 both remain available during the transition
  21. Kubernetes - Resource metrics pipeline: what metrics.k8s.io actually serves, who serves it, and how `kubectl top` and the HorizontalPodAutoscaler consume it
  22. Kubernetes - Horizontal Pod Autoscaling: the largest consumer of the resource metrics API, and the reason its graduation matters beyond `kubectl top`
  23. KEP-2033 / KEP-4960: Kubelet in UserNS, also known as rootless mode. Graduating to beta in v1.37, which lets node components run as an unprivileged user on the host while still appearing as root inside the namespace
  24. Kubernetes - User namespaces for pods: the workload-level feature that reached GA in v1.36, distinct from the rootless kubelet but built on the same kernel mechanism
  25. KEP-1432: Volume health monitoring. Reset to alpha in v1.37 with four new CSI RPCs and three new status fields, after an initial implementation in v1.21 that never graduated
  26. Kubernetes v1.34: Of Wind & Will - the release that opened the containerd 1.x end-of-support discussion and the CRI cgroup-driver work behind it. Read alongside the container runtimes page, which records where that timeline actually ended up: the fallback is dropped in v1.38, not v1.36
  27. Kubernetes v1.36 release announcement - the release that made user namespaces GA, graduated SELinuxMountReadWriteOncePod, and shipped the selinux-warning-controller that this article tells you to switch on
  28. Kubernetes v1.36: Deprecation and removal of Service ExternalIPs - note that despite the title, v1.36 only deprecates the field and starts emitting warnings; kube-proxy support goes off by default no earlier than v1.40 and removal is no earlier than v1.43. A good illustration both of why reading one release's notes is not enough, and of how a headline turns into a rumour
  29. Kubernetes - Releases: the supported branches and their end-of-life dates. The source for the support window this article uses to argue about how much runway a cluster actually has
  30. Kubernetes - Patch releases: the cadence, the support period and the maintenance-mode window at the end of each minor release's life
  31. Kubernetes - Version skew policy: how far the kubelet may lag the API server, which is what makes a staged node upgrade legal in the first place
  32. Kubernetes - Upgrading kubeadm clusters: the control plane first, then one node at a time, with drain and uncordon around each. The sequence the runbook in this article slots into
  33. Kubernetes - Deprecation policy: the rules that govern how long a deprecated feature must survive before removal, which is why an ipvs deprecation in v1.37 cannot become a removal before v1.43
  34. Kubernetes - Container runtimes: installing and configuring containerd or CRI-O, including the cgroup driver requirement that ties this article to the cgroup v2 migration. It is also the page that settles the containerd question, stating that older containerd versions still work today through the kubelet's cgroup-driver fallback and that in Kubernetes 1.38 that fallback is dropped and they will fail with newer kubelets
  35. containerd - Versioning and release: the release-status table and the Kubernetes/containerd support matrix. As of September 2026 the matrix stops at Kubernetes 1.36 (2.3.0+, 2.2.0+) and has no row for 1.37, and containerd 1.7's extended LTS support ends in September 2026
  36. Kubernetes - Releases: the release and support dates quoted in this article, including v1.35 on 17 December 2025, v1.36 on 22 April 2026 and v1.37 on 26 August 2026
  37. Kubernetes - MutatingAdmissionPolicy: the in-tree way to apply the seLinuxChangePolicy opt-out across a namespace or a cluster without editing every workload by hand
  38. Kyverno: a policy engine the SELinux blog names explicitly as a way to apply the opt-out fleet-wide. Listed because the alternative - patching every Deployment and StatefulSet individually - does not scale past a small cluster
  39. Gateway API v1.6: TCPRoute and UDPRoute graduate to Standard. Out of scope for this article but on the same upgrade window for most clusters, and the reason the ingress migration keeps appearing in the same maintenance plan

Was this useful?