شبكة الخدمات: Istio وLinkerd

سياسات الصمود في الشبكة الخدماتية

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

سياسات الصمود في الشبكة الخدماتية

كان أحد الوعود الأصيلة للشبكة الخدماتية هو نقل منطق الصمود من مكتبات التطبيقات إلى طبقة البنية التحتية. قبل Istio، كانت كل فريق يستنسخ أكواد إعادة المحاولة وقواطع الدائرة من أطر عمل كـ Hystrix وResilience4j وPolly — بإعدادات متباينة طفيفاً ومهل زمنية غير متسقة وبلا رصد موحد. تجعل الشبكة هذه السياسات تصريحية وموحدة وقابلة للرصد عبر كل خدمة في الكتلة دون تغيير سطر واحد من كود التطبيق.

لكن هذا التوحيد ذاته يصبح خطراً إذا أسأت ضبطه. إعادة المحاولات المُضبَطة بشكل خاطئ تُحدث عواصف طلبات تُحوّل انقطاعاً جزئياً إلى كامل. وقواطع الدائرة المُضبَطة بعدوانية مفرطة تتخلص من حركة مرور كان يمكنها أن تنجح. تتناول هذه الدرس الآليات الأربع للصمود التي يوفرها Istio — إعادة المحاولات، والمهل الزمنية، وقطع الدائرة، وكشف الحالات الشاذة — وكيف يضبطها المهندسون الكبار لأحمال الإنتاج على نطاق واسع.

إعادة المحاولات

تنتمي إعادة المحاولات في الشبكة إلى المسارات الاستهلاكية للقراءة ذات الطابع التكراري — طلبات GET، والبحث في ذاكرة التخزين المؤقت، وقواعد البيانات المتكررة. ينبغي نادراً ضبطها على عمليات الكتابة دون ضمانات تكافؤ على مستوى التطبيق (معرفات طلبات فريدة، ورموز إزالة التكرار). في Google وAmazon، الإرشاد الداخلي هو التحفظ في عدد المحاولات وتقييدها دائماً بميزانية محددة.

تُضبَط إعادة محاولات Istio في VirtualService. الحقول الرئيسية هي: attempts (الحد الأقصى لعدد المحاولات)، وperTryTimeout (المهلة لكل محاولة منفردة)، وretryOn (الشروط التي تُطلق إعادة المحاولة). حقل retryOn بالغ الأهمية — القيمة الافتراضية connect-failure,refused-stream,unavailable,cancelled,retriable-4xx معقولة لكن عليك فهم معنى كل حالة في نمط حركة مرورك.

# VirtualService: سياسة إعادة المحاولة لخدمة الكتالوج كثيرة القراءة apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: catalog-vs namespace: production spec: hosts: - catalog.production.svc.cluster.local http: - route: - destination: host: catalog.production.svc.cluster.local port: number: 8080 timeout: 5s # المظروف الزمني الكلي؛ يجب أن يكون > attempts * perTryTimeout retries: attempts: 3 perTryTimeout: 1.5s # 3 * 1.5 = 4.5s < 5s مظروف retryOn: connect-failure,refused-stream,unavailable,503
تضخيم إعادة المحاولات: مع 100 عميل يُرسل كل منهم 10 طلبات في الثانية، يُتسبب ارتفاع مؤقت في أخطاء 503 في إعادة المحاولة لدى هؤلاء العملاء. مع attempts: 3 تتضاعف الطلبات ثلاثة أضعاف على الخادم الخلفي مؤقتاً — من 1000 إلى 3000 — تحديداً حين يكون في ضائقة أصلاً. تصف أدبيات Google SRE هذا بـ عاصفة إعادة المحاولات. تخفيفها يتطلب: (1) إبقاء attempts بين 2 و3 كحد أقصى، (2) إقران إعادة المحاولات بالتراجع الأسي مع التشويش على مستوى العميل (ليس مدمجاً في Istio)، (3) وضع ميزانية للمحاولات عند طبقة موازن الحمل بحيث لا تتجاوز 10% من الطلبات الكلية.

المهل الزمنية

المهل الزمنية هي أكثر المقابض تأثيراً في النظام الموزع. غياب المهلة يعني أن خادماً أعلى متوقفاً يمكنه الإمساك بخيط تنفيذ وفتحة في تجمع الاتصالات وروتين غوروتين إلى أجل غير مسمى — استنزاف موارد متتالٍ يُحوّل بوداً بطيئاً واحداً إلى حادثة على مستوى الكتلة. تفرض الشبكة المهل الزمنية حتى حين ينساها التطبيق.

حقل timeout في VirtualService هو المهلة الكلية للطلب شاملة جميع محاولات إعادة المحاولة. الخطأ الشائع هو ضبط timeout: 3s مع attempts: 3 وperTryTimeout: 2s — الحساب لا يستقيم وIstio يبتر صامتاً عند المظروف الزمني. تحقق دائماً: total_timeout >= attempts * perTryTimeout + overhead.

على نطاق الإنتاج، تحتاج فئات حركة المرور المختلفة إلى ملفات مهل مختلفة. يسمح Istio بالمطابقة على الترويسات أو بادئات URI أو تسميات المصدر لتطبيق سياسات لكل مسار — مثلاً 500ms للاستدعاءات المتزامنة API و30s لنقاط نهاية التصدير الدُفعي على الخدمة ذاتها.

# مستويات مهل على VirtualService واحد باستخدام شروط المطابقة apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: export-service-vs spec: hosts: - export-service http: - match: - uri: prefix: /v1/export/batch # مسار التصدير الطويل timeout: 30s route: - destination: host: export-service port: number: 8080 - route: # افتراضي: جميع المسارات الأخرى - destination: host: export-service port: number: 8080 timeout: 800ms

قطع الدائرة

قطع الدائرة هو تحكم على طبقة الاتصال. على عكس إعادة المحاولات والمهل التي تعمل على الطلبات الفردية، يحد قاطع الدائرة من التزامن وعمق قائمة الانتظار لوجهة ما. في Istio يُضبَط هذا عبر DestinationRule بكتلة trafficPolicy.connectionPool.

المعاملات الرئيسية هي: http1MaxPendingRequests / http2MaxRequests (حدود قائمة الانتظار/التزامن)، وmaxRequestsPerConnection (يُجبر على إعادة الاتصال لتفادي الاتصالات القديمة طويلة الأمد)، وmaxRetries (يقيّد تزامن إعادة المحاولة على مستوى تجمع الاتصالات، مستقلاً عن عدد محاولات VirtualService).

# DestinationRule: قاطع دائرة لخدمة المدفوعات apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: payments-dr namespace: production spec: host: payments.production.svc.cluster.local trafficPolicy: connectionPool: tcp: maxConnections: 100 # حد اتصالات L4 لكل عامل Envoy connectTimeout: 250ms tcpKeepalive: time: 300s interval: 75s http: http2MaxRequests: 500 # تدفقات HTTP/2 المتزامنة http1MaxPendingRequests: 50 # عمق قائمة الانتظار قبل إعادة 503 maxRequestsPerConnection: 25 # إعادة تدوير الاتصالات لتفادي إعاقة الرأس maxRetries: 10 # حد محاولات تجمع الاتصالات outlierDetection: # مرافق لقطع الدائرة (انظر أدناه) consecutive5xxErrors: 5 interval: 10s baseEjectionTime: 30s maxEjectionPercent: 50
ما يعنيه "الدائرة المفتوحة" في Envoy: على عكس آلة الحالة CLOSED/OPEN/HALF-OPEN في Hystrix، لا يوجد في Envoy/Istio دائرة منفصلة بهذه الحالات. بدلاً من ذلك، عند تجاوز http1MaxPendingRequests، يُعيد Envoy فوراً 503 UO (تجاوز المنبع) للطلبات الجديدة دون إعادة توجيهها. يظهر هذا في المقاييس كـ upstream_rq_pending_overflow. "تُغلق الدائرة" تلقائياً بمجرد استنزاف الطلبات المعلقة — لا توجد انتقالات حالة صريحة للمراقبة.

كشف الحالات الشاذة

كشف الحالات الشاذة هو مكافئ الشبكة لـ الفحص الصحي السلبي. يراقب ردود حركة المرور الفعلية من كل بود فردي في المنبع ويُقصي المضيفين الذين يُظهرون سلوكاً شاذاً من تجمع موازنة الحمل لفترة تهدئة. هذا مختلف جوهرياً عن قطع الدائرة: قطع الدائرة يحد من حركة المرور إلى الخدمة بأكملها، بينما يُقصي كشف الحالات الشاذة البودات السيئة المنفردة من المجموعة الفرعية.

هذا الأمر بالغ الأهمية في الأساطيل الكبيرة. نشر Deployment بـ 50 نسخة حيث يعاني بودان من تسرب ذاكرة يجعلهما يُعيدان 5xx عند كل طلب ثالث سيُدهور جميع العملاء بنسبة 4% من الوقت — لا يكفي لإطلاق تنبيه، لكنه كافٍ لانتهاك SLO. يُقصي كشف الحالات الشاذة هذين البودين بصمت بينما الـ 48 الأخرى تخدم حركة مرور نظيفة. تؤدي الشبكة العمل الذي كان سيستلزم من إنسان تتبع معدلات الخطأ لكل بود في Prometheus ثم تنفيذ kubectl cordon يدوياً للعقدة.

Outlier Detection: Ejecting Unhealthy Pods from Load Balancing Pool Client Envoy Sidecar Outlier Detector (Envoy LB policy) tracks 5xx/latency per upstream host Upstream Pod Pool Pod A healthy Pod B healthy Pod C healthy Pod D EJECTED (30s) no traffic 5 consec. 5xx detected re-probe after 30s
يُقصي كشف الحالات الشاذة Pod D بعد 5 أخطاء 5xx متتالية؛ تستوعب البودات السليمة كل حركة المرور؛ يُعاد اختبار Pod D بعد انتهاء نافذة الإقصاء الأساسية.

تفاعل المعاملات الحرج الذي يجب استيعابه هو maxEjectionPercent. هذا هو الحد الأمان: إذا كانت حدود الكشف مشددة للغاية وبدأت في إقصاء بودات سليمة خلال ارتفاع الحمل (الذي يولد أيضاً أخطاء عابرة)، لن يُقصي Istio ما يتجاوز هذه النسبة المئوية من التجمع. ضبطها عالياً جداً (100%) يُخاطر بإقصاء الأسطول بأكمله. القيمة الآمنة للإنتاج هي 50% لمعظم الخدمات؛ وتُخفَّض إلى 25-30% للخدمات ذات الحالة أو الخدمات الفردية.

# كشف حالات شاذة مُدقَّق لخدمة ذات حالة (وكيل قاعدة بيانات، طبقة تخزين مؤقت) # أكثر تحفظاً: فترات أطول، حد متتالٍ أعلى outlierDetection: consecutive5xxErrors: 10 # يحتاج إخفاقات مستمرة، ليس مجرد ارتعاشة consecutiveGatewayErrors: 5 # أخطاء بوابة L4/L7 (502, 503, 504) interval: 30s # نافذة التقييم baseEjectionTime: 60s # الحد الأدنى لمدة الإقصاء maxEjectionPercent: 25 # لا تُقصي أكثر من 1 من كل 4 مضيفين minHealthPercent: 50 # توقف عن الإقصاء إذا انخفض التجمع دون 50% سليمة splitExternalLocalOriginErrors: true # تمييز أخطاء الأصل المحلي عن المنبع
الجمع بين قطع الدائرة وكشف الحالات الشاذة: هاتان الآليتان متكاملتان لا متكررتان. اضبطهما معاً على كل DestinationRule للخدمات الحرجة. قطع الدائرة (connectionPool) يحمي من الحمل الزائد المتزامن للخدمة ككل. كشف الحالات الشاذة يحمي من النسخ السيئة الفردية التي تُفسد معدل نجاحك. ظهور كلاهما في كتلة DestinationRule ذاتها، كما هو مُبيَّن في مثال المدفوعات أعلاه، هو النمط القياسي في الإنتاج لدى شركات التقنية الكبرى التي تشغل Istio على نطاق واسع.

مراقبة سلوك سياسات الصمود

السياسات المُضبَطة لكنها غير مُتحقق منها تشكل عبئاً. بعد كل تغيير في إعداد إعادة المحاولة/المهلة/قاطع الدائرة، تحقق من السلوك عبر واجهة API الإدارية لـ Envoy ومقاييس Prometheus. أكثر الإشارات إفادة:

  • envoy_cluster_upstream_rq_pending_overflow — الطلبات التي أسقطها قاطع الدائرة (تجاوز تجمع الاتصالات).
  • envoy_cluster_outlier_detection_ejections_active — عدد المضيفين المُقصَيين حالياً؛ نبّه إذا كان غير صفري لأكثر من دقيقتين.
  • envoy_cluster_upstream_rq_retry وenvoy_cluster_upstream_rq_retry_success — حجم إعادة المحاولة ومعدل النجاح. معدل نجاح أقل من 60% يعني أنك تُعيد المحاولة على إخفاقات غير عابرة.
  • istio_requests_total{response_flags="UO"} — تجاوز المنبع؛ إشارة مباشرة أن قطع الدائرة يُسقط حركة مرور بنشاط.
# فحص إحصاءات قاطع دائرة Envoy مباشرة على الـ sidecar kubectl exec -n production deploy/payments -c istio-proxy -- \ curl -s http://localhost:15000/clusters | grep payments | grep -E "cx_active|rq_active|rq_pending" # فحص الإقصاءات الحالية لخدمة ما kubectl exec -n production deploy/frontend -c istio-proxy -- \ curl -s http://localhost:15000/clusters | grep payments | grep outlier # استعلام Prometheus: معدل إعادة المحاولة خلال آخر 5 دقائق (كنسبة من الطلبات الكلية) # rate(envoy_cluster_upstream_rq_retry[5m]) / rate(envoy_cluster_upstream_rq_total[5m])

في الشركات التي تشغل Istio على آلاف الخدمات، تُدار سياسات الصمود في مستودع GitOps مركزي وتُصيَّر كقوالب VirtualService / DestinationRule لكل طبقة خدمة — لا تُصنع يدوياً لكل فريق. توجد ملفات إعداد افتراضية (صارم/مرن/معطل) لطبقات الخدمة، وتختار الفرق ملف إعداد أشد للمسارات الحرجة كالمدفوعات. هذا يمنع الانجراف في الإعدادات الذي يُصعّب تشخيص حوادث الإنتاج حين يكون لكل خدمة دلالات إعادة محاولة مختلفة طفيفاً.