أساسيات لينكس

نشر تطبيقات PHP دون توقف: مجلدات الإصدارات والتبديل الذري للرابط الرمزي وOPcache

انشر تطبيقات PHP دون انقطاع: ابنِ كل إصدار في مجلده، وبدّل الرابط الرمزي ذرياً، واستخدم $realpath_root، وأعد تحميل PHP-FPM، وتراجع بسرعة.

تاريخ النشر
مدة القراءة
6 دقائق
الكاتب
SKYLINE Engineering

نسخ الملفات الجديدة فوق تطبيق PHP يعمل هو ما يجعل المواقع تقدّم شيفرة نصفها قديم ونصفها جديد لبضع ثوانٍ، أو صفحة بيضاء إن فشل النشر في منتصفه. والحل الشائع بسيط: ابنِ كل إصدار في مجلد مستقل، ثم بدّل رابطاً رمزياً اسمه current ليشير إليه في خطوة ذرية واحدة، وتأكد من أن Nginx وPHP-FPM وOPcache تلاحظ هذا التبديل كلها. يعرض هذا الدليل هذا النمط على خادم Debian أو Ubuntu يعمل بـ Nginx وPHP-FPM، مع قواعد ترحيلات قاعدة البيانات والتراجع.

هيكل المجلدات

/var/www/myapp/
  releases/
    20261030120000/
    20261031090000/
  shared/
    .env
    storage/
  current -> releases/20261031090000

كل إصدار نسخة كاملة من التطبيق للقراءة فقط. وكل ما يجب أن يبقى بعد عمليات النشر، مثل ملف .env والملفات المرفوعة والجلسات والسجلات، يعيش في shared/ ويُربط داخل كل إصدار، بينما يشير Nginx دائماً إلى current.

ابنِ الإصدار الجديد بجوار الإصدار الحي

كل العمل البطيء والمحفوف بالمخاطر يتم قبل أن يرى المستخدمون أي شيء. وهذا سكربت نشر مبسّط:

#!/usr/bin/env bash
set -euo pipefail
APP=/var/www/myapp
REL=$APP/releases/$(date +%Y%m%d%H%M%S)

git clone --depth 1 --branch "$1" https://git.example.com/myapp.git "$REL"
cd "$REL"
composer install --no-dev --optimize-autoloader --no-interaction
ln -s $APP/shared/.env "$REL/.env"
rm -rf "$REL/storage" && ln -s $APP/shared/storage "$REL/storage"

# run backward-compatible database migrations here

ln -s "$REL" $APP/current.new
mv -T $APP/current.new $APP/current
sudo systemctl reload php8.4-fpm

# keep the five newest releases
ls -1dt $APP/releases/* | tail -n +6 | xargs -r rm -rf

الخيار composer install --no-dev يتجاوز حزم التطوير، و--optimize-autoloader يحوّل التحميل التلقائي بمعيار PSR-4 إلى خريطة أصناف لتحميل أسرع. وبفضل set -e فإن أي فشل قبل التبديل يترك الموقع الحي كما هو، ولا يبقى إلا مجلد غير مستخدم في releases/.

بدّل الرابط الرمزي ذرياً

السطران المهمان هما:

ln -s "$REL" $APP/current.new
mv -T $APP/current.new $APP/current

يُنشأ الرابط الجديد باسم مؤقت ثم يُعاد تسميته فوق current. فأداة mv من GNU تنقل الملف بإعادة تسميته متى أمكن، وتوثيق rename(2) في لينكس ينص على أن الوجهة الموجودة تُستبدل ذرياً، فلا توجد لحظة تجد فيها عملية أخرى current مفقوداً. والخيار -T (--no-target-directory) ضروري، فمن دونه ترى mv أن current يشير إلى مجلد فتنقل الرابط الجديد إلى داخل الإصدار القديم بدلاً من استبداله.

اجعل Nginx وPHP يريان الإصدار الجديد

إذا مرّر Nginx المسار المار عبر الرابط الرمزي إلى PHP، فقد يُبقي مخزنان مؤقتان الإصدار القديم حياً بعد التبديل: مخزن realpath في PHP يحفظ المسارات المحلولة مدة realpath_cache_ttl ثانية (120 افتراضياً)، وOPcache يخزّن السكربتات المترجمة بحسب مسار الملف. والحل أن يحلّ Nginx الرابط الرمزي مع كل طلب عبر المتغير $realpath_root الذي يعرّفه توثيق Nginx بأنه مسار الجذر بعد حل كل الروابط الرمزية. ويوصي توثيق Symfony لخوادم الويب بهذا تحديداً لعمليات النشر عبر الروابط الرمزية، منبّهاً إلى أن OPcache قد لا يكتشف التغييرات بغير ذلك:

server {
    listen 80;
    server_name example.com;
    root /var/www/myapp/current/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }
}

طابق مسار المقبس مع سطر listen في /etc/php/8.4/fpm/pool.d/www.conf (تأتي Debian 13 مع PHP 8.4 وDebian 12 مع PHP 8.2). ويضيف Symfony تحذيراً: إذا كان PHP-FPM يعمل على جهاز غير جهاز Nginx فقد لا يُحلّ $realpath_root كما تتوقع.

إعدادات OPcache وإعادة تحميل FPM

في الإنتاج يمكنك منع OPcache من فحص طوابع وقت الملفات مع كل طلب عبر opcache.validate_timestamps=0. ودليل PHP صريح في النتيجة: مع تعطيل هذا الإعداد يجب إعادة ضبط OPcache يدوياً عبر opcache_reset() أو opcache_invalidate() أو بإعادة تشغيل خادم الويب كي تسري تغييرات الملفات. ومع تفعيله (وهو الافتراضي) لا تُلتقط التغييرات إلا كل opcache.revalidate_freq ثانية (2 افتراضياً)، فقد يُقدَّم الإصدار من مخزن قديم لفترة وجيزة حتى في هذه الحالة.

ونمط مجلدات الإصدارات يتجنب معظم ذلك، لأن الإصدار الجديد له مسارات حقيقية جديدة، فيترجم OPcache ملفاته كسكربتات جديدة. ثم ينفّذ سكربت النشر systemctl reload php8.4-fpm، وفي Debian ترسل إعادة التحميل الإشارة SIGUSR2 التي يصفها دليل php-fpm بأنها إعادة تحميل هادئة لكل العمليات العاملة ولإعدادات FPM وملفه التنفيذي: تكتمل الطلبات الجارية وتتولى عمليات جديدة العمل. تحقق من النتيجة على خادمك، مثلاً بنص إصدار يطبعه التطبيق، وتجنّب restart الكامل أثناء وجود حركة.

ترحيلات قاعدة البيانات: توسيع ثم تقليص

تبديل الرابط فوري، لكن قاعدة البيانات يتشاركها الإصداران القديم والجديد لوقت قصير، ويعود إليها الإصدار القديم إن تراجعت. لذا اكتب الترحيلات بحيث يعمل الإصداران مع البنية:

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

أما الترحيل الذي يعيد تسمية عمود في عملية النشر نفسها التي تغيّر الشيفرة، فسيكسر أي إصدار لا يتوقعه.

التراجع

لأن الإصدارات القديمة تبقى على القرص، فالتراجع هو التبديل الذري نفسه في الاتجاه المعاكس:

ln -s /var/www/myapp/releases/20261030120000 /var/www/myapp/current.new
mv -T /var/www/myapp/current.new /var/www/myapp/current
sudo systemctl reload php8.4-fpm

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

أخطاء شائعة

  • الكتابة داخل مجلد الإصدار: الملفات المرفوعة المحفوظة داخل إصدار تختفي مع النشر التالي، فوجّه كل مسار قابل للكتابة إلى shared/.
  • العمليات طويلة التشغيل: عمال الطوابير والمجدولات والخدمات التي بدأت من الإصدار القديم تواصل تشغيل الشيفرة القديمة حتى تعيد تشغيلها، فاجعل إعادة تشغيلها جزءاً من النشر.
  • المخازن المؤقتة المبنية أثناء التشغيل: مخازن أطر العمل التي تحفظ مسارات مطلقة يجب بناؤها داخل الإصدار الجديد قبل التبديل، لا نسخها من القديم.
  • مساحة القرص: احتفظ بعدد ثابت من الإصدارات واحذف الباقي كما يفعل السكربت.

ما الذي ينبغي فعله

  1. انتقل إلى هيكل releases/ وshared/ وcurrent، مع وضع كل البيانات القابلة للكتابة في shared/.
  2. ابنِ الإصدار وثبّت اعتمادياته وشغّل الفحوص في مجلده الجديد قبل التبديل.
  3. بدّل عبر ln -s ثم mv -T، ولا تنسخ الملفات فوق الشجرة الحية أبداً.
  4. استخدم $realpath_root في SCRIPT_FILENAME وDOCUMENT_ROOT، وأعد تحميل PHP-FPM بعد كل تبديل.
  5. اجعل الترحيلات متوافقة مع الإصدار السابق، وتدرّب على التراجع قبل أن تحتاج إليه.

المصادر

SKYLINE Engineering

@skyline

The engineering team at SKYLINE Industrial Solutions. We publish field-tested guides drawn from real KSA and GCC deployments.

صفحة الكاتب
SKYLINE engineering services

Need this implemented for you?

Reading is free — building it right takes a team. SKYLINE engineers ship Linux Basics for Aramco vendors, banks, hospitals and government agencies across Saudi Arabia. Talk to us before you start.

Aramco Approved Contractor ISO 9001 · ISO 27001 SAMA CSF aligned NCA ECC ready 247+ KSA clients

Comments

0 total · 0 threads
Be the first to leave a comment.