المُجدوِل في Kubernetes هو أحد أكثر مكونات الأنظمة الموزعة تطورًا على الإطلاق. افتراضيًا يضع الـ Pods على أي عقدة تملك CPU وذاكرة كافية — وللكثير من الأعباء الوظيفية، هذا تمامًا ما تحتاجه. لكن كلاسترات الإنتاج على النطاق الواسع تتطلب دقة أكبر: أعباء GPU يجب أن تُنشر على عقد GPU، وPods الواجهة الأمامية ينبغي أن تتوزع عبر مناطق التوافر، ومهمة خط بيانات مرهقة للنظام لا ينبغي أن تشارك عقدة مع واجهة برمجية حساسة للكمون، ومجموعة عقد الـ spot-instance يجب ألا تقبل إلا الأعباء التي تتحمل الانقطاعات صراحةً. يغطي هذا الدرس العناصر الأربعة التي تمنحك تحكمًا دقيقًا ومتعمدًا في أماكن تشغيل الـ Pods.
تقارب العقد (Node Affinity): استهداف خصائص العقدة
تقارب العقد هو التطور عن nodeSelector الأقدم. بينما يدعم nodeSelector تطابق التسميات الحرفية فقط، يدعم تقارب العقد عوامل منطقية (In، NotIn، Exists، DoesNotExist، Gt، Lt) ويميز بين القواعد المطلوبة (قيود صارمة — لن تُجدول الـ Pod إذا لم تُستوفَ) وقواعد التفضيل (قيود ناعمة — يحاول المُجدول احترامها لكنه يضع الـ Pod على أي حال إذا تعذر).
# node-affinity.yaml — عبء GPU يجب أن ينشر على عقدة GPU،
# ويُفضَّل نشره على عقد مُسماة zone=us-east-1a
apiVersion: apps/v1
kind: Deployment
metadata:
name: model-inference
namespace: ml
spec:
replicas: 4
selector:
matchLabels:
app: model-inference
template:
metadata:
labels:
app: model-inference
spec:
affinity:
nodeAffinity:
# قاعدة صارمة — تبقى Pod في حالة Pending إذا لم توجد عقدة مطابقة
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: accelerator
operator: In
values: [nvidia-a100, nvidia-h100]
# قاعدة ناعمة — تفضيل المنطقة us-east-1a بوزن 80 من 100
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: [us-east-1a]
- weight: 20
preference:
matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values: [p4d.24xlarge]
containers:
- name: inference
image: company/model-server:v3.2.1
resources:
limits:
nvidia.com/gpu: "1"
# وضع تسمية على العقدة لتطابق القاعدة الصارمة:
kubectl label node gpu-node-01 accelerator=nvidia-a100
IgnoredDuringExecution: كلا نوعي القواعد يحملان اللاحقة IgnoredDuringExecution، مما يعني أن تغييرات تسميات العقد بعد الجدولة لا تُؤدي إلى إخلاء الـ Pods قيد التشغيل. هناك نوع مستقبلي RequiredDuringExecution سيضيف هذه القدرة. في الوقت الحالي، إذا أزلت تسمية من عقدة، تستمر الـ Pods العاملة التي نُشرت استنادًا إلى تلك القاعدة في التشغيل دون أي اضطراب.
تقارب وتنافر الـ Pods: التجميع والفصل
يُجدوِل تقارب الـ Pod وحدةً بالقرب من Pods أخرى تطابق محدد تسميات (أو بعيدًا عنها)، يُقاس ذلك ضمن نطاق طوبولوجي — عادةً عقدة أو منطقة توافر أو رف. هكذا تُطبق نمطين إنتاجيين حيويين: التجميع (ضع ذاكرة التخزين المؤقت على نفس عقدة الـ API لتقليص الكمون) والفصل (انشر النسخ عبر مناطق التوافر حتى لا يقضي على الكل إذا فشلت منطقة واحدة).
# pod-affinity-antiaffinity.yaml
# سيناريو: ذاكرة Redis يجب أن تعمل على نفس عقدة الـ API (تجميع)،
# ونسخ الـ API يجب ألا تشترك في نفس المنطقة (تنافر لضمان التوفر العالي)
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-api
spec:
affinity:
podAffinity:
# مطلوب: الجدولة بالقرب من pod مُسمى app=payments-cache على نفس العقدة
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: payments-cache
topologyKey: kubernetes.io/hostname # "نفس العقدة"
podAntiAffinity:
# مطلوب: لا نسختان من payments-api في نفس المنطقة
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: payments-api
topologyKey: topology.kubernetes.io/zone # "نفس منطقة التوافر"
# ناعم: يُفضَّل أيضًا عقد مختلفة ضمن نفس المنطقة
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: payments-api
topologyKey: kubernetes.io/hostname
containers:
- name: api
image: company/payments-api:v8.1.0
فخ إنتاجي — تنافر صارم للـ Pod مع عدد مناطق غير كافٍ: إذا ضبطت requiredDuringSchedulingIgnoredDuringExecution لتنافر الـ Pod بنطاق topology.kubernetes.io/zone وكان كلاسترك يحتوي على 3 مناطق لكنك تطلب 4 نسخ، ستبقى النسخة الرابعة في حالة Pending إلى الأبد — لا توجد منطقة رابعة لوضعها فيها. هذا سبب شائع لفشل التوسع الغامض. إما استخدم التنافر الناعم، أو اجعل عدد النسخ لا يتجاوز عدد المناطق المتاحة. تحقق دائمًا عبر kubectl get events --field-selector reason=FailedScheduling.
تنافر الـ Pod بمفتاح طوبولوجيا المنطقة: النسخ تتوزع عبر مناطق التوافر، لكن النسخة الرابعة تبقى معلقة عند وجود 3 مناطق فقط.
التلويث والتسامح (Taints & Tolerations): إبعاد الـ Pods عن العقد
يعمل التلويث في الاتجاه المعاكس للتقارب. التلويث (Taint) يُوضع على عقدة ويطرد جميع الـ Pods التي لا تحمل تسامحًا (Toleration) مطابقًا. هذه هي الطريقة التي تُنشئ بها مجمعات عقد مخصصة: مجمعات الـ spot-instance، ومجمعات GPU، وعقد الذاكرة العالية، وعقد حدود الامتثال التي ينبغي أن تعمل عليها أعباء بعينها فقط.
كل تلويث له ثلاثية key=value:effect. هناك ثلاثة تأثيرات:
NoSchedule: لن تُجدول الـ Pods الجديدة بدون تسامح مطابق على هذه العقدة. الـ Pods الموجودة لا تُخلى.
PreferNoSchedule: يحاول المُجدول تجنب وضع الـ Pods هنا لكنه سيفعل ذلك إذا لم يكن هناك خيار آخر. شكل ناعم من NoSchedule.
NoExecute: لا تُجدول الـ Pods الجديدة وتُخلى الـ Pods الموجودة بدون تسامح (بعد فترة سماح اختيارية عبر tolerationSeconds).
# --- إدارة التلوينات على العقد ---
# إضافة تلويث: الأعباء المتسامحة مع الـ spot فقط تعمل هنا
kubectl taint node spot-node-01 node-role=spot:NoSchedule
# إضافة تلويث: عقد يجري تصريفها (Kubernetes يفعل ذلك تلقائيًا)
kubectl taint node worker-02 node.kubernetes.io/unschedulable:NoSchedule
# إزالة تلويث (لاحظ الشرطة في النهاية)
kubectl taint node spot-node-01 node-role=spot:NoSchedule-
# عرض التلوينات الموجودة
kubectl describe node spot-node-01 | grep -A5 Taints
# --- ملف Pod مع تسامحات ---
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
namespace: data
spec:
replicas: 10
selector:
matchLabels:
app: batch-processor
template:
metadata:
labels:
app: batch-processor
spec:
# تحمل تلويث الـ spot — يمكن لهذه الـ Pod التشغيل على عقد spot
tolerations:
- key: node-role
operator: Equal
value: spot
effect: NoSchedule
# تحمل حالات عقدة غير جاهزة مؤقتة لمدة 60 ثانية
# قبل الإخلاء (يُلغي الافتراضي البالغ 300 ثانية)
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 60
# مقترن مع تقارب العقدة لإلزام عقد spot (لا مجرد تحملها)
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role
operator: In
values: [spot]
containers:
- name: processor
image: company/batch:v2.0.0
التسامحات تسمح، لا تجذب. التسامح وحده لا يضمن النشر على العقدة المُلوَّثة. يمكن للـ Pod أن تهبط أيضًا على أي عقدة غير مُلوَّثة. لإجبار النشر على مجمع تلويث بعينه، ادمج التسامح مع قاعدة nodeAffinity أو nodeSelector. هذا التوليف — تلويث + تسامح + تقارب — هو النمط الكانوني لمجمعات العقد المخصصة الذي تستخدمه كل كبرى موفري السحابة في عروض Kubernetes المُدارة (EKS, GKE, AKS).
قيود انتشار الطوبولوجيا (Topology Spread Constraints): التوزيع المتساوي
topologySpreadConstraints هو أقوى عناصر الجدولة وأكثرها قابلية للتركيب، أُضيف في إصدارات Kubernetes الحديثة (GA في 1.19). يتيح لك تحديد أقصى انحراف مسموح به — الفرق في عدد الـ Pods بين نطاق الطوبولوجيا الأكثر حملًا والأقل. حيث يُعبِّر التنافر عن قاعدة ثنائية "لا معًا"، يُعبِّر انتشار الطوبولوجيا عن هدف توزيع مستمر.
# topology-spread.yaml
# نشر 12 نسخة بأكبر قدر من التوازن عبر المناطق والعقد
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: production
spec:
replicas: 12
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
topologySpreadConstraints:
# القاعدة 1: أقصى انحراف 1 pod عبر المناطق (صارم)
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule # صارم — يحجب إذا تعذر الاستيفاء
labelSelector:
matchLabels:
app: frontend
# القاعدة 2: أقصى انحراف 1 pod عبر العقد ضمن كل منطقة (ناعم)
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway # ناعم — يُحذِّر لكن يمضي
labelSelector:
matchLabels:
app: frontend
containers:
- name: web
image: company/frontend:v5.3.0
# فحص قرارات الجدولة
kubectl get pods -n production -o wide -l app=frontend | awk '{print $7}' | sort | uniq -c
قيم whenUnsatisfiable:DoNotSchedule (صارم — تبقى الـ Pod في Pending إذا كان انتشارها سيُخل بالانحراف) وScheduleAnyway (ناعم — يختار المُجدول العقدة التي تُقلص الانحراف إلى الحد الأدنى حتى لو خالفته). في الإنتاج، يجب أن يكون الانتشار على مستوى المنطقة صارمًا (DoNotSchedule) لضمان التوفر العالي، بينما يمكن أن يكون الانتشار على مستوى العقدة ناعمًا (ScheduleAnyway) لتجنب الـ Pending غير الضروري.
التجميع معًا: نمط مجمع عقد إنتاجي
الكلاسترات الحقيقية تُركِّب العناصر الأربعة. كلاستر نموذجي لشركة كبرى يحتوي على مجمعات عقد متعددة: مجمع عام (بدون تلوينات)، ومجمع GPU (مُلوَّث accelerator=true:NoSchedule)، ومجمع spot (مُلوَّث node-role=spot:NoSchedule)، ومجمع نظام (مُلوَّث CriticalAddonsOnly=true:NoSchedule للـ daemonsets الحيوية). كل عبء وظيفي يُعلن بالضبط المجمعات التي يتحملها، وتلك التي يطلبها عبر التقارب، وكيف يريد الانتشار. هذا الفصل يمنع تداخل الأعباء المزعجة، ويُحسِّن التكلفة (العمليات الدفعية على spot، والـ APIs على الـ on-demand)، ويضمن ألا يُخلى البنية التحتية الحيوية بواسطة أعباء المستخدمين.
# التحقق من قرارات الجدولة وتشخيص Pods في حالة Pending
kubectl get events -n production --field-selector reason=FailedScheduling --sort-by='.lastTimestamp'
# محاكاة الجدولة دون نشر فعلي للـ Pod (تشغيل تجريبي)
kubectl apply -f deployment.yaml --dry-run=server
# استخدام نقطة نهاية الشرح للمُجدول (ميزة alpha في الكلاسترات الحديثة)
kubectl alpha events --for pod/my-pod-abc123 -n production
# عرض تسميات العقد (تُستخدم لكتابة قواعد التقارب)
kubectl get nodes --show-labels
# عزل عقدة (يضيف تلويث Unschedulable، يمنع الـ Pods الجديدة)
kubectl cordon worker-03
# تصريف عقدة للصيانة (يُخلي الـ Pods مع احترام PodDisruptionBudgets)
kubectl drain worker-03 --ignore-daemonsets --delete-emptydir-data --grace-period=60
# إلغاء عزل العقدة بعد الصيانة
kubectl uncordon worker-03
لا تُقيِّد كلاسترك بإفراط. كل قاعدة جدولة صارمة (requiredDuringScheduling، وتلوينات NoSchedule، وقيود طوبولوجيا DoNotSchedule) تُضيِّق مجموعة المواضع الصالحة. تراكم قواعد صارمة متعددة دون عقد كافية لاستيفائها جميعًا في آنٍ واحد يُبقي الـ Pods في حالة Pending إلى أجل غير مسمى — وغالبًا لا يُكتشف ذلك إلا في أثناء ارتفاع حاد في الحركة أو حدث فشل عقدة. فضِّل القواعد الناعمة قدر الإمكان، واختبر دائمًا مزيج قيودك بتوسيع مؤقت للـ Deployment في كلاستر تجريبي ومراقبة أي الـ Pods تصل إلى حالة Running.
نستخدم ملفات تعريف الارتباط لتشغيل هذا الموقع وتحليل الزيارات وعرض إعلانات مخصّصة. يمكنك قبول كل ملفات تعريف الارتباط أو رفض غير الأساسية منها.
سياسة الخصوصية