أساسيات الشبكات لـ DevOps

موازنة التحميل

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

موازنة التحميل

لا يستطيع خادم واحد تحمّل عدد غير محدود من الاتصالات المتزامنة دون أن ترتفع التأخيرات وتنتهي مهل الطلبات. تحلّ موازنة التحميل هذه المشكلة بتوزيع حركة المرور الواردة على مجموعة من الخوادم الخلفية (تُعرف بـالمصادر العليا أو خوادم الأصل). على نطاق الشركات الكبرى، لا تُعدّ موازنات التحميل مجرد أدوات أداء — فهي الآلية الأساسية لـالنشر دون توقف، وعزل الأعطال، والتوسع الأفقي.

موازنة التحميل مقابل اكتشاف الخدمات: هذان المفهومان متكاملان. يُجيب اكتشاف الخدمات (Consul، نقاط نهاية Kubernetes) على سؤال "أيّ حالات موجودة الآن؟" أما موازنة التحميل فتُجيب على "أيّها يجب أن يستقبل هذا الطلب؟" في Kubernetes، يجمع كائن Service بين الاثنين: يحتفظ kube-proxy بقواعد iptables/IPVS التي توازن التحميل تلقائياً على حزم Pod الصحيحة.

L4 مقابل L7: أين يتخذ القرار في المكدس؟

الخيار التصميمي الأهم هو ما إذا كان موازن التحميل يعمل على مستوى الطبقة الرابعة (النقل) أو الطبقة السابعة (التطبيق).

موازنة تحميل الطبقة 4 تعمل على مستوى TCP/UDP. يُعيد الموازن توجيه تدفقات البايت دون فحص الحمولة. يرى عنوان IP الوجهة والمنفذ، يختار خادماً خلفياً، ويُنشئ اتصالَي TCP — أحدهما من العميل إلى الموازن ("الواجهة الأمامية")، والآخر من الموازن إلى الخادم الخلفي ("اتصال الخلفية") — ويربطهما معاً. وهو سريع للغاية ويعمل مع أي بروتوكول TCP/UDP.

موازنة تحميل الطبقة 7 تُنهي بروتوكول التطبيق (HTTP، gRPC، WebSocket). يُحلّل الموازن طلب HTTP بالكامل قبل توجيهه، مما يتيح اتخاذ قرارات بناءً على مسار URL، ورأس المضيف، والكوكيز، ومعاملات الاستعلام، أو جسم الطلب. تأتي هذه الإمكانية المتقدمة مع تأخير إضافي (~1-5 مللي ثانية لكل قفزة عند التحميل العالي) وتعقيد أكبر.

L4 vs L7 Load Balancer — side-by-side comparison L4 Load Balancer TCP/UDP passthrough Client L4 LB sees: IP + Port Backend :8080 Backend :8080 Backend :8080 + Lower latency + Any TCP/UDP protocol - No URL/host routing - No TLS termination L7 Load Balancer HTTP-aware routing Client L7 LB reads: URL, Host, Cookie, Headers /api/* → API Pods /static/* → CDN /ws/* → WS Servers + URL/host-based routing + TLS termination - Higher per-request overhead - HTTP only (mostly)
موازنات التحميل L4 مقابل L7 — تُمرر L4 تدفقات TCP دون فحص؛ تقرأ L7 طلب HTTP كاملاً وتوجّهه بناءً على URL أو المضيف أو الكوكيز.

في الواقع العملي، تجمع بيئات الإنتاج في الغالب بين النوعين: موازن تحميل L4 (مثل AWS NLB أو GCP TCP Proxy) عند حافة الشبكة للإنتاجية العالية والثبوتية، يُغذّي وكلاء L7 عكسية (nginx، Envoy، HAProxy) تتولى التوجيه بناءً على المحتوى وإنهاء TLS.

خوارزميات موازنة التحميل

تُحدد الخوارزمية أيّ خادم خلفي يستقبل الطلب التالي. الاختيار الخاطئ يُسبب نقاط ساخنة حتى في مجموعة "متوازنة".

  • Round Robin (التناوب الدائري) — تتناوب الطلبات على الخوادم بالتسلسل. بسيط وعادل عندما تكون جميع الخوادم متماثلة وتكلفة الطلبات موحدة. الإعداد الافتراضي في معظم الوكلاء.
  • Weighted Round Robin (التناوب المرجّح) — تحصل الخوادم على نسبة من الحركة تتناسب مع وزن مُعيَّن. مفيد خلال النشر التجريبي (10% للإصدار الجديد، 90% للمستقر) أو عند اختلاف القدرات.
  • Least Connections (أقل الاتصالات) — يذهب الطلب التالي إلى الخادم ذي أقل اتصالات نشطة. أفضل من Round Robin للاتصالات طويلة الأمد (WebSocket، gRPC streaming، اتصالات قواعد البيانات).
  • IP Hash / Sticky Sessions (التثبيت) — يُجزئ IP العميل (أو كوكيز) لربطه دائماً بنفس الخادم. ضروري للتطبيقات التي تخزن بيانات الجلسة في الذاكرة. نمط مضادّ خطير في الخدمات المصغرة عديمة الحالة لأنه يمنع التوزيع المتوازن.
  • Power of Two Choices (قوة الاختيارين) — اختيار خادمَين عشوائيَّين والتوجيه لأقلّهما اتصالات. يُحقق توزيعاً شبه مثالي بتكلفة O(1). مستخدم داخلياً في Envoy وكثير من الأنظمة الكبرى.
  • Least Response Time (أقل وقت استجابة) — يوجّه إلى الخادم ذي أدنى متوسط تأخير. مفيد عند تفاوت أداء الخوادم.
قاعدة إبهام Google SRE: للخدمات المصغرة HTTP عديمة الحالة، يُفضَّل least connections أو round robin مع فحوصات صحة دقيقة. تجنب IP hash ما لم يكن التطبيق يتطلب فعلاً التقارب مع الخادم — فهو يُعطّل التوسع الأفقي بصمت ويُعقّد النشر المتدرّج.

فحوصات الصحة: قلب موازن التحميل العامل

موازن التحميل لا يتجاوز جودة فحوصاته. بدونها، سيُرسل بسعادة الحركةَ إلى خادم يستمع على المنفذ لكنه يُعيد 500 — وهو أسوأ أنواع الأعطال لأنه صامت من منظور العميل.

ثلاثة أنواع من فحوصات الصحة، بترتيب متصاعد التطور:

  1. فحص TCP — يفتح الموازن اتصال TCP إلى الخادم. إذا اتصل، يُعدّ الخادم "نشطاً". يُثبت أن العملية تستمع لكن ليس أنها تستطيع تقديم الخدمة فعلاً.
  2. فحص HTTP — يُرسل الموازن طلب HTTP (عادةً GET /healthz) ويتوقع استجابة 2xx ضمن مهلة. المعيار للخدمات HTTP.
  3. فحص مستوى التطبيق — يُرسل الموازن طلباً إلى نقطة نهاية /readyz يتحقق فيها تطبيقك بنشاط: هل مجموعة اتصالات قاعدة البيانات سليمة؟ هل الكاش متاح؟ هل العمال الخلفيون يعملون؟ هذا ما تُطبّقه مسبارات الجاهزية في Kubernetes.
Liveness مقابل Readiness — لا تخلط بينهما. يُخبر مسبار liveness كوبيرنيتس بإعادة تشغيل الحاوية إذا توقفت العملية. يُخبر مسبار readiness موازن التحميل بإزالة الـPod مؤقتاً من شريحة نقاط النهاية (مثلاً، أثناء بدء التشغيل أو عند إشباع اتصالات DB). إعادة مسبار readiness فاشلاً عند تعطل قاعدة البيانات يعني توقف البود عن استقبال الحركة — وهذا صحيح. إعادة مسبار liveness فاشلاً في نفس الحالة يعني إعادة تشغيل كوبيرنيتس للحاوية — مما قد يُفاقم المشكلة في حالات الفشل المتسلسلة.

تهيئة nginx L7 حقيقية: مجموعة Upstream مع فحوصات صحة

# /etc/nginx/conf.d/api.conf upstream api_pool { # least_conn أفضل من round-robin لواجهات API ذات أوقات استجابة متغيرة least_conn; server 10.0.1.10:8080 weight=1 max_fails=3 fail_timeout=30s; server 10.0.1.11:8080 weight=1 max_fails=3 fail_timeout=30s; server 10.0.1.12:8080 weight=1 max_fails=3 fail_timeout=30s; # keepalive: إعادة استخدام اتصالات upstream (يُقلل عبء مصافحة TCP) keepalive 64; } server { listen 443 ssl http2; server_name api.example.com; ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; location / { proxy_pass http://api_pool; proxy_http_version 1.1; # مطلوب لعمل keepalive proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # مهل الوقت: فشل سريع، دع العميل يُعيد المحاولة proxy_connect_timeout 3s; proxy_send_timeout 30s; proxy_read_timeout 30s; # إعادة المحاولة عند الأخطاء — لكن ليس على POST (قد يُسبب إرسالاً مزدوجاً) proxy_next_upstream error timeout http_500 http_502 http_503; proxy_next_upstream_tries 2; } location /healthz { proxy_pass http://api_pool; access_log off; } }

HAProxy: مثال كامل على L4 + L7

# /etc/haproxy/haproxy.cfg global log /dev/log local0 maxconn 50000 nbthread 4 # يجب أن يطابق عدد أنوية المعالج tune.ssl.default-dh-param 2048 defaults log global mode http option httplog option dontlognull option forwardfor # يُضيف رأس X-Forwarded-For option http-server-close # يُفعّل HTTP keepalive للخوادم الخلفية timeout connect 3s timeout client 30s timeout server 30s retries 3 frontend http_in bind *:443 ssl crt /etc/haproxy/certs/api.example.com.pem alpn h2,http/1.1 default_backend api_servers # توجيه L7: /payments تذهب لمجموعة مخصصة acl is_payments path_beg /payments use_backend payments_servers if is_payments backend api_servers balance leastconn option httpchk GET /healthz HTTP/1.1\r\nHost:\ api.example.com http-check expect status 200 server api1 10.0.1.10:8080 check inter 2s fall 3 rise 2 server api2 10.0.1.11:8080 check inter 2s fall 3 rise 2 server api3 10.0.1.12:8080 check inter 2s fall 3 rise 2 backend payments_servers balance roundrobin option httpchk GET /healthz HTTP/1.1\r\nHost:\ api.example.com http-check expect status 200 server pay1 10.0.2.10:8080 check inter 2s fall 3 rise 2 server pay2 10.0.2.11:8080 check inter 2s fall 3 rise 2

معاملات مهمة: inter 2s — المسح كل ثانيتين؛ fall 3 — وضع علامة "معطل" بعد 3 فشل متتاليين؛ rise 2 — استعادة الخدمة بعد نجاحَين متتاليَّين. عدم التماثل (3 للتعطيل / 2 للاستعادة) مقصود: تريد التأكد من أن الخادم فعلاً سليم قبل إرسال حركة إنتاج إليه.

معمارية الطبقة المتوازنة

Production balanced tier — DNS, L4 NLB, L7 LB, App tier, Data tier Internet DNS CNAME → NLB L4 NLB (AWS NLB) TCP passthrough, static IPs nginx L7 LB - AZ1 TLS termination + routing nginx L7 LB - AZ2 TLS termination + routing App Pod 1 App Pod 2 App Pod 3 App Pod 4 App Pod 5 Primary DB + Read Replicas
موازنة التحميل ثنائية الطبقة: موازن L4 NLB عند الحافة لعناوين IP ثابتة ومرور TCP شفاف، يُغذّي وكلاء nginx L7 في كل منطقة توافر تتولى إنهاء TLS والتوجيه الذكي لمجموعة الـPods.

أنماط الفشل الشائعة في الإنتاج

  • القطيع الرعدي بعد إعادة تشغيل الخادم — عند عودة الخادم للعمل، ترسل إليه جميع الموازنات الحركة المتراكمة في آنٍ واحد. خففه بالتدريج التدريجي: slowstart 30s في HAProxy، أو slow_start_window في Envoy.
  • تذبذب فحوصات الصحة — خادم يتردد بين الصحة والمرض مما يُسبب توجيهاً غير منتظم. اضبط rise (2-3 نجاح على الأقل قبل الاستعادة) وأضف تشويشاً لفترات الفحص لمنع تزامن جميع الموازنات.
  • الجلسات الثابتة تُخفي السعة — مع IP hash، يُثبَّت عميل ثقيل واحد على خادم بعينه. تحت الحركة العالية من CDN (كل الحركة تبدو قادمة من بضعة عناوين IP)، يمكن أن يُفرط هذا في تحميل خادم واحد بينما يخمل الباقون. الحل: استخدام الثبات عبر كوكيز، أو — الأفضل — إزالة حالة الجلسة من الخادم.
  • استنفاد مجموعة الاتصالات — يحتفظ الموازن بـ1000 اتصال keepalive لخادم يقبل 100 فقط. اضبط keepalive في كتلة upstream لقيمة أقل من حد max_connections للخادم.
متطلبات المراقبة لأي موازن تحميل: راقب الاتصالات النشطة لكل خادم، ومعدل الطلبات، ومعدل الأخطاء (4xx/5xx)، وتغيرات حالة فحوصات الصحة. في Google وMeta، القاعدة: إذا لم يستطع موازن التحميل إخبارك بأي خادم مشبع في أقل من 30 ثانية، فالتهيئة ليست جاهزة للإنتاج.

Kubernetes: موازنة التحميل التلقائية

في Kubernetes، كل خدمة ClusterIP هي IP افتراضي مدعوم بقواعد iptables أو IPVS يُديرها kube-proxy. عند إنشاء Deployment بـ5 نسخ متماثلة، يُضيف kube-proxy تلقائياً قواعد توزّع الحركة (سلسلة احتمالات عشوائية في iptables أو least-connections في IPVS) على عناوين IP الـPods الصحيحة. يُزيل متحكم نقاط النهاية Pod من شريحة نقاط النهاية فور فشل مسبار جاهزيته — قبل إنهاء الـPod.

# عرض أيّ Pods في شريحة نقاط النهاية للخدمة kubectl get endpoints my-api-service -n production # تفعيل وضع IPVS في kube-proxy (أفضل للمجموعات الكبيرة — O(1) بدلاً من O(n)) # في ConfigMap الخاص بـ kube-proxy: # mode: "ipvs" # ipvs: # scheduler: "lc" # least connections # فحص قواعد IPVS الحالية على عقدة ssh node1 sudo ipvsadm -Ln # عرض أحداث تحجيم HPA (موازن أفقي تلقائي) kubectl describe hpa my-api-hpa -n production

النقاط الرئيسية

  • موازنات L4 تُمرّر تدفقات TCP دون تحليل الحمولة — سريعة، تعمل مع أي بروتوكول، لكن بلا توجيه URL.
  • موازنات L7 تُحلّل HTTP — تتيح التوجيه بالمسار/المضيف، وإنهاء TLS، والتلاعب بالرؤوس، وإعادة المحاولة الذكية.
  • اختيار الخوارزمية مهم: least connections للاتصالات الطويلة أو متغيرة التكلفة؛ round robin فقط عند تماثل الطلبات والخوادم.
  • فحوصات الصحة يجب أن تتحقق من جاهزية التطبيق الفعلية، لا مجرد توافر المنفذ. فرّق بين liveness وreadiness.
  • صمّم للفشل: التدريج التدريجي، وعدم تماثل rise/fall، وحدود مجموعة الاتصالات تمنع الأعطال المتسلسلة.
  • في Kubernetes، يتولى كائن Service + kube-proxy موازنة L4 تلقائياً؛ أضف Ingress controller (nginx، Envoy/Contour) لـL7.