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

ابدأ بسبب اختلاف المسارين، ثم قرّر أين ينبغي أن يعيش النفق.

لماذا يعمل المضيف ولا تعمل الحاوية

إنشاء النفق يمنح المضيف واجهة ومجموعة مسارات، وعادةً سجل مُحلِّل يوفّره الـ VPN. لا ترث الحاوية شيئًا من ذلك: فهي تبدأ بواجهة واحدة على جسر المحرّك ومسار افتراضي يشير إلى ذلك الجسر. المسارات المثبّتة على المضيف غير مرئية لأي شيء يجري داخل تلك المساحة الاسمية.

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

الوصول إلى المضيف من داخل الحاوية

أي عملية داخل الحاوية تتصل بـ localhost إنما تتصل بالحاوية. ينشر Docker Desktop الاسم host.docker.internal ليشير عودةً إلى الجهاز الذي يشغّل المحرّك؛ أما محرّكات Linux فتتركه غير معرّف ما لم تطلبه الخدمة، وهذا ما يفعله التعيين أدناه. وبذلك تستهدف التطبيقة اسمًا ومنفذًا بدلًا من العنوان الخاص.

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      INTERNAL_API_HOST: "host.docker.internal:8666"

يبقى النفق على جانب المضيف من القفزة، والمنفذ في ذلك المتغيّر هو المنفذ الذي سيستمع عليه الوكيل. والحاوية التي تفقد النفق تفشل الآن عند قفزة واحدة معروفة بدلًا من محاولة مسار لم تملكه يومًا.

اختر أين يعيش النفق

ثلاثة أشكال ممكنة لخدمة Compose تحتاج واجهة API خاصة، وهي تختلف في أيّ عملية تملك النفق. يمكن لعميل VPN أن يعمل داخل صورة الحاوية، وهذا يتطلب كشف جهاز نفق ومنح قدرة إدارة الشبكة المرافقة له. ويمكن للتطبيقة أن تشارك المساحة الاسمية الشبكية لحاوية نفق منفصلة، فيرى العمليتان مكدّسًا واحدًا وجدول توجيه واحدًا. أو يمكن للنفق أن يبقى على المضيف، مع مُمرِّر ضيق من المضيف إلى الحاوية.

الشكلان الأولان يسحبان ملفات التعريف وسلوك إعادة الاتصال وسياسة المُحلِّل إلى المشروع، ويجعلان كل خدمة في الملف تعتمد على صحة النفق. هذه درجة كبيرة من الارتباط لنداء API واحد. ولمكدّس تطوير يقرأ نقطة نهاية داخلية واحدة، فإن المُمرِّر على جانب المضيف هو أصغر تغيير يحفظ بيانات الاعتماد حيث يديرها فريق المنصّة.

انشر مسارًا ضيقًا واحدًا على المضيف

ابدأ بما يرثه المُمرِّر: شغّله بالحساب الذي يملك النفق، فتلتقط مقابس خروجه مسارات المضيف ومُحلِّل المضيف. كتلة location واحدة في nginx تغطي مسارًا واحدًا.

server {
    listen 8666;
    location /search/ {
        proxy_pass http://10.20.30.40:8090/;
        proxy_set_header Host internal-api.example;
        proxy_read_timeout 60s;
    }
}

تعامل مع العنوان والمنفذ كعناصر بديلة واستبدل بهما الزوج الذي تستمع عليه واجهة API فعليًا. كل مصدر أعلى إضافي يستحق كتلة location خاصة به، وينبغي لكل كتلة ألا تسمح إلا بالمسارات التي تناديها هذه الخدمة، حتى لا يصبح الوكيل بابًا عامًا إلى الشبكة الخاصة. اربط المستمع بعنوان الجسر للعملاء الذين يصلون عبر host-gateway، وبالـ loopback فقط عندما يشارك العميل المساحة الاسمية الشبكية للمضيف، ولا تربطه أبدًا بـ 0.0.0.0. واختر منفذًا لا يزال حرًا أيضًا: الرقم المشغول مسبقًا يترك الوكيل غير مستمع بينما تستمر الحاوية في المهلة، وهو أمر لا يمكن تمييزه عن عطل الشبكة الذي بدأت به.

المنافذ والأسماء وقرار TLS

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

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

تحقّق من داخل الخدمة

أرسل الطلب من الحاوية، لا من المضيف: أمر curl عادي على مسار الوكيل، ثم فحص الصحة الخاص بالتطبيقة. عندما يجيب الوكيل ويفشل التطبيق رغم ذلك، قارن ثلاثة أشياء: عنوان URL الأساسي الذي يبنيه، وأي متغيّر وكيل في بيئته، وسلطة الشهادات التي يثق بها. كلها في بيئة الحاوية وسجلاتها، وإصلاح أي منها يترك الـ VPN وشأنه.

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

مقالات ذات صلة