معرفة NGINX المستخدم في OpenResty

API7.ai

September 17, 2022

OpenResty (NGINX + Lua)

من خلال المقال السابق، لديك معرفة عامة عن OpenResty. في المقالات القليلة القادمة، سأأخذك عبر الركيزتين الأساسيتين لـ OpenResty: NGINX وLuaJIT، ويمكنك تعلم OpenResty بشكل أفضل من خلال إتقان هذه الأساسيات.

اليوم سأبدأ مع NGINX، وهنا سأقدم فقط بعض الأساسيات التي قد تُستخدم في OpenResty، وهي مجرد جزء صغير من NGINX.

بالنسبة للتكوين، في تطوير OpenResty، نحتاج إلى الانتباه إلى النقاط التالية.

  • تقليل تكوين nginx.conf قدر الإمكان.
  • تجنب استخدام مجموعة من الأوامر مثل if، set، rewrite، إلخ.
  • عدم استخدام تكوين NGINX، المتغيرات، والوحدات عندما يمكن حلها باستخدام كود Lua.

هذه الطرق ستزيد من قابلية القراءة، الصيانة، والتوسع. التكوين التالي لـ NGINX هو مثال سيء لاستخدام التكوين ككود.

location ~ ^/mobile/(web/app.htm) {
    set $type $1;
    set $orig_args $args;
    if ( $http_user_Agent ~ "(iPhone|iPad|Android)" ) {
        rewrite  ^/mobile/(.*) http://touch.foo.com/mobile/$1 last;
    }
    proxy_pass http://foo.com/$type?$orig_args;
}

هذا ما نحتاج إلى تجنبه عند التطوير باستخدام OpenResty.

تكوين NGINX

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

لنبدأ بالتكوين التالي، وهو بسيط جدًا.

worker_processes auto;

pid logs/nginx.pid;
error_log logs/error.log notice;

worker_rlimit_nofile 65535;

events {
    worker_connections 16384;
}

http {
    server {
        listen 80;
        listen 443 ssl;

        location / {
            proxy_pass https://foo.com;
        }
    }
}

stream {
    server {
        listen 53 udp;
    }
}

ومع ذلك، حتى هذه التكوينات البسيطة تتضمن بعض المفاهيم الأساسية.

أولاً، كل توجيه له سياقه، وهو نطاقه في ملف تكوين NGINX.

المستوى الأعلى هو main، والذي يحتوي على بعض التعليمات التي لا علاقة لها بالأعمال المحددة، مثل worker_processes، pid، وerror_log، وكلها جزء من سياق main. بالإضافة إلى ذلك، هناك علاقة هرمية بين السياقات. على سبيل المثال، سياق location هو server، سياق server هو http، وسياق http هو main.

لا يمكن تشغيل التوجيهات في السياق الخاطئ. سيتحقق NGINX من شرعية nginx.conf عند بدء التشغيل. على سبيل المثال، إذا قمنا بتغيير listen 80; من سياق server إلى سياق main وبدأنا خدمة NGINX، سنرى خطأ مثل هذا:

"listen" directive is not allowed here ......

ثانيًا، يمكن لـ NGINX التعامل ليس فقط مع طلبات HTTP وحركة HTTPS ولكن أيضًا مع حركة UDP وTCP. الطبقة السابعة (L7) هي في HTTP والطبقة الرابعة (L4) هي في Stream. في OpenResty، lua-nginx-module وstream-lua-nginx-module يتوافقان مع هذين السياقين على التوالي.

شيء واحد يجب ملاحظته هنا هو أن OpenResty لا يدعم جميع الميزات في NGINX، وعليك النظر إلى إصدار OpenResty. إصدار OpenResty يتوافق مع NGINX، مما يسهل التعرف عليه.

التوجيهات التكوينية المشار إليها في nginx.conf أعلاه موجودة في الوحدات الأساسية لـ NGINX ngx_core_module, ngx_http_core_module, وngx_stream_core_module، والتي يمكنك النقر عليها لرؤية الوثائق المحددة.

نمط MASTER-WORKER

بعد فهم ملف التكوين، دعونا نلقي نظرة على نمط العمليات المتعددة لـ NGINX (كما هو موضح في الشكل أدناه). كما ترى، عند بدء NGINX، سيكون هناك عملية Master واحدة وعدة عمليات Worker (أو عملية Worker واحدة فقط، اعتمادًا على كيفية التكوين).

نمط Worker في NGINX

أولاً، عملية Master، كما يوحي اسمها، تلعب دور "المدير" ولا تتعامل مع طلبات العملاء. إنها تدير عمليات Worker، بما في ذلك استقبال الإشارات من المسؤول ومراقبة حالة Workers. عندما تخرج عملية Worker بشكل غير طبيعي، ستقوم عملية Master بإعادة تشغيل عملية Worker جديدة.

عمليات Worker هي "الموظفون العاملون الحقيقيون" الذين يتعاملون مع طلبات العملاء. يتم إنشاؤها من عملية Master وهي مستقلة عن بعضها البعض. هذا النموذج متعدد العمليات أكثر تقدمًا بكثير من النموذج متعدد الخيوط في Apache، حيث لا يوجد قفل عبر الخيوط وسهل التصحيح. حتى إذا تعطلت عملية وخرجت، فإنها عادة لا تؤثر على عمل العمليات الأخرى.

يضيف OpenResty وكيلًا مميزًا فريدًا إلى نموذج Master-Worker في NGINX. هذه العملية لا تستمع إلى أي منافذ ولديها نفس الصلاحيات مثل عملية Master في NGINX، لذا يمكنها القيام ببعض المهام التي تتطلب صلاحيات عالية، مثل بعض عمليات الكتابة على ملفات القرص المحلي.

إذا عملت العملية المميزة مع آلية الترقية الساخنة الثنائية لـ NGINX، يمكن لـ OpenResty تنفيذ الترقية الثنائية الكاملة على الفور دون الاعتماد على البرامج الخارجية.

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

مراحل التنفيذ

مراحل التنفيذ هي أيضًا ميزة أساسية لـ NGINX وترتبط ارتباطًا وثيقًا بالتنفيذ المحدد لـ OpenResty. لدى NGINX 11 مرحلة تنفيذ، والتي يمكننا رؤيتها في الكود المصدري لـ ngx_http_core_module.h:

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,

    NGX_HTTP_SERVER_REWRITE_PHASE,

    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,

    NGX_HTTP_PREACCESS_PHASE,

    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,

    NGX_HTTP_PRECONTENT_PHASE,

    NGX_HTTP_CONTENT_PHASE,

    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

إذا كنت تريد معرفة المزيد عن دور هذه المراحل الـ 11، يمكنك قراءة وثائق NGINX حتى لا أتطرق إليها هنا.

بالصدفة، لدى OpenResty أيضًا 11 توجيهًا *_by_lua مرتبطًا بمرحلة NGINX، كما هو موضح في الشكل أدناه (من وثائق lua-nginx-module).

ترتيب توجيهات وحدة Lua NGINX

init_by_lua يتم تنفيذه فقط عند إنشاء عملية Master، وinit_worker_by_lua يتم تنفيذه فقط عند إنشاء كل عملية Worker. التوجيهات الأخرى *_by_lua يتم تشغيلها بواسطة طلبات العملاء ويتم تنفيذها بشكل متكرر.

لذلك خلال مرحلة init_by_lua، يمكننا تحميل وحدات Lua والبيانات العامة للقراءة فقط مسبقًا للاستفادة من ميزة COW (نسخ عند الكتابة) في نظام التشغيل لتوفير الذاكرة.

معظم العمليات يمكن القيام بها داخل content_by_lua، لكنني أوصي بتقسيمها وفقًا للوظائف المختلفة، مثل التالي.

  • set_by_lua: تعيين المتغيرات.
  • rewrite_by_lua: إعادة التوجيه، التحويل، إلخ.
  • access_by_lua: الوصول، الأذونات، إلخ.
  • content_by_lua: إنشاء المحتوى المراد إرجاعه.
  • header_filter_by_lua: معالجة تصفية رأس الاستجابة.
  • body_filter_by_lua: معالجة تصفية جسم الاستجابة.
  • log_by_lua: تسجيل الأحداث.

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

location /mixed {
    content_by_lua '...';
}

بالطبع لا. باستخدام ميزة المراحل، يمكننا فك التشفير في مرحلة access والتشفير في مرحلة body filter دون إجراء أي تغييرات على الكود في مرحلة content الأصلية.

location /mixed {
    access_by_lua '...';
    content_by_lua '...';
    body_filter_by_lua '...';
}

ترقية NGINX الثنائية على الفور

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

يتم الترقية الساخنة عن طريق إرسال إشارات USR2 وWINCH إلى عملية Master القديمة. بالنسبة لهاتين الخطوتين، تبدأ الأولى عملية Master الجديدة؛ بينما تغلق الثانية عملية Worker تدريجيًا.

بعد هاتين الخطوتين، يتم بدء عملية Master الجديدة وعملية Worker الجديدة. في هذه المرحلة، لا تخرج عملية Master القديمة. السبب في عدم الخروج بسيط: إذا كنت بحاجة إلى التراجع، يمكنك إرسال إشارة HUP إلى عملية Master القديمة. بالطبع، إذا كنت قد قررت أنك لا تحتاج إلى التراجع، يمكنك إرسال إشارة KILL إلى عملية Master القديمة للخروج.

هذا كل شيء، وتمت ترقية NGINX الثنائية على الفور.

إذا كنت تريد معرفة المزيد من المعلومات التفصيلية حول هذا، يمكنك التحقق من الوثائق الرسمية لمواصلة التعلم.

الخلاصة

بشكل عام، ما تستخدمه في OpenResty هي أساسيات NGINX، وترتبط بشكل رئيسي بالتكوين، العمليات الرئيسية والتابعة، مراحل التنفيذ، إلخ. الأشياء الأخرى التي يمكن حلها باستخدام كود Lua يتم حلها باستخدام الكود قدر الإمكان، بدلاً من استخدام وحدات وتكوينات NGINX، وهذا تغيير في التفكير عند تعلم OpenResty.

أخيرًا، تركت لك سؤالًا مفتوحًا: يدعم Nginx رسميًا NJS، مما يعني أنه يمكنك كتابة JS للتحكم في بعض منطق NGINX، مشابه لـ OpenResty. ما رأيك في هذا؟ مرحبًا بمشاركة هذه المقالة.