يجمع هذا الدرس الختامي كل مفهوم من مفاهيم البرنامج التعليمي في تصميم متماسك واحد. ستسير خلال العملية الشاملة لبناء استراتيجية قياس تلقائي لحمل عمل متقطع مدفوع بالأحداث — النوع من الأنظمة التي تعمل عند 4,000 طلب/ثانية معظم اليوم، ثم ترتفع إلى 40,000 طلب/ثانية في غضون دقيقتين عند إرسال بريد إلكتروني تسويقي. الهدف ليس تهيئة تجريبية؛ بل هو مخطط إنتاجي بأرقام حقيقية وأنماط فشل حقيقية وقرارات الحكم الأول التي تفصل بين نظام موثوق وآخر يُنبّهك في الساعة الثانية صباحًا.
حمل العمل: تشريح نظام متقطع
نظامنا المرجعي هو واجهة برمجية SaaS متعددة المستأجرين بالملف الشخصي التالي:
الخط الأساسي: 4,000 طلب/ثانية، متوسط زمن الاستجابة 45 مللي ثانية، P99 بـ 120 مللي ثانية. استخدام المعالج ~35 % على 20 عقدة m6i.2xlarge (8 vCPU لكل منها = 160 vCPU إجمالًا).
نمط الطفرة: رسائل البريد الإلكتروني للحملات التسويقية تصل في أحداث تقويمية محددة مسبقًا — عادةً الساعة 09:00 بالتوقيت المحلي لكل منطقة جغرافية من أصل 4 مناطق. معامل الطفرة 8–12x. المدة: 4–8 دقائق في الذروة، 20–30 دقيقة للعودة إلى الخط الأساسي.
الحساسية لزمن الاستجابة الطرفي: مستوى الخدمة P99 أقل من 500 مللي ثانية عند ذروة الطفرة. انتهاكه يؤدي إلى اعتمادات SLA للعملاء.
أنواع pods مختلطة: طبقة API عديمة الحالة، وطبقة عمال غير متزامنين (مستهلكو SQS)، ومجموعة Redis تُستخدم لمفاتيح الاتساق. تتوسع كل طبقة بشكل مختلف.
مبدأ التصميم: قبل كتابة أي ملف YAML، نمذج أسوأ الحالات على الورق. الطفرة 10x، تدوم 6 دقائق، ويجب أن تبقى ضمن مستوى الخدمة. هل يمكن لسلسلة القياس التلقائي (توسع pods عبر HPA → توفير عقدة بواسطة Karpenter → جاهزية الـ pod) أن تكتمل خلال أول 90 ثانية؟ إذا لا، فيجب أن تعتمد الاستراتيجية على التوسع الاستباقي بدلًا من التفاعلي.
الطبقة الأولى — HPA لطبقة API
طبقة API عديمة الحالة، مقيّدة بالمعالج، ومجزّأة أفقيًا. يستخدم HPA مقياسًا مركّبًا: معدل الطلبات المخصص لكل pod من Prometheus Adapter بالإضافة إلى المعالج كإجراء احترازي. يُضبط الاستهداف باستخدام 50 % من المعالج بشكل متحفظ لإبقاء هامش كافٍ لاستيعاب الطفرة قبل أن تصبح pods الجديدة جاهزة.
# hpa-api.yaml — HPA إنتاجي لطبقة API
# قرارات رئيسية:
# targetAverageValue 40 rps/pod (4000 rps أساسي ÷ 100 pod = 40؛ هامش الطفرة مدمج)
# scaleUp: 100% في نافذة 30 ثانية — توسع عدواني؛ pods رخيصة
# scaleDown: بطيء (stabilizeFor 300s) — تجنب الاهتزاز بعد استنزاف الطفرة
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 80 # الحد الأدنى = 4000 rps / 50 rps لكل pod؛ لا تنخفض أبدًا
maxReplicas: 600 # السقف = 30000 rps / 50 rps لكل pod؛ يطابق حد المجموعة
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "50"
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # تفاعل فوري
policies:
- type: Percent
value: 100 # ضاعف عدد pods كل 30 ثانية إذا لزم
periodSeconds: 30
- type: Pods
value: 100 # أو أضف 100 pod دفعة واحدة، أيهما أكبر
periodSeconds: 30
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300 # انتظر 5 دقائق بعد الطفرة قبل التقليص
policies:
- type: Percent
value: 20
periodSeconds: 60
مع minReplicas: 80، تمتلك المجموعة دائمًا 80 pod مجدولة مسبقًا. عند بدء الطفرة، يُطلق HPA في غضون فترة الاستطلاع الأولى (15 ثانية) ويمكنه مضاعفة عدد pods كل 30 ثانية. من 80 إلى 600 pod يستغرق نحو 4 خطوات توسع — حوالي دقيقتين — إذا كانت العقد دافئة بالفعل. هذا هو سبب أهمية طبقة المجموعة.
الطبقة الثانية — Karpenter للمجموعة
يجب على Karpenter توفير عقد جديدة أسرع مما تستنفد HPA العقد الموجودة. تُستخدم تهيئتان لـ NodePool: مجموعة أساسية من عقد on-demand من نوع m6i.2xlarge (دافئة دائمًا) ومجموعة طفرة من عقد spot من نوع m6i.4xlarge / c6i.4xlarge (تُوفَّر عند الطلب، spot للتوفير في التكلفة، نماذج كبيرة لتقليل زمن توفير العقدة).
# nodepool-burst.yaml — مجموعة طفرة Karpenter لطفرات حركة المرور
# عقد Spot: ~70% توفير في التكلفة مقارنة بـ on-demand؛ مقبول لـ API عديمة الحالة
# نماذج كبيرة: عقدة m6i.4xlarge واحدة توفر 16 vCPU — تشغيل ~50 pod لكل عقدة
# بمعنى 10 عقد جديدة تستوعب 500 pod جديدة؛ Karpenter يمكنه توفير جميعها بالتوازي
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: burst-spot
namespace: kube-system
spec:
disruption:
consolidationPolicy: WhenEmpty # دمج العقد الفارغة تمامًا فقط بعد الطفرة
consolidateAfter: 5m
budgets:
- nodes: "30%" # معدل تقليص آمن بعد استنزاف الطفرة
limits:
cpu: "1280" # 80 x m6i.4xlarge؛ سقف صارم
memory: "5120Gi"
weight: 10 # أولوية أقل من مجموعة on-demand الأساسية
template:
metadata:
labels:
pool: burst-spot
spec:
taints:
- key: pool
value: burst-spot
effect: NoSchedule # فقط pods متحملة للطفرة تحط هنا
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m6i.4xlarge", "m6i.8xlarge", "m7i.4xlarge", "c6i.4xlarge"]
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a", "us-east-1b", "us-east-1c"]
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
name: default
kubelet:
maxPods: 110
يجب أن يتحمل نشر API تلويث مجموعة الطفرة حتى يتمكن Karpenter من جدولة pods هناك. أضف tolerations إلى مواصفات الـ Deployment: key: pool, operator: Equal, value: burst-spot, effect: NoSchedule. بالإضافة إلى ذلك، استخدم topologySpreadConstraints للتوزيع عبر جميع مناطق التوفر الثلاث — إذا أصابت مقاطعة spot منطقة توفر واحدة خلال الطفرة، فإن pods المتبقية في منطقتي التوفر الأخريين تستوعب حركة المرور دون انتهاك مستوى الخدمة.
طبقتا القياس التلقائي: HPA يُطلق فورًا على pods الدافئة في المجموعة الأساسية؛ Karpenter يُوفّر عقد spot في مجموعة الطفرة بالتوازي في غضون 90 ثانية.
الطبقة الثالثة — القياس القائم على قائمة الانتظار لطبقة العمال
تعالج طبقة العمال غير المتزامنة الوظائف المُدرجة في قائمة الانتظار من قِبَل API (تغيير حجم الصور، تسليم الـ webhooks، إرسال البريد الإلكتروني). خلال الطفرة، يرتفع عمق قائمة الانتظار قبل أن تلحق طاقة العمال. يُوسّع KEDA العمال مباشرةً من عمق قائمة SQS، وهو إشارة أكثر موثوقية بكثير من المعالج لمستهلكي قوائم الانتظار.
# keda-scaledobject-worker.yaml
# توسيع العمال بناءً على عمق قائمة SQS، لا المعالج.
# cooldownPeriod 120s: إبقاء العمال نشطين بعد الطفرة لاستنزاف الأعمال المتراكمة
# minReplicaCount 5: لا تتوسع إلى الصفر؛ عمليات الإقلاع الباردة تضر الاستجابة للطفرة
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: job-worker
namespace: production
spec:
scaleTargetRef:
name: job-worker
minReplicaCount: 5
maxReplicaCount: 400
cooldownPeriod: 120
pollingInterval: 10
advanced:
restoreToOriginalReplicaCount: true
horizontalPodAutoscalerConfig:
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 200
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 180
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/job-queue
queueLength: "20" # الهدف: 20 رسالة قيد المعالجة لكل pod عامل
awsRegion: us-east-1
scaleOnInFlight: "true"
التوسع المسبق للطفرات المتوقعة
أهم رؤية للطفرات المدفوعة بالتقويم: القياس التلقائي التفاعلي بطيء جدًا. سقوط البريد الإلكتروني الساعة 09:00 معروف قبل 48 ساعة. وسّع مسبقًا قبل 10 دقائق، قبل وصول حركة المرور، باستخدام CronTrigger من KEDA أو Kubernetes CronJob بسيط يُصحّح minReplicas مباشرةً.
#!/bin/bash
# pre-scale.sh — تشغيله كـ Kubernetes CronJob عند 08:50 في كل منطقة زمنية مستهدفة
# يرفع حدود minReplicas قبل الطفرة؛ مهمة ثانية تستعيدها في 10:30
set -euo pipefail
BURST_MIN_API=300 # 15,000 rps مُوفّرة مسبقًا (300 pod x 50 rps)
BURST_MIN_WORKER=100
RESTORE_MIN_API=80
RESTORE_MIN_WORKER=5
ACTION=${1:-"burst"} # "burst" أو "restore"
if [[ "$ACTION" == "burst" ]]; then
kubectl patch hpa api-server -n production \
--type=merge \
-p "{\"spec\":{\"minReplicas\":${BURST_MIN_API}}}"
kubectl patch scaledobject job-worker -n production \
--type=merge \
-p "{\"spec\":{\"minReplicaCount\":${BURST_MIN_WORKER}}}"
echo "تطبيق التوسع المسبق: api=${BURST_MIN_API}, worker=${BURST_MIN_WORKER}"
else
kubectl patch hpa api-server -n production \
--type=merge \
-p "{\"spec\":{\"minReplicas\":${RESTORE_MIN_API}}}"
kubectl patch scaledobject job-worker -n production \
--type=merge \
-p "{\"spec\":{\"minReplicaCount\":${RESTORE_MIN_WORKER}}}"
echo "تمت الاستعادة: api=${RESTORE_MIN_API}, worker=${RESTORE_MIN_WORKER}"
fi
استخدم KEDA CronTrigger بديلًا: يدعم KEDA نوع مشغّل cron الذي يضبط desiredReplicas وفق جدول زمني دون الحاجة إلى نصوص خارجية. هذا أنظف لسير عمل GitOps لأن النية تعيش في مانيفيست ScaledObject، لا في CronJob منفصل.
تحديد الحمل كخط الدفاع الأخير
حتى مع التوسع المسبق المثالي، يمكن أن تستنفد طفرة 20x غير متوقعة أو فشل متعدد مناطق التوفر السعةَ. يجب أن تتضمن الاستراتيجية تحديد الحمل لكي يتدهور النظام بشكل لطيف بدلًا من الانهيار. نفّذ طبقتين:
تحديد معدل Nginx عند بوابة الدخول:limit_req_zone مع بدل طفرة لكل مستأجر. المستأجرون في الخطط المجانية يُحدَّدون أولًا؛ الخطط المدفوعة تحصل على ميزانية طفرة أكبر.
قاطع دائرة على مستوى التطبيق: عندما يتجاوز عمق قائمة الانتظار الداخلية حدًا معينًا، ترد API بـ 429 Too Many Requests مع رأس Retry-After. هذا أرخص من السماح للطلبات بالانتظار داخل التطبيق وانتهاء مهلتها بخطأ 504.
فخ إنتاجي — القياس التلقائي + مقاطعة spot خلال الطفرة: يمكن استرداد عقد Spot بإشعار دقيقتين في أي وقت، بما في ذلك أثناء الطفرة. خفّف هذا الخطر بـ: (1) استخدام عائلات نماذج متعددة في NodePool من Karpenter حتى يجد AWS دائمًا سعة متاحة؛ (2) ضبط PodDisruptionBudget بـ maxUnavailable: 10% على نشر API حتى لا تُزيل مقاطعة spot أكثر من 10 % من pods في آنٍ واحد؛ (3) تشغيل حد أدنى من عقد on-demand في المجموعة الأساسية يمكنها وحدها تحمل 1x من الحمل — spot مخصص فقط لمعامل الطفرة.
التحقق: اختبار الحمل للاستراتيجية قبل الإنتاج
يجب التحقق من كل استراتيجية قياس تلقائي تحت حمل اصطناعي قبل حدث حقيقي. استخدم k6 لمحاكاة شكل الطفرة: ارتفاع إلى 10x خلال 60 ثانية، احتفاظ بالذروة لمدة 5 دقائق، استنزاف خلال 10 دقائق. راقب وقت استجابة HPA، زمن توفير Karpenter، P99 لزمن الاستجابة، ومعدل الخطأ في Grafana. إذا تجاوز P99 حاجز 400 مللي ثانية خلال مرحلة الارتفاع (قبل انضمام عقد جديدة)، ارفع حد التوسع المسبق أو خفّض استهداف استخدام HPA.
// burst-loadtest.js — سكريبت k6 لمحاكاة طفرة حركة مرور 10x
// التشغيل: k6 run --out prometheus=remote_write_url burst-loadtest.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 400 }, // الخط الأساسي: 400 VU ~ 4000 rps
{ duration: '1m', target: 4000 }, // ارتفاع إلى طفرة 10x
{ duration: '5m', target: 4000 }, // إدامة الطفرة
{ duration: '2m', target: 400 }, // استنزاف العودة
{ duration: '3m', target: 400 }, // تأكيد الاستقرار عند الخط الأساسي
],
thresholds: {
http_req_duration: ['p(99)<500'], // مستوى الخدمة: P99 أقل من 500ms
http_req_failed: ['rate<0.001'], // معدل الخطأ أقل من 0.1%
},
};
export default function () {
const res = http.get('https://api.example.com/v1/healthz', {
headers: { 'X-Tenant-ID': `tenant-${Math.floor(Math.random() * 1000)}` },
});
check(res, { 'status 200': (r) => r.status === 200 });
sleep(0.1);
}
ملخص الاستراتيجية — القرارات الأربعة: (1) اضبط minReplicas في HPA بشكل مرتفع بما يكفي لكي تستوعب السعة الدافئة أول 60 ثانية قبل أن تصبح عقد جديدة جاهزة. (2) استخدم عقد spot كبيرة في Karpenter لتقليل رحلات توفير العقدة. (3) وسّع مسبقًا للأحداث التقويمية المعروفة — القياس التلقائي التفاعلي وحده بطيء جدًا للطفرات المتوقعة. (4) أضف تحديد الحمل وPodDisruptionBudgets كشبكات أمان للمفاجآت. معًا، تتيح هذه الطبقات الأربع للنظام استيعاب طفرة 10x دون انتهاك مستوى الخدمة.
نستخدم ملفات تعريف الارتباط لتشغيل هذا الموقع وتحليل الزيارات وعرض إعلانات مخصّصة. يمكنك قبول كل ملفات تعريف الارتباط أو رفض غير الأساسية منها.
سياسة الخصوصية