كلاود
development

إدارة تطبيقات Node.js على السيرفر بـPM2: من التشغيل إلى المراقبة

كلاود|١٨ أكتوبر ٢٠٢٦|6 دقائق قراءة|١٤٠ مشاهدة
إدارة تطبيقات Node.js على السيرفر بـPM2: من التشغيل إلى المراقبة

تشغيل Node.js بأمر node مباشرة يعني أن تطبيقك يموت مع أول خطأ أو إعادة إقلاع. PM2 يعيد تشغيله تلقائياً، ويوزعه على كل الأنوية بوضع cluster، ويعيده عند تجاوز سقف الذاكرة، ويدوّر السجلات، ويقلع مع السيرفر عبر systemd.

تشغيل تطبيق Node.js على السيرفر بأمر node server.js داخل نافذة SSH يعمل في اليوم الأول فقط، فأول استثناء غير معالج يقتل العملية، وإغلاق النافذة يقتلها، وإعادة إقلاع السيرفر تتركها متوقفة حتى ينتبه أحد. PM2 مدير عمليات مصمم لهذه المشكلة بالذات: يعيد تشغيل التطبيق فور سقوطه، ويشغّل نسخة على كل نواة بوضع cluster فيستغل المعالج كاملاً بدل نواة واحدة، ويعيد تدوير العملية إذا تجاوزت سقف ذاكرة تحدده، ويجمع السجلات ويدوّرها حتى لا تملأ القرص، ويقلع تلقائياً مع السيرفر عبر systemd. الإعداد الكامل يتلخص في أربعة أوامر: npm install -g pm2، ثم pm2 start ecosystem.config.js --env production، ثم pm2 startup systemd، ثم pm2 save. على سيرفر بـ4 أنوية يعني وضع cluster أن تطبيقك يعالج قرابة 4 أضعاف الطلبات المتزامنة مقارنة بعملية واحدة، لأن Node يعمل بخيط تنفيذ رئيسي واحد لكل عملية. هذا المقال يشرح كل خطوة، ومتى يكون PM2 الخيار الصحيح ومتى يكفي systemd وحده أو يكون Docker أنسب.

لماذا لا تشغّل node بأمر مباشر

المشاكل التي يحلها مدير العمليات ليست نظرية، بل تحدث في الأسبوع الأول لأي تطبيق حقيقي:

  • السقوط: وعد مرفوض (promise rejection) لم يُعالج في مسار نادر يُنهي العملية، فيتوقف الموقع حتى تعيد تشغيله يدوياً.
  • إعادة الإقلاع: تحديث أمني للنواة يعيد إقلاع السيرفر الساعة 4 فجراً، فيعود السيرفر بلا تطبيقك.
  • الأنوية المهدرة: عملية Node واحدة تستخدم نواة واحدة لتنفيذ JavaScript، فعلى سيرفر بـ8 أنوية تبقى 7 منها شبه عاطلة.
  • تسرب الذاكرة: تخزين مؤقت داخل متغير عام ينمو ببطء حتى يصل لـ1.5GB بعد أيام، ثم يقتله نظام OOM مع عمليات أخرى.
  • السجلات: console.log إلى ملف بلا تدوير يصل لعشرات الجيجابايت خلال أسابيع.

وضع cluster واستغلال كل الأنوية

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

pm2 start server.js -i max --name api
# أو عدد محدد يترك نواة لقاعدة البيانات
pm2 start server.js -i 3 --name api

الشرط المهم: الجلسات والتخزين المؤقت والطوابير يجب أن تكون خارج العملية، في Redis مثلاً، لأن كل طلب قد يصل لنسخة مختلفة. لو حفظت جلسة المستخدم في Map داخل الذاكرة سيُسجّل خروجه عشوائياً كلما وصل طلبه لنسخة أخرى. على سيرفر بباقة أعمال (4 vCPU و8GB) تشغيل 3 نسخ مع ترك نواة لقاعدة البيانات هو التوزيع الذي نوصي به غالباً.

ميزة أخرى في هذا الوضع: الأمر pm2 reload api يعيد تشغيل النسخ واحدة تلو الأخرى، فتبقى نسخ تخدم الطلبات طوال الوقت، وهذا نشر بلا توقف دون موزع أحمال خارجي.

ملف ecosystem للبيئات

بدل تذكّر الخيارات في كل مرة، ضع كل شيء في ملف واحد داخل المستودع:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'api',
    script: './dist/server.js',
    instances: 3,
    exec_mode: 'cluster',
    max_memory_restart: '600M',
    exp_backoff_restart_delay: 200,
    kill_timeout: 5000,
    wait_ready: true,
    listen_timeout: 10000,
    env: { NODE_ENV: 'development', PORT: 3000 },
    env_production: { NODE_ENV: 'production', PORT: 3000 }
  }]
};

تشغّله بأمر pm2 start ecosystem.config.js --env production. الخيار exp_backoff_restart_delay يمنع حلقة إعادة تشغيل مجنونة إذا كان التطبيق يسقط فور إقلاعه، فيزيد الانتظار تدريجياً من 200 مللي ثانية حتى 15 ثانية. أما wait_ready فيجعل PM2 ينتظر رسالة process.send('ready') من تطبيقك بعد اتصاله بقاعدة البيانات، فلا يُعتبر جاهزاً قبل أن يكون جاهزاً فعلاً، وهذا ما يجعل reload آمناً تماماً. الخيار kill_timeout يعطي العملية 5 ثوانٍ لإنهاء الطلبات الجارية بعد إشارة الإيقاف قبل قتلها بالقوة.

إعادة التشغيل عند تجاوز الذاكرة

الخيار max_memory_restart: '600M' شبكة أمان لا علاج: إذا تجاوزت أي نسخة 600MB يعيد PM2 تشغيلها، وفي وضع cluster لا يلاحظ المستخدم شيئاً لأن النسخ الأخرى تخدم الطلبات. اختر الرقم بعد قياس، فراقب التطبيق بأمر pm2 monit عدة أيام، ثم ضع السقف عند ضعف الاستهلاك الطبيعي تقريباً. إذا كان التطبيق يستقر عند 250MB فسقف 500 إلى 600MB مناسب، وإذا رأيت إعادة تشغيل بسبب الذاكرة أكثر من مرة في اليوم فلديك تسرب حقيقي يحتاج تحليلاً بـ--inspect ولقطات heap، لا رفع السقف.

احسب الذاكرة الكلية أيضاً: 3 نسخ بسقف 600MB تعني 1.8GB في أسوأ الأحوال، وعلى سيرفر بـ4GB يشارك قاعدة بيانات فهذا الحد الأقصى المعقول. إن احتجت أكثر فإضافة 2GB RAM على الخادم السحابي تكلف 600 ريال سنوياً.

تدوير السجلات

PM2 يكتب مخرجات كل تطبيق في ~/.pm2/logs/ بلا حد افتراضي، والحل وحدة رسمية تدوّرها:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 14
pm2 set pm2-logrotate:compress true

بهذا الإعداد لا يتجاوز أي ملف سجل 50MB، وتُحفظ آخر 14 نسخة مضغوطة، فيبقى استهلاك السجلات تحت 300MB لكل تطبيق تقريباً بدل أن ينمو بلا نهاية. لقراءة السجلات الحية استخدم pm2 logs api --lines 200، وإن كنت تريد سجلات منظمة بصيغة JSON لإرسالها لنظام مركزي فأضف log_type: 'json' في ملف ecosystem.

الإقلاع التلقائي مع systemd

PM2 نفسه عملية تحتاج من يشغّلها بعد إعادة الإقلاع، وهنا يأتي دور systemd:

pm2 startup systemd -u deploy --hp /home/deploy
# انسخ الأمر الذي يطبعه ونفذه بصلاحية root
pm2 save

الأمر الأول ينشئ خدمة pm2-deploy.service تُشغّل PM2 عند الإقلاع، والثاني يحفظ قائمة التطبيقات الحالية حتى يستعيدها. أكثر خطأ شائع هنا: تضيف تطبيقاً جديداً وتنسى pm2 save، فيعمل حتى أول إعادة إقلاع ثم يختفي. اجعل pm2 save آخر سطر في سكربت النشر دائماً، وجرّب إعادة الإقلاع مرة واحدة بعد الإعداد للتأكد بدل الاكتشاف وقت حادثة.

المراقبة: ماذا تتابع يومياً

الأمر pm2 ls يعطيك في سطر واحد لكل نسخة أهم رقمين: عمود ↺ وهو عدد مرات إعادة التشغيل، وعمود الذاكرة. عدد إعادة التشغيل هو مؤشرك الأول على صحة التطبيق، فإذا كان 0 أو 1 بعد أسبوع فكل شيء جيد، وإذا قفز إلى 40 فهناك سقوط متكرر يخفيه PM2 عنك بإعادة التشغيل السريعة، والمستخدمون يرون أخطاء متقطعة لا تظهر لك. الأمر pm2 describe api يعرض سبب آخر إيقاف ومسارات السجلات وزمن التشغيل. للتنبيه الآلي ضع فحصاً بسيطاً في cron كل 5 دقائق يقرأ مخرجات pm2 jlist بصيغة JSON ويرسل تنبيهاً إذا زاد عدد إعادة التشغيل عن 5 خلال ساعة، فهذا يلتقط المشاكل قبل أن يبلغك بها عميل.

PM2 أم systemd وحده أم Docker

PM2 ليس الخيار الوحيد، والاختيار يعتمد على شكل بنيتك:

المعيارPM2systemd وحدهDocker
إعادة التشغيل عند السقوطنعمنعم (Restart=always)نعم (restart: unless-stopped)
وضع cluster على عدة أنويةمدمجيدوي بعدة وحدات أو node:clusterعدة حاويات خلف موزع
نشر بلا توقفpm2 reloadيحتاج ترتيباً يدوياًيحتاج أداة تنسيق
سقف الذاكرةإعادة تشغيل ناعمةMemoryMax يقتل العمليةmem_limit
العزل بين التطبيقاتضعيفمتوسطقوي
الأنسب لـتطبيق أو عدة تطبيقات Node على سيرفر واحدخدمة واحدة بسيطةتطبيقات بلغات وإصدارات مختلفة

قاعدتنا العملية: إذا كان السيرفر يشغّل تطبيقات Node فقط، فPM2 يعطيك cluster وreload والمراقبة بأقل جهد. إذا كانت خدمة واحدة صغيرة بنسخة واحدة، فملف systemd من 10 أسطر يكفي ويوفر طبقة إضافية. أما إذا كان السيرفر يجمع Node 18 وNode 22 وPython وقاعدة بيانات، فDocker يمنع تعارض الإصدارات، ويمكنك حتى تشغيل PM2 داخل الحاوية بأمر pm2-runtime. قوالب استضافة الذكاء الاصطناعي مثل n8n وFlowise تعتمد على Node أصلاً، وتأتي جاهزة بإدارة عمليات مضبوطة.

خطوات التنفيذ

  1. ثبّت PM2 عالمياً وأنشئ ecosystem.config.js بوضع cluster وسقف ذاكرة.
  2. انقل الجلسات والتخزين المؤقت إلى Redis قبل تفعيل أكثر من نسخة.
  3. أضف process.send('ready') بعد اتصال قاعدة البيانات وفعّل wait_ready.
  4. ثبّت pm2-logrotate بحد 50MB و14 نسخة.
  5. نفذ pm2 startup ثم pm2 save، وجرّب إعادة إقلاع واحدة.
  6. استخدم pm2 reload في سكربت النشر بدل restart.

باقة نمو (2 vCPU و4GB) بـ6,900 ريال سنوياً تكفي لتطبيق Node بنسختين مع Redis، وباقة أعمال بـ11,500 ريال سنوياً تعطيك 4 أنوية لثلاث نسخ وقاعدة بيانات، والمقارنة الكاملة في صفحة الأسعار.

مشاركة