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

لماذا نحتاج إلى شبكة الخدمات؟

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

لماذا نحتاج إلى شبكة الخدمات؟

حين كانت Netflix تُشغّل بضع مئات من الخدمات في ذروة حقبة الأحادية، كان التواصل بين الخدمات بسيطًا: عميل HTTP أو اثنان، مكتبة مشتركة، وربما موازن أحمال. أما اليوم فتُشغّل Netflix أكثر من 700 خدمة مصغّرة. وتُشغّل Uber أكثر من 4,000، وتمتلك Airbnb أكثر من 2,000. في هذه البيئات، أصبحت الشبكة الرابطة بين الخدمات بالغة التعقيد — وبالغة الأهمية للبعثة — بمقدار الخدمات ذاتها. وهذا التعقيد تحديدًا هو ما صُمِّمت شبكة الخدمات للتعامل معه.

شبكة الخدمات (Service Mesh) هي طبقة بنية تحتية مخصصة تتولى جميع عمليات التواصل من خدمة إلى خدمة (الحركة الشرق-غربية) داخل الكتلة. وهي تجعل هذا التواصل قابلًا للرصد، وآمنًا، وقادرًا على التعافي — دون أن يضطر كود التطبيق إلى تنفيذ أي من ذلك. يستعرض هذا الدرس المشكلات الدقيقة التي تدفع المنظمات الناضجة إلى تبني شبكة الخدمات، ولماذا محاولة حل هذه المشكلات في كود التطبيق لا تصلح للتوسع.

مشكلة الاهتمامات المشتركة عبر القطاعات

تشترك الأنظمة الموزعة في مجموعة من الاهتمامات التي تحتاجها كل خدمة لكنها لا علاقة لها بمنطق الأعمال. في الأحادية، تكون هذه استدعاءات مكتبة. في معمارية الخدمات المصغّرة، تكون سلوكيات شبكة يجب تنفيذها باتساق عبر عشرات اللغات والبيئات والفرق:

  • TLS المتبادل (mTLS) — هوية مشفرة وتشفير لكل اتصال. يجب على كل خدمة التحقق من أنها تتحدث مع الخدمة التي تعتقد أنها تتحدث معها، لا مع وكيل مخترَق أو مهاجم تمكّن من اختراق شبكة الكتلة.
  • إعادة المحاولة مع تراجع وتشويش — تحدث الأعطال العابرة باستمرار على نطاق واسع. دون منطق إعادة محاولة متسق، يتسبب تباطؤ اعتمادية واحدة في استنزاف متتالٍ لتجمع الخيوط عبر سلسلة الاستدعاءات.
  • قاطع الدائرة — حين تكون اعتمادية معطوبة فعلًا، يجب على المستدعين التوقف عن قرعها وإرجاع أخطاء سريعة. قاطع دائرة يفتح عند عتبة خاطئة — أو لا يفتح أبدًا — يحوّل انقطاعًا محليًا إلى حادثة تطال المنصة بأكملها.
  • المهل الزمنية — يجب أن يكون لكل نداء إجراء بعيد (RPC) موعد نهائي. دونه، الطلب الذي يتعلق 90 ثانية يُقيّد الخيوط والمهام وفتحات تجمع الاتصالات ويُوجِّه التعليق نحو الأعلى.
  • موازنة الأحمال — الجولة البسيطة على مستوى L4 تتجاهل حقيقة أن بعض الحالات أبطأ. الموازنة الواعية بـ L7 كأقل-الطلبات أو EWMA تُخفّض التأخير الطرفي تخفيضًا ملموسًا على نطاق واسع.
  • نشر سياق التتبع الموزع — يجب تمرير رأس سياق التتبع (traceparent أو x-b3-traceid) في كل قفزة. خدمة واحدة تنسى تمريره تكسر التتبع الكامل لمسار ذلك الطلب.
  • تشكيل الحركة — يجب التحكم في الإصدارات التدريجية (canary) وتقسيم الحركة A/B وانعكاسها لكل مسار دون إعادة نشر أي خدمة.

بدون شبكة خدمات: فخ المكتبة المشتركة

الحل الغريزي هو مكتبة مشتركة — عميل HTTP ضخم يُغلّف كل ما سبق. هذا تحديدًا ما بنته Twitter (Finagle)، وما بنته Netflix (Hystrix + Ribbon + Eureka)، وما بنته Uber في مراحلها الأولى. بحلول عام 2018، تعلّمت كل شركة كبيرة تعمل بخدمات مصغّرة نفس الدروس الصعبة عن هذا النهج:

  • تشتت اللغات — مكتبة إعادة المحاولة وقاطع الدائرة لديك حزمة Go. الخدمات الثلاث المكتوبة بـ Python، والاثنتان بـ Java، وواحدة بـ Rust، كل منها تحتاج تطبيقًا منفصلًا. الحفاظ على اتساق سلوكها عبر الإصدارات يُكلّف وقت دوام كامل.
  • انحراف الإصدارات — المكتبة اعتمادية متعدية. فرق الخدمات تُحدّث بجدولها الخاص. ينتهي بك الأمر بسياسات إعادة محاولة مختلفة تعمل في آنٍ واحد عبر الأسطول، مما يجعل الاستدلال على سلوك النظام أثناء الحوادث أمرًا مستحيلًا.
  • اقتران النشر — تغيير عتبة قاطع الدائرة يستلزم إصدارًا جديدًا من كل فريق خدمة. تغيير سياسة يجب أن يستغرق دقائق يستغرق أسابيع.
  • ثغرات الرؤية — المكتبة تُصدر مقاييس، لكنها تذهب إلى حيثما تذهب مقاييس كل خدمة. لا توجد رؤية موحدة ومتسقة لكل اتصال من حيث التأخير ومعدل الخطأ والإنتاجية عبر رسم الحركة الشرق-غربية بأكمله.
المفهوم الأساسي: يخلط نهج المكتبة المشتركة بين السياسة (كيف يجب أن تسلك الاتصالات؟) والتطبيق (كيف تُفرَّض هذه السياسة؟). تفصل شبكة الخدمات بينهما: تُعلَن السياسة مركزيًا في المستوى التحكمي، ويعمل التطبيق في الوكيل الجانبي (sidecar). كود التطبيق خارج الصورة تمامًا.

المعمارية: مع وبدون شبكة خدمات

يوضح الرسم البياني أدناه نفس سلسلة الاستدعاء ذات ثلاث خدمات في كلتا المعماريتين. بدون شبكة، كل خدمة مسؤولة عن اهتماماتها المشتركة. مع الشبكة، تعيش تلك الاهتمامات في وكيل جانبي رقيق يعترض كل حركة واردة وصادرة بشفافية.

Without a Service Mesh vs With a Service Mesh Without a Mesh Service A retry logic circuit breaker mTLS client cert Service B retry logic circuit breaker mTLS client cert Service C retry logic circuit breaker mTLS client cert each service owns policy With a Mesh (Sidecar) Pod A Service A app code only Proxy Envoy / linkerd2 Pod B Service B app code only Proxy Envoy / linkerd2 Pod C Service C app code only Proxy Envoy / linkerd2 mTLS retries tracing
بدون شبكة، كل خدمة تُضمّن منطق الاهتمامات المشتركة. مع الشبكة، التطبيق رفيع والوكيل الجانبي يتولى جميع سياسات الشبكة باتساق.

ما الذي يعترضه الوكيل الجانبي؟

الآلية الأساسية التي تجعل الوكيل الجانبي شفافًا هي حقن قواعد iptables. حاوية تهيئة (أو في الوضع البيئي، عامل على مستوى العقدة) تُثبّت قواعد REDIRECT في iptables لاعتراض جميع حركة TCP الواردة والصادرة من/إلى الحاوية وتوجيهها عبر الوكيل على الـ loopback — عادةً المنافذ 15001 (صادر) و15006 (وارد) في Istio. يفتح التطبيق اتصالًا بـ service-b:8080 كالمعتاد؛ النواة تُعيد توجيهه بصمت إلى وكيل Envoy المحلي الذي يُطبّق السياسة ويُنشئ اتصال mTLS الحقيقي مع وكيل خدمة B، ثم يُمرّر الطلب. التطبيق لا يدري شيئًا مما جرى.

ممارسة إنتاجية: يعني نموذج اعتراض iptables أنك تستطيع التحقق من أن الشبكة تعترض الحركة فعلًا بالتحقق مما إذا كانت الاتصالات بين الحاويات تستخدم TLS رغم عدم تهيئتها في كود التطبيق. نفّذ kubectl exec -it <pod> -- openssl s_client -connect <other-pod-ip>:8080 قبل حقن الشبكة وبعده. بعد الحقن يجب أن ترى شهادة Envoy لا اتصالًا نصيًا.

التكلفة الحقيقية لغياب الشبكة: أنماط فشل الإنتاج

فيما يلي فئات حقيقية من حوادث الإنتاج التي تمنعها شبكة الخدمات أو تجعلها مرئية — لا مخاوف نظرية:

  • عاصفة إعادة المحاولة — الخدمة A لها خطأ في مكتبة إعادة المحاولة: عند استقبال 503، تُعيد المحاولة فورًا دون تشويش، 5 مرات. حين تتدهور الخدمة B، كل حالة من A تُطلق 5 أضعاف حجم الطلبات الطبيعي في آنٍ واحد، محوّلةً تدهورًا طفيفًا في B إلى حِمل كامل يُوقفها. مع الشبكة، تُهيَّأ عمليات إعادة المحاولة مرةً واحدة في VirtualService بتشويش إلزامي وتُطبَّق بالتساوي.
  • تخفيض mTLS غير المكتشف — خدمة مُنشَرة حديثًا تنسى تهيئة TLS فتتواصل في نص واضح. دون أن تُفرّض الشبكة PeerAuthentication في وضع STRICT، يمر هذا دون أن يُلاحَظ. مع الشبكة، يُرفض الاتصال النصي عند الوكيل.
  • التسرب البطيء الصامت — حاوية واحدة في نشر تستجيب للطلبات في ثانيتين بدلًا من 200 مللي ثانية. الموازنة الدائرية البسيطة على مستوى L4 لا تزال تُوجّه ~17% من الحركة إليها (1 من 6 حاويات). وكيل الشبكة باستخدام موازنة أقل-الطلبات يكتشف عمق طابور الطلبات المعلقة ويتوقف عن التوجيه للحاوية البطيئة تلقائيًا.
  • التتبع المكسور — خدمة Node.js تنسى نشر x-b3-traceid. جميع التتبعات السفلية من هذا المسار يتيمة. أسابيع من التحقيق تكشف أن 40% من مسارات الطلبات لديها تتبعات مكسورة. الشبكة يمكنها نشر رؤوس التتبع تلقائيًا على مستوى الوكيل.
فخ إنتاجي: شبكة الخدمات ليست مجانية. الوكيل Envoy الجانبي يُضيف تقريبًا 2-7 مللي ثانية من التأخير لكل قفزة (p99) ويستهلك 50-100 ميجابايت من الذاكرة لكل حاوية في وضع الخمول. في كتلة بها 1,000 حاوية، هذا 50-100 جيجابايت من الذاكرة مخصصة لعمليات الوكيل. على مقياس Google وMeta، هذه التكلفة دفعت إلى الاستثمار في الشبكة البيئية (Ambient Mesh) — eBPF/عامل L4 على مستوى العقدة + وكلاء waypoint مشتركة على مستوى L7 — مما يُزيل الوكيل الجانبي لكل حاوية بالكامل. قِس الأداء دائمًا قبل تفعيل شبكة على أعباء عمل حساسة للتأخير في مستوى البيانات.

متى تُجدي شبكة الخدمات نفعًا؟

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

  • نعم، استخدم الشبكة: أكثر من 10 خدمات، لغات أو فرق متعددة، متطلبات امتثال PCI/SOC2 تتطلب إثبات التشفير أثناء النقل، الحاجة لتقسيم الحركة دون إعادة نشر، أو حادثة سابقة سببها تفاوت في سلوك إعادة المحاولة أو المهل.
  • على الأرجح ليس بعد: أقل من 5 خدمات، لغة أو فريق واحد، NetworkPolicy الأصلي في k8s يُلبّي متطلبات الأمان، والتكلفة التشغيلية لمستوى التحكم غير مبررة.

يستعرض بقية هذا البرنامج التعليمي الشبكتين الإنتاجيتين السائدتين — Istio (الشبكة الكاملة المدعومة من Google) وLinkerd (الشبكة خفيفة الوزن المُعتمدة من CNCF والمبنية على وكيل Rust) — ويتناول تهيئة الإنتاج الفعلية لإدارة الحركة والأمان والمرونة والرؤية على مقياس الشركات الكبرى.