مصادقة بين الخدمات دون service mesh
نمط الـ sidecar proxy على Cloud Run: مصادقة الوارد، الصادر عبر بوابة API داخلية مركزية، وموزّع حمل داخلي يوجّه بالمسار — فوائد الـ mesh دون ثقله.
في كل مرة قيّمتُ فيها service mesh لمنصة من نحو دزينة خدمة، خرجت الحسبة بالنتيجة نفسها: المشاكل حقيقية، والـ mesh آلة أكبر مما تستحق المشاكل. ما احتجناه فعلًا على هذه المنصة — خدمات Go مصغّرة على Cloud Run خلف موزّع حمل داخلي — ثلاثة أشياء: ألّا تقبل الخدمات حركة مرور من أي كان؛ أن تمر الاستدعاءات الصادرة عبر مسار واحد مضبوط؛ وألّا يعيش أي من ذلك المنطق في كود التطبيق. حصلنا على الثلاثة من نمط sidecar proxy وطبقة توجيه مملة عمدًا.
القطع
- موزّع حمل داخلي بتوجيه على أساس المسار بابًا أماميًا لاستدعاءات الخدمات فيما بينها. اسم مضيف داخلي ثابت واحد؛
/orders/*يذهب لخدمة الطلبات، و/payments/*لبوابة الدفع، وهكذا. وCloud Armor أمام كل ما يواجه الخارج. - حاوية sidecar في كل خدمة Cloud Run تتولى اتجاهَي الثقة:
- الوارد: صادِق على المستدعي قبل أن يبلغ الطلب حاوية التطبيق — تحقق من رمز الهوية، وتأكد أن المستدعي على قائمة السماح لهذه الخدمة، وارفض كل ما عداه.
- الصادر: تغادر الاستدعاءات عبر الـ sidecar نحو بوابة API داخلية مركزية، وهي المكان الوحيد الذي يعرف طريق الوصول إلى كل خدمة داخلية (وإلى حفنة الـ APIs الخارجية التي اعتمدنا عليها).
- Terraform لكل ذلك — الموزّع، المسارات، سياسات Cloud Armor، إعدادات الخدمات — لأن طبقة توجيه لا تستطيع عمل diff لها هي طبقة توجيه لا تثق بها.
أما حاويات التطبيق فتتكلم HTTP عاديًا إلى localhost. كود العمل في الخدمة لا يحتوي سكّ رموز ولا تحقق أقران ولا إعادة محاولة عند فشل مصادقة — يرسل طلبًا إلى الـ sidecar الخاص به وتتولى المنصة الباقي.
لماذا بوابة مركزية بدل الاستدعاءات المباشرة
السماح لكل خدمة بالاتصال المباشر بكل خدمة أخرى هو الوضع الافتراضي، ويعمل إلى أن تحتاج للإجابة عن «ما الذي يستدعي ماذا؟» في منتصف حادثة. تمرير الصادر عبر بوابة مركزية اشترى لنا:
- جردًا واحدًا للتبعيات. إعدادات البوابة هي مخطط تبعيات الخدمات — قابلة للمراجعة في pull request، وقابلة للـ grep في الثانية صباحًا.
- نقطة خنق واحدة للسياسات العرضية. المهل وميزانيات إعادة المحاولة وقوائم سماح الصادر تعيش في مكان واحد، بدل 12 إعداد
http.Clientتنجرف متباعدةً. - مكانًا واحدًا تُبلَغ منه الـ APIs الخارجية. نقاط الطرف الثالث وبيانات اعتمادها وحدود معدلاتها مشكلةُ البوابة؛ الخدمات تطلب قدرة، لا URL.
الثمن هو الصدق بشأن ماهية البوابة: نقطة توجيه واحدة (تتوسع وتتكرر، لكنها مفهوميًا واحدة) وقفزة كمون إضافية. لحركة تفاعلية منطق أعمالها بالمللي ثانية كانت القفزة ضجيجًا؛ أما أنماط الفشل التي استدعتنا فعلًا، فكانت البوابة حيث يسكن الدليل.
ما كان الـ mesh سيضيفه — ولم يضفه
يستحق الـ mesh تعقيدَه حين تحتاج mTLS في كل مكان، أو تقسيم الحركة، أو سياسات عابرة للعناقيد، أو حين تشغّل مئات الخدمات تملكها فرق كثيرة. كان لدينا دزينة خدمات، وملكية فريق واحد، ومنصة مُدارة تتولى أصلًا إنهاء TLS ورموز الهوية. أعطانا نمط الـ sidecar أجزاء عرض القيمة التي سنستخدمها فعلًا — وارد مُصادَق، صادر مضبوط، سياسة موحّدة — بمكوّنات تملكها المنصة أصلًا.
قاعدة القرار التي خرجت بها: عُدَّ ميزات الـ mesh التي ستفعّلها في الأشهر الستة الأولى. إذا كانت القائمة «مصادقة بين الخدمات ومعرفة ما يخاطب ماذا»، فبوسعك بناء ذلك بـ sidecars وموزّع حمل تفهمه أصلًا — وتشخيصه بـ curl.
دروس من التشغيل
- ضع فحص قائمة السماح في الـ sidecar لا في التطبيق. إذا تسرّب منطق التفويض مرة واحدة إلى كود التطبيق، بدأ الانحراف خدمةً بخدمة، وعدت إلى تدقيق 12 تنفيذًا.
- التوجيه بالمسار يشيخ جيدًا. إضافة خدمة كانت مسار Terraform ومدخل قائمة سماح — لا مراسم DNS ولا إعادة نشر عملاء.
- الـ sidecar عقدُ وقتِ نشر. رقّمه وطرحه كترقية مكتبة: بتروٍّ، خدمةً بخدمة، مع قبول الإصدار القديم أثناء الانتقال.
- اكتب نموذج الثقة. نموذجنا اتسع لنصف صفحة: الحركة الخارجية تُنهى عند الحافة؛ المستدعون الداخليون يُصادَقون برمز هوية عند الوارد؛ الصادر يمر عبر البوابة. ذلك النصف صفحة نفع في التأهيل أكثر من أي مخطط.
لا شيء من هذا مبتكر — إنها فكرة الـ mesh بعُشر الحجم. المهارة ليست في اختيار الأداة الشهيرة، بل في ملاحظة أن الأداة الشهيرة تحل مشاكل لا تملكها، وبناء الميزات الثلاث التي تحتاجها فعلًا بقطع يتسع لها رأسك.