أحمال عمل Kubernetes وإعدادها

طلبات الموارد والحدود

18 دقيقة الدرس 4 من 32

طلبات الموارد والحدود

يمكن لكل حاوية في Kubernetes أن تحدد رقمين لكل مورد حوسبة — المعالج والذاكرة: طلب (request) وحد (limit). هذه القيم الأربع (طلب المعالج، حد المعالج، طلب الذاكرة، حد الذاكرة) تقود ثلاث آليات مستقلة: قرارات الجدولة، وتقليص المعالج، وإنهاء الحاوية بسبب نفاد الذاكرة. الخلط بينها — أو إهمالها كلياً — هو السبب الرئيسي لكثير من الحوادث الإنتاجية في بيئات Kubernetes.

يغطي هذا الدرس الآليات الثلاث بعمق، ويشرح كيف ترتبط بأوليات نواة Linux، وكيف تحدد فئات جودة الخدمة (QoS) أي Pods تبقى حية عندما تنضب ذاكرة العقدة.

الطلبات: ما يراه المُجدوِل

الطلب هو تلميح للجدولة. يخبر مُجدوِل Kubernetes: "أحتاج على الأقل هذا القدر من المعالج والذاكرة كي أبدأ." يجمع المُجدوِل طلبات جميع Pods الجارية على عقدة ما ولا يضع Pod جديدة عليها إلا إذا كانت السعة القابلة للتخصيص كافية. وهذا الحساب يعتمد على الطلب فحسب، لا على ما تستخدمه الحاوية فعلياً في وقت التشغيل.

تُعبَّر طلبات المعالج بوحدة m (ميلي-نواة). نواة كاملة = 1000m. طلب بقيمة 250m يعني أن الحاوية تحتاج ربع نواة معالج. طلبات الذاكرة بالبايتات مع لواحق Mi وGi. على مستوى النواة، يضبط الطلب قيمة cpu.shares في مُجدوِل CFS الخاص بـ Linux، مما يتحكم في نصيب الحاوية من وقت المعالج عند الضغط. حاوية بطلب 500m تحصل على ضعف وقت المعالج مقارنةً بحاوية بطلب 250m حين تكون العقدة مكتظة.

فكرة أساسية: المُجدوِل يحزم الأحمال بناءً على الطلبات، لا على الاستخدام الفعلي. إذا طلبت 4Gi لكنك لا تستخدم سوى 500Mi، فأنت تحجز 3.5Gi من السعة القابلة للتخصيص على كل عقدة تحل فيها Pod. الطلب المبالغ فيه يهدر سعة الكتلة؛ والطلب المنخفض جداً يسبب فشل الجدولة أو OOM kills غير متوقعة.

الحدود: التقليص ونفاد الذاكرة

الحد هو سقف تطبيقي تفرضه نواة Linux، لا Kubernetes نفسه. حدود المعالج والذاكرة تتصرف بشكل مختلف تماماً:

  • حد المعالج — التقليص: يُنفَّذ عبر التحكم في عرض نطاق CFS (cpu.cfs_quota_us). إذا تجاوزت الحاوية حد معالجها خلال فترة جدولة (افتراضياً 100ms)، تُوقِف النواة الحاوية مؤقتاً لبقية تلك الفترة. الحاوية تظل تعمل، لكنها تحصل على دورات معالج أقل. هذا غير مرئي للتطبيق، لكنه يظهر كزمن استجابة أعلى وإنتاجية أقل. في الإنتاج، تقليص المعالج من أصعب المشاكل تشخيصاً لأن Pod تبدو صحية.
  • حد الذاكرة — إنهاء OOM: الذاكرة غير قابلة للضغط. إذا خصصت الحاوية ذاكرة أكثر من حدها، يرسل قاتل OOM في نواة Linux إشارة SIGKILL لعملية في تلك الـ cgroup — عادةً العملية الرئيسية للحاوية. يعيد Kubernetes تشغيل الحاوية بعدها، وستجد OOMKilled في مخرجات kubectl describe pod.
# مواصفات Pod إنتاجية واقعية مع الطلبات والحدود apiVersion: v1 kind: Pod metadata: name: api-server spec: containers: - name: api image: mycompany/api:v2.1.0 resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "1000m" memory: "512Mi"
# فحص الموارد الفعلية مقابل المطلوبة على العقد kubectl top nodes # فحص الموارد الفعلية مقابل المطلوبة لكل Pod kubectl top pods --all-namespaces --sort-by=memory # رصد الحاويات التي أُنهيت بسبب OOMKilled kubectl describe pod <pod-name> | grep -A 10 "Last State:" # رصد تقليص المعالج عبر مقاييس cAdvisor (استعلام Prometheus) # container_cpu_cfs_throttled_seconds_total / container_cpu_cfs_periods_total # نسبة تزيد على 0.25 (25%) تشير إلى ضغط تقليص حقيقي

فئات جودة الخدمة: من يبقى حين تجوع العقدة

يعيّن Kubernetes لكل Pod إحدى ثلاث فئات جودة الخدمة (QoS) عند الإنشاء. تحدد الفئة أولوية الإخلاء حين تنضب ذاكرة العقدة. تُستنتج الفئة تلقائياً من الطلبات والحدود التي تضبطها — لا يمكنك تعيينها مباشرةً.

  • Guaranteed (مضمونة): كل حاوية في Pod لها طلبات وحدود متساوية لكلٍّ من المعالج والذاكرة (لا يجوز حذف أي منهما). تُخلى هذه الـ Pods آخراً. استخدم هذه الفئة للخدمات الحساسة لزمن الاستجابة (معالجة الدفع، المصادقة) ولأعضاء StatefulSet التي تحمل بيانات.
  • Burstable (قابلة للانفجار): على الأقل حاوية واحدة لها طلب أو حد، لكنهما غير متساويين. هذه الفئة الأكثر شيوعاً في الواقع العملي. يُخلي kubelet هذه الـ Pods حين لا يجد BestEffort Pods ليُخليها والضغط مستمر.
  • BestEffort (أفضل جهد): لا حاوية في Pod لها أي طلبات أو حدود. تُخلى هذه الـ Pods أولاً تحت ضغط الذاكرة. مناسبة فقط للمهام الدفعية أو بيئات التطوير التي تتحمل الإنهاء التعسفي.
Kubernetes QoS Classes and Eviction Order Kubernetes QoS Classes — Eviction Priority Node (memory pressure: eviction proceeds bottom → top) Eviction order (first → last) BestEffort No requests or limits set — evicted FIRST Example: dev pods, disposable batch jobs Burstable Requests set, limits differ from requests (or partially set) Evicted after BestEffort — sorted by usage vs. request ratio Example: most production microservices Guaranteed requests == limits for ALL containers (CPU + memory) Evicted LAST — only under extreme node pressure Example: payments service, database sidecars, auth tokens
فئات جودة الخدمة وترتيب الإخلاء: تُخلى BestEffort Pods أولاً، وتُخلى Guaranteed Pods آخراً، حين ينضب الذاكرة في العقدة.

اختيار القيم الصحيحة في الإنتاج

التحدي الحقيقي هو اختيار الأرقام الصحيحة. حد ذاكرة منخفض جداً يسبب OOM kills تحت ضغط الحمل الطبيعي؛ وطلب مرتفع جداً يمنع المُجدوِل من إيواء Pods جديدة على العقد. تتبع شركات التقنية الكبرى نمطاً ثابتاً في الإنتاج:

  1. الرصد أولاً: انشر مع حدود سخية وبدون LimitRange. شغّل حمل واقعي (إعادة تشغيل ترافيك الإنتاج أو اختبار حمل بحجم الذروة). اجمع container_memory_working_set_bytes وcontainer_cpu_usage_seconds_total من Prometheus على مدى 48–72 ساعة تشمل أيام الأسبوع والعطلة.
  2. اضبط الطلب عند p90 من الاستخدام المرصود. هذا يترك هامشاً للارتفاعات المفاجئة مع تمثيل الاستهلاك المعتاد بدقة لأغراض الجدولة.
  3. اضبط حد الذاكرة عند p99 + 20% هامش أمان. لا تساوِّ حد الذاكرة بطلبه في خدمة Burstable — ستتعرض لـ OOM kill عند كل ارتفاع طبيعي.
  4. للـ Pods المضمونة (Guaranteed): ساوِّ الطلبات بالحدود فقط حين تحتاج زمن استجابة متوقعاً ويمكنك تحمل تكلفة الحجز الكامل دائماً. اقبل التكلفة: Pod مضمونة بـ cpu: 2 تحجز نواتين كاملتين على العقدة حتى في أوقات الخمول.
# استخدام VPA (Vertical Pod Autoscaler) في وضع التوصية للحصول على اقتراحات مبنية على البيانات # تثبيت VPA (إن لم يكن موجوداً) kubectl apply -f https://github.com/kubernetes/autoscaler/releases/latest/download/vertical-pod-autoscaler.yaml # إنشاء كائن VPA في وضع التوصية فقط (لا يُعدّل الـ Pods) cat <<'EOF' | kubectl apply -f - apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: api-server-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: api-server updatePolicy: updateMode: "Off" # توصية فقط؛ غيّر إلى Auto للتحجيم التلقائي EOF # بعد 24 ساعة، اقرأ التوصيات kubectl describe vpa api-server-vpa # ابحث عن: "Lower Bound"، "Target"، "Upper Bound" لكل حاوية
نصيحة إنتاجية — LimitRange على مستوى الـ Namespace: اضبط LimitRange في كل namespace بحيث تحصل الحاويات التي لا تحدد طلبات وحدوداً صريحة على قيم افتراضية معقولة، ولا تكون BestEffort بالصدفة. حدد defaultRequest وdefault (حد). هذا يمنع المطوّر من نشر حاوية تسرق كل الذاكرة المتاحة وتُجوّع الجيران بصمت.
# LimitRange على مستوى الـ Namespace — يضبط القيم الافتراضية ويفرض سقفاً أقصى apiVersion: v1 kind: LimitRange metadata: name: default-limits namespace: production spec: limits: - type: Container default: # يُطبَّق كحد حين لا يُحدَّد أي حد cpu: "500m" memory: "256Mi" defaultRequest: # يُطبَّق كطلب حين لا يُحدَّد أي طلب cpu: "100m" memory: "128Mi" max: # السقف الأقصى — لا حاوية يجوز أن تتجاوزه cpu: "4" memory: "4Gi" min: cpu: "50m" memory: "64Mi"
فخ إنتاجي — حدود المعالج في الخدمات الحساسة لزمن الاستجابة: أزالت كثير من فرق SRE في كبرى شركات التقنية حدود المعالج (limits) من Deployments الحساسة لزمن الاستجابة مع الإبقاء على الطلبات. السبب: يمكن لتقليص CFS إضافة 10–100ms من الكمون لكل طلب حتى حين تكون للعقدة طاقة فائضة، لأن النواة توقف العملية فور تجاوزها حصتها في نافذة 100ms. إذا كان SLO الخاص بك هو p99 < 50ms، فحد المعالج على الأرجح عدوك الخفي. راقب container_cpu_cfs_throttled_periods_total في Grafana وتصرف حين تتجاوز نسبة التقليص 20%.

ResourceQuota: الحوكمة على مستوى الكتلة

كائن ResourceQuota يفرض سقوفاً إجمالية عبر namespace كامل — مثلاً، لا يمكن لـ namespace الاختبار أن يستهلك مجتمعاً أكثر من 20 نواة معالج و40Gi ذاكرة. يعمل جنباً إلى جنب مع LimitRange: الأخير يحدد القيم الافتراضية والسقوف لكل حاوية؛ والأول يحدد الميزانية الإجمالية لـ namespace. كلاهما إلزامي في الكتل متعددة المستأجرين حيث تشترك الفرق في كتلة واحدة.

# ResourceQuota لـ namespace فريق عمل apiVersion: v1 kind: ResourceQuota metadata: name: team-alpha-quota namespace: team-alpha spec: hard: requests.cpu: "10" requests.memory: "20Gi" limits.cpu: "20" limits.memory: "40Gi" pods: "50" services: "10" persistentvolumeclaims: "20"

فهم طلبات الموارد والحدود وفئات QoS وكائنات LimitRange وResourceQuota أساسٌ لتشغيل Kubernetes على أي نطاق جاد. هذه الأوليات تتفاعل مع المُوسِّع التلقائي (الدرس 8) ومع إخلاء العقد، مما يجعلها من أعلى الإعدادات تأثيراً في يد مهندس DevOps.