عمليات Kubernetes المتقدمة

التكلفة والكفاءة في Kubernetes

18 دقيقة الدرس 9 من 30

التكلفة والكفاءة في Kubernetes

تتناسب فواتير البنية التحتية السحابية لدى كبرى المزودين تناسباً مباشراً مع وحدات المعالجة والذاكرة التي تحجزها — لا فقط ما تستهلكه حمولاتك فعلياً. في مجموعة Kubernetes ناضجة، ما بين 30 و60% من الموارد المخصصة تجلس خاملة في الغالب، تدفع مقابل طاقة استيعابية لا تعالج بايتاً واحداً من حركة المرور الحقيقية. هذا الدرس يتناول سد هذه الفجوة: فهم مصادر الهدر، وقياسه بدقة، واستخدام آليات جدولة Kubernetes لتشغيل المزيد من العمل على عدد أقل من العقد.

هذه التقنيات ليست ضبطاً اختيارياً. على نطاق الشركات الكبرى — آلاف العقد ومئات الفرق — تحسين الكفاءة بنسبة 10% يتحول مباشرةً إلى ملايين الدولارات سنوياً ويُقلل بشكل ملموس من البصمة الكربونية للبنية التحتية.

السبب الجذري: الطلبات مقابل الحدود مقابل الاستخدام الفعلي

يمكن لكل مواصفة Pod تحديد بُعدَين للموارد لكل حاوية: الطلبات (ما يحجزه المجدول) والحدود (السقف الذي يفرضه النواة وقت التشغيل). المجدول لا يرى إلا الطلبات؛ يضع Pod على عقدة عندما تكون الطاقة المخصصة مطروحاً منها مجموع طلبات الحاويات الموجودة أكبر من أو تساوي طلبات الحاوية الجديدة. الحدود لا تؤثر في القرار.

هذا يُفرز ثلاثة أنماط مميزة للهدر:

  • طلبات مبالغ فيها: حاوية تطلب 2 CPU / 4 Gi لكنها تستهلك عادةً 0.3 CPU / 800 Mi. يحجز المجدول 2 CPU كاملة على عقدة؛ لا يمكن لأي Pod آخر استخدام الهامش — رغم أن 85% منه غير مستخدم.
  • طلبات مفقودة: حاوية بلا طلب موارد تُجدول كأنها تحتاج صفراً من CPU/الذاكرة. يبدو ذلك اقتصادياً لكنه يسبب إخلاء خطيراً للجيران: عند ضغط العقدة، يخلي Kubernetes هذه الحاويات أولاً، مما يسبب انقطعات غير متوقعة.
  • حدود عالية أو غير محددة: بدون حد لوحدة المعالجة، يمكن لحاوية واحدة مضطربة سرقة CPU من كل الجيران على العقدة. بدون حد للذاكرة، يقتل OOM حاويات عشوائية على العقدة.
المجدول يهتم بالطلبات؛ kubelet يهتم بالحدود. الضبط الصحيح يعني ضبط الطلبات بما يعادل النسبة المئوية 95 من الاستخدام الفعلي، والحدود بمقدار 2-3 أضعاف ذلك. ضبط الطلبات والحدود بنفس القيمة (ضمان QoS) يزيد من قابلية التنبؤ بالجدولة لكنه يهدر الطاقة الاستيعابية للحمولات المتقلبة — استخدمه فقط للحاويات الحساسة للتأخير.

قياس الهدر الخامل بأوامر حقيقية

لا يمكنك ضبط ما لا تقيسه. الأداة الأولى هي kubectl top مدعومةً بـ Metrics Server. للتحليل التاريخي الأعمق، استعلم Prometheus مباشرةً باستخدام PromQL.

# تثبيت Metrics Server (إن لم يكن موجوداً) kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml # استخدام CPU والذاكرة الحالي لكل pod في نطاق kubectl top pods -n production --sort-by=cpu # الاستخدام الحالي لكل عقدة — يُظهر المخصص مقابل المستخدم kubectl top nodes # --- استعلامات PromQL لتحليل الكفاءة (في Prometheus/Grafana) --- # نسبة استخدام طلب CPU لكل نطاق (نسبة الفعلي إلى المطلوب) # القيم المنخفضة = مبالغة في الطلب؛ الهدف 60-80% sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (namespace) / sum(kube_pod_container_resource_requests{resource="cpu",container!=""}) by (namespace) # هدر الذاكرة: مطلوبة لكن غير مستخدمة لكل pod خلال ساعة ( avg_over_time(kube_pod_container_resource_requests{resource="memory"}[1h]) - avg_over_time(container_memory_working_set_bytes{container!=""}[1h]) ) / (1024*1024) # النتيجة بـ MiB # Pods بلا طلب cpu (جدولة خطيرة) kube_pod_container_resource_requests{resource="cpu"} == 0 # المخصص للعقدة مقابل مجموع طلبات الحاويات (نسبة الإفراط في الاشتراك) sum(kube_node_status_allocatable{resource="cpu"}) by (node) - sum(kube_pod_container_resource_requests{resource="cpu"}) by (node)

تُظهر المجموعة السليمة عادةً استخدام CPU عند 50–70% من الطلبات والذاكرة عند 60–75%. القيم دون 30% تشير إلى هدر كبير. القيم فوق 85% تعني أن المجموعة تعمل بكثافة — أنت على بعد حركة مرور واحدة من موجة إخلاء.

تكديس Bin-Packing: جدولة المزيد من Pods لكل عقدة

التكديس هو ممارسة احتواء أكبر عدد ممكن من Pods على أقل عدد من العقد. Kubernetes 1.22+ يُزوّد بمكوّن تسجيل تكديس مدمج يُسمى MostAllocated. افتراضياً يستخدم المجدول LeastAllocated (التوزيع)، وهو أكثر أماناً لكنه أغلى. في المجموعات المُحسَّنة للتكلفة تقلب هذا عبر KubeSchedulerConfiguration.

# KubeSchedulerConfiguration لتفعيل التكديس (MostAllocated) # انشره كـ ConfigMap وأشر إليه من static pod الخاص بـ kube-scheduler apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler pluginConfig: - name: NodeResourcesFit args: scoringStrategy: type: MostAllocated # تكديس الحاويات بدلاً من التوزيع resources: - name: cpu weight: 1 - name: memory weight: 1 plugins: score: disabled: - name: NodeResourcesBalancedAllocation # تعطيل التوزيع الافتراضي enabled: - name: NodeResourcesFit --- # على EKS/GKE/AKS — استخدم API الجدولة المُدارة أو دمج Karpenter # دمج Karpenter المدمج يحقق التكديس تلقائياً: apiVersion: karpenter.sh/v1beta1 kind: NodePool metadata: name: default spec: disruption: consolidationPolicy: WhenUnderutilized # استنزاف وإنهاء العقد دون العتبة consolidateAfter: 30s template: spec: nodeClassRef: name: default requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"]
Bin-packing vs spread scheduling: two strategies for pod placement LeastAllocated (Spread) 3 nodes required — higher cost Node 1 Pod A Pod B idle Node 2 Pod C idle Node 3 Pod D idle bin-pack MostAllocated (Bin-pack) 2 nodes — lower cost, node 3 removed Node 1 Pod A Pod B Pod C small idle Node 2 Pod D idle Node 3 TERM.
يُوزع LeastAllocated الحاويات على 3 عقد تاركاً طاقة خاملة كبيرة؛ يكدّسها MostAllocated على عقدتين مما يتيح إنهاء العقدة الثالثة وتوفير تكلفتها.

مُحسّن الحاوية العمودي (VPA): الضبط التلقائي للحجم

يُراقب الحجم العمودي التلقائي للحاوية الاستهلاك الفعلي للموارد عبر الوقت ويوصي — أو يطبق تلقائياً — طلبات وحدوداً مضبوطة صحيحاً. يحل المشكلة البشرية: المهندسون لا يراجعون المانيفستات تقريباً بعد النشر الأولي.

يعمل VPA في ثلاثة أوضاع. في الإنتاج، ابدأ بـ Off (توصية فقط) أو Initial (يضبط عند إنشاء الحاوية دون إعادة تشغيل مباشرة) قبل التدرج إلى Auto. في وضع Auto يُخلي VPA الحاويات ويُعيد إنشاءها عند اكتشاف انحراف كبير، مما يعني أنك تحتاج PodDisruptionBudgets قبل تفعيله.

# تثبيت VPA (يتطلب metrics-server) git clone https://github.com/kubernetes/autoscaler.git cd autoscaler/vertical-pod-autoscaler ./hack/vpa-up.sh # كائن VPA يستهدف Deployment — وضع التوصية (نقطة بداية آمنة) apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: api-server-vpa namespace: production spec: targetRef: apiVersion: apps/v1 kind: Deployment name: api-server updatePolicy: updateMode: "Off" # توصية فقط؛ استخدم "Auto" بعد وضع PDBs resourcePolicy: containerPolicies: - containerName: api-server minAllowed: cpu: 50m memory: 64Mi maxAllowed: cpu: 4 memory: 8Gi controlledResources: ["cpu", "memory"] # فحص توصيات VPA kubectl describe vpa api-server-vpa -n production # ابحث عن: Status > Recommendation > ContainerRecommendations # Lower Bound = حد أدنى آمن | Target = موصى به | Upper Bound = أقصى ذروة مرصودة # تطبيق القيم الموصى بها يدوياً (سير عمل وضع Off) kubectl set resources deployment/api-server \ --requests=cpu=320m,memory=420Mi \ --limits=cpu=1200m,memory=1200Mi \ -n production
لا يمكن لـ VPA وHPA التحكم في CPU على نفس Deployment. إذا كنت تستخدم HPA للتوسع الأفقي بناءً على CPU، اضبط VPA للتحكم في الذاكرة فقط (controlledResources: ["memory"]). هذا يمنحك أفضل الحالتين: HPA يوسع أفقياً تحت الحمل، VPA يُبقي طلبات الذاكرة دقيقة.

حصص موارد النطاق وحدود النطاق

على مستوى المجموعة، تبدأ إدارة التكلفة بـ ResourceQuota (سقف على إجمالي استهلاك الموارد لكل نطاق) وLimitRange (يفرض قيماً افتراضية وحدوداً لكل حاوية). هذه رافعات السياسة: الفرق لا تستطيع المبالغة في التخصيص عن طريق الخطأ لأن خطاف القبول يرفض النشر قبل أن يصل إلى أي عقدة.

# ResourceQuota: سقف صارم لنطاق team-frontend apiVersion: v1 kind: ResourceQuota metadata: name: team-frontend-quota namespace: team-frontend spec: hard: requests.cpu: "20" # حد أقصى 20 نواة معالجة مطلوبة عبر كل الحاويات requests.memory: 40Gi limits.cpu: "40" limits.memory: 80Gi pods: "60" # حد أقصى 60 pod في هذا النطاق persistentvolumeclaims: "10" --- # LimitRange: طلبات/حدود افتراضية تُحقن عند إغفال pod لها apiVersion: v1 kind: LimitRange metadata: name: team-frontend-limits namespace: team-frontend spec: limits: - type: Container default: # يُطبق عند عدم تحديد حد cpu: 500m memory: 512Mi defaultRequest: # يُطبق عند عدم تحديد طلب cpu: 100m memory: 128Mi max: # سقف صارم لكل حاوية cpu: "4" memory: 8Gi min: cpu: 10m memory: 32Mi
ResourceQuota يمنع إنشاء أي pod بمجرد امتلاء النطاق. بدون رصد، سيتلقى المهندسون خطأ "تجاوزت الحصة" غامضاً أثناء نشر إنتاجي في أسوأ لحظة. أنشئ تنبيهاً على استخدام حصة النطاق فوق 80% باستخدام PromQL هذا: kube_resourcequota{type="used"} / kube_resourcequota{type="hard"} > 0.8. نبّه فريق المنصة عند 90%.

تحديد وإزالة الحمولات الخاملة

فضلاً عن ضبط الحاويات الجارية، يأتي هدر كبير من حمولات لا ينبغي أن تعمل أصلاً: بيئات التطوير المتركة خلال عطل نهاية الأسبوع، Deployments منسية لميزات مهجورة، CronJobs بلا مستهلكين. تدقيق قياسي:

# إيجاد Deployments بصفر نسخ جاهزة (مكسورة أو منسية على الأرجح) kubectl get deployments -A -o json \ | jq '.items[] | select(.status.readyReplicas == 0 or .status.readyReplicas == null) | {ns: .metadata.namespace, name: .metadata.name}' # إيجاد pods في حالة Pending لأكثر من 10 دقائق (غير قابلة للجدولة = حصة مهدرة) kubectl get pods -A --field-selector=status.phase=Pending \ -o custom-columns=\'NS:.metadata.namespace,POD:.metadata.name,AGE:.metadata.creationTimestamp\' # إجمالي CPU/ذاكرة مطلوبة لكل نطاق — اكتشف أي فريق أكبر منفق kubectl get pods -A -o json | jq -r \' .items[] | .metadata.namespace as $ns | .spec.containers[].resources.requests | [$ns, (.cpu // "0"), (.memory // "0")] | @csv\' \ | sort | uniq -c | sort -rn | head -20 # تخفيض نطاقات غير الإنتاج خارج ساعات العمل (استخدم CronJob) kubectl scale deployment --all --replicas=0 -n staging

تخصيص التكاليف: الفوترة والعرض

يُصلح الضبط الصحيح المشكلة التقنية؛ الفوترة تُصلح مشكلة الحوافز. عندما لا ترى الفرق فاتورتها السحابية، ليس لديها سبب للاهتمام بالكفاءة. النهج المعياري في شركات التقنية الكبرى هو تسمية كل مورد بتسميات الفريق والبيئة، ثم تجميع التكلفة بتلك التسميات باستخدام أدوات مثل OpenCost (مشروع CNCF، مجاني) أو Kubecost.

أدنى اتفاقية تسمية قابلة للتطبيق — افرضها عبر خطاف قبول يرفض الحاويات التي تفتقر إلى هذه التسميات:

  • team: payments — رمز الفريق المالك
  • env: production — البيئة (production / staging / dev)
  • component: api — مكون الخدمة
  • cost-center: CC-1042 — رمز المالية للفوترة
مثيلات Spot/Preemptible هي الرافعة الأكبر. الحمولات عديمة الحالة والمتحملة للأعطال — خوادم الويب، ومعالجات الدفعات، وعمال المعالجة غير المتزامنة — يمكن تشغيلها بتكلفة أقل بنسبة 60–80% على Spot. استخدم NodePools من Karpenter (أو مجموعات عقد Cluster Autoscaler) لجدولة الحاويات المتسامحة مع Spot تلقائياً. ادمجها مع PodDisruptionBudgets للإبقاء على نسخة واحدة على الأقل على مثيل on-demand. هذا التغيير الواحد كثيراً ما يمثل 40%+ من تخفيض تكاليف المجموعة.

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

ES
Edrees Salih
منذ ساعة

We are still cooking the magic in the way!