البروكسي العكسي والخوادم الأمامية
البروكسي العكسي والخوادم الأمامية
في الشركات الكبيرة، نادرًا ما يقدّم Nginx استجابات التطبيقات مباشرةً — بل يعمل كبوابة أمامية تُنهي اتصالات العملاء وتُعيد توجيه الطلبات إلى مجموعة من خوادم التطبيقات التي تعمل خلفه. هذا هو البروكسي العكسي: يتحدث العميل إلى Nginx، ويتحدث Nginx إلى تطبيقك، ولا يرى العميل التطبيق مطلقًا بشكل مباشر. كل شيء من البنية التحتية لواجهة Google إلى طبقة الحافة في Netflix يتبع هذا النمط.
إتقان proxy_pass وكتلة upstream هو المهارة الأهم في Nginx لمهندس DevOps. ستستخدمها مع تطبيقات Node.js، وخدمات Python/Django/Flask، وتطبيقات Laravel/PHP-FPM عبر FastCGI، وخلفيات gRPC، وشبكات الخدمات المصغّرة، وخوادم WebSocket.
proxy_pass: التوجيه الأساسي
يُخبر proxy_pass Nginx بوجهة توجيه الطلب المطابق. عند استقبال Nginx لاتصال فإنه:
- يقرأ ترويسات الطلب الكاملة من العميل (أو أول مخزن مؤقت من الجسم).
- يفتح اتصال TCP إلى الهدف الأمامي (أو يُعيد استخدام اتصال keepalive من مجموعته).
- يُعيد توجيه الطلب المُعاد بناؤه مع إضافة ترويسات البروكسي.
- يُعيد بث استجابة الخادم الأمامي إلى العميل مع التخزين المؤقت أو التمرير المباشر حسب إعداداتك.
الشرطة المائلة الأخيرة في proxy_pass مهمة جدًا. بدون شرطة مائلة أخيرة، يُرسَل URI الكامل (بما في ذلك بادئة location) إلى الخادم الأمامي. أما مع الشرطة المائلة (أو أي مسار آخر)، فإن Nginx يحذف البادئة المطابقة ويُلحق الباقي.
/api، فأنت بحاجة إلى الشرطة المائلة. أما إذا كان يتوقع المسار الكامل، فاحذفها. الأخطاء الناتجة عن التطابق الخاطئ تُنتج خطأ 404 يبدو وكأنه خطأ في توجيه Nginx لكنه في الحقيقة رفض التطبيق للـ URI المُعاد كتابته. اختبر دائمًا باستخدام curl -v وراقب ما يستقبله التطبيق فعلًا.
ترويسات البروكسي الأساسية
عندما يُعيد Nginx توجيه طلب، فإنه يُنشئ اتصال TCP جديدًا إلى الخادم الأمامي. بشكل افتراضي يرى الخادم الأمامي عنوان IP الاسترجاعي لـ Nginx كعميل — لا عنوان IP الزائر الحقيقي. يجب عليك تمرير السياق الأصلي بشكل صريح في الترويسات.
X-Forwarded-For قائمة مفصولة بفواصل تنمو كلما مر الطلب عبر بروكسيات (client, proxy1, proxy2). يجب على تطبيقك قراءة عنوان IP الأقصى يسارًا الذي يثق به — لا الأقصى يمينًا الذي يمكن للمهاجم تزويره. أما X-Real-IP فهي ترويسة ذات قيمة واحدة يضبطها Nginx على $remote_addr (عنوان IP الاتصال الذي استقبله Nginx، وهو IP العميل الحقيقي عندما يكون Nginx أول بروكسي). في معظم الإعدادات ذات الطبقة الواحدة، يكون استخدام X-Real-IP في كود التطبيق أبسط.
مجموعات الخوادم الأمامية: التوسع إلى أكثر من خادم
عملية تطبيق واحدة هي نقطة فشل واحدة. تُعرّف كتلة upstream مجموعة مسماة من خوادم الخلفية التي يوزع Nginx حركة المرور عليها. تعيش جميع منطق موازنة التحميل، والتحقق من الصحة، والتعامل مع الأعطال هنا.
معاملات خادم upstream
كل توجيه server داخل upstream يقبل معاملات اختيارية تمنحك تحكمًا دقيقًا في توزيع حركة المرور ومعالجة الأعطال:
weight=N— أرسل N أضعاف الطلبات إلى هذا الخادم مقارنةً بخوادم الوزن 1. يُستخدم للأجهزة غير المتجانسة.max_fails=N— علّم الخادم على أنه غير متاح بعد N فشل متتالٍ خلال نافذةfail_timeout(الافتراضي: فشل واحد، نافذة 10 ث).fail_timeout=Xs— المدة التي يبقى فيها الخادم مُعلَّمًا على أنه غير متاح قبل إعادة محاولة Nginx.backup— استخدم هذا الخادم فقط عند توقف جميع الخوادم الأساسية. نمط الاحتياط الساخن الكلاسيكي.down— علّم هذا الخادم دائمًا على أنه غير متاح (مفيد أثناء عمليات النشر المتدرج دون تعديل الإعداد الحي).
keepalive، يفتح Nginx اتصال TCP جديدًا إلى الخادم الأمامي لكل طلب يتم توجيهه. عند 1000 طلب/ث يعني ذلك 1000 مصافحة TCP في الثانية: ترتفع الكمون وتستنزف واصفات الملفات. اضبط keepalive 32 (أو أعلى بناءً على تزامنك) واقرنه دائمًا بـ proxy_http_version 1.1 وproxy_set_header Connection "" — الأخير يمسح ترويسة Connection: close التي يرسلها HTTP/1.0 بشكل افتراضي، والتي قد تُغلق الاتصال بعد كل استجابة.
توجيه WebSocket
تبدأ WebSockets كاتصال HTTP/1.1 ثم تجري مصافحة الترقية — يرسل العميل Connection: Upgrade وUpgrade: websocket، ويستجيب الخادم بـ 101 Switching Protocols. بعد ذلك يصبح الاتصال قناة TCP مستمرة وثنائية الاتجاه تعيش لدقائق أو ساعات.
بشكل افتراضي Nginx هو بروكسي طلب-استجابة. يُسقط ترويسة Upgrade ويُغلق الاتصال، مما يُعطل WebSockets كليًا. يجب عليك تعيين ترويسات الترقية وتمريرها بشكل صريح:
ip_hash كل عنوان IP للعميل على نفس الخادم الأمامي طوال فترة الاتصال. يحل هذا مشكلة حالة WebSocket في العملية لكنه يُعطل موازنة التحميل الجغرافي ويفشل بشكل سيئ في التعامل مع الأعطال (إذا مات عقدة، تعيد اتصال جميع عملائها المُثبَّتين وقد تصطدم بخادم بارد). الحل الجاهز للإنتاج هو تخارج حالة الجلسة إلى Redis (مهايئ socket.io، Action Cable، إلخ) حتى يتمكن أي خادم أمامي من معالجة أي اتصال — ثم استخدام round-robin بسيط.
دمج المواقع الثابتة والمُوجَّهة
يدمج كتلة خادم إنتاجية كاملة عادةً كلا النمطين: يقدم Nginx الأصول الثابتة بسرعة السلك ويُوجّه كل شيء آخر إلى مجموعة التطبيقات. هذا النمط الذي ستراه في كل دليل نشر لـ Laravel وRails وDjango وNext.js:
- الأصول الثابتة (CSS، JS، الصور): يقدمها Nginx مباشرةً مع ترويسات تخزين مؤقت طويل الأمد.
- مسارات API والصفحات: تُوجَّه إلى مجموعة الخوادم الأمامية.
- نقطة نهاية WebSocket (
/ws/): تُوجَّه مع ترويسات الترقية ومهلة قراءة طويلة. - نقطة نهاية الفحص الصحي: تُوجَّه إلى التطبيق أو يُجيب عليها Nginx مباشرةً.
هذا الفصل يعني أن خوادم تطبيقاتك لا تُهدر أي وحدة معالجة مركزية على ملفات لا تتغير أبدًا — فرق ملموس على نطاق واسع عندما تمثل الأصول الثابتة 80–90% من حجم الطلبات.