تأمين الأنظمة عالية الحمل وإطلاقها: حافة محصَّنة ونشر بلا توقف
طبقات الأمان من CDN وWAF إلى الشبكة الخاصة، وخط تسليم يعتمد النشر التدريجي وblue/green وترحيلات expand and contract، وينتهي بقائمة جاهزية لليوم الذي تأتي فيه الذروة.
Author
Anichur Rahaman
منذ يوم13 min read1 views
قبل سبع عشرة دقيقة من شكوى صفحة الدفع في الجزء الرابع، وفي التاسعة وأربع وأربعين دقيقة من صباح يوم الطرح نفسه، يواجه مهندس المناوبة في موقع التذاكر مشكلة مختلفة. يبدأ البيع في العاشرة، ونحو 1,000 مشترٍ سيتحركون في الدقيقة نفسها. يطلب مطوّر إطلاق إصلاح من سطر واحد لخطأ إملائي في بريد تأكيد الطلب. وعند 9:52 تُظهر لوحة المتابعة 4,000 محاولة دخول في الدقيقة من حفنة عناوين، أي سكربت مضاربين يسخّن محركاته. (هذا سيناريو توضيحي لا حادثة حقيقية.)
يتحدد مصير ذلك الصباح بقرارين اتُّخذا قبل أسابيع: ما مقدار النظام الذي يستطيع الإنترنت الوصول إليه، وهل يحتاج النشر إلى إيقاف الموقع. فإذا كان الجواب «كل شيء» و«نعم»، اضطر المهندس إلى رفض الإصلاح وانتظار أن يملّ السكربت من تلقاء نفسه.
يتناول هذا المقال الأخير الأمرين معًا: مسارًا محصَّنًا من الإنترنت إلى البيانات، وخط تسليم لا يوقف الموقع أبدًا. وأعتمد فيه على ملاحظاتي الميدانية من منصات جهّزتها لذروات مجدولة، كتخفيضات خاطفة أو طرح تذاكر أو بدء امتحان. هذه دروس إعداد واحد، لا وصفة تصلح لكل حالة.
لا يوجد منتج واحد يؤمّن النظام. الجدار الناري لا يعالج كلمة مرور مسرّبة، وكلمة المرور القوية لا تنفع إذا كانت قاعدة البيانات مفتوحة على الإنترنت. النهج العملي هو الطبقات، بحيث تفترض كل طبقة أن التي أمامها ستفشل أحيانًا.
تصلح قائمة OWASP Top 10 مقياسًا واقعيًا هنا. فالإصدار الحالي، OWASP Top 10:2025، يضع Broken Access Control في المرتبة الأولى وSecurity Misconfiguration في الثانية. وكلاهما يتعلق بطريقة ربط النظام لا بثغرات نادرة. وهذا ما أراه في الواقع: معظم الحوادث سببها منفذ تُرك مفتوحًا، أو إعداد افتراضي لم يُغيَّر، أو صلاحية أوسع مما يلزم.
الحافة وحدها عامة. كل ما خلفها يعيش في شبكة خاصة ولا يقبل حركة المرور إلا من مصادر محددة.
الحافة: CDN وWAF والروبوتات وتحديد المعدل
ضع شبكة CDN مع جدار حماية لتطبيقات الويب (WAF) أمام كل ما هو عام. فهي تمتص الفيضانات الكبيرة، وتقدّم الصفحات المخزّنة دون أن تلمس خوادمك، وتحجب أشيع أنماط الهجوم قبل أن تصل إلى شيفرتك. وفي أوقات الذروة تحمي طاقتك أيضًا: كل طلب تجيب عنه الحافة لا تراه عقد الويب أصلًا.
ثلاثة إعدادات تفوق غيرها أهمية:
إدارة الروبوتات. التخفيضات الخاطفة تجذب المضاربين والسكربتات. اختبر العملاء المشبوهين في مساري الدفع وتسجيل الدخول فقط، لا في الموقع كله، حتى لا يتباطأ العملاء الحقيقيون.
تحديد المعدل عند الحافة. ضع حدًا لكل عميل على تسجيل الدخول وإعادة تعيين كلمة المرور والبحث والدفع. وابنِ الحد على حركة حقيقية: أعد في اختبار الحمل تشغيل مزيج الطلبات من ذروة سابقة، ولاحظ أعلى معدل مشروع لكل عميل.
تحديد المعدل داخل التطبيق. يمكن تجاوز الحافة إذا عثر أحدهم على عنوان الخادم الأصلي. لذلك أبقِ حدًا ثانيًا في التطبيق، لكل حساب ولكل إجراء، كي لا يستنزف مستخدم واحد نقطة نهاية مكلفة.
أحكِم قفل الخادم الأصلي بحيث لا يقبل إلا حركة شبكة الحافة. فإذا كانت خوادمك ترد على أي جهة في الإنترنت مباشرة، فإن WAF مجرد اقتراح لا ضابط.
TLS وأين ينتهي
شفّر كل شيء أثناء النقل. استخدم TLS 1.3 حيث يدعمه العملاء، وأبقِ TLS 1.2 حدًّا أدنى. يشترط معيار PCI DSS 4.0 تشفيرًا قويًا لبيانات حاملي البطاقات على الشبكات العامة، ويستبعد SSL والإصدارات القديمة من TLS، فأوقف TLS 1.0 و1.1 إذا كنت تقبل البطاقات.
سؤال التصميم هو أين ينتهي TLS. كثير من الإعدادات تنهيه عند CDN، ثم مرة أخرى عند وكيل عكسي على المضيف، ثم تمرّر HTTP عاديًا إلى الحاوية. هذه القفزة الأخيرة مقبولة داخل شبكة مضيف خاصة، لكن بشرط أن تكون واثقًا من أنها خاصة فعلًا. وإذا عبرت الحركة شبكة لا تتحكم بها، فشفّر هذه القفزة أيضًا.
وهناك فخ قريب هو عدم تطابق الحدود. في إحدى المنصات التي شغّلتها كانت طبقتا nginx متتاليتين: وكيل المضيف الذي ينهي TLS، ووكيل الحاوية أمام PHP-FPM. كان الحد الافتراضي لحجم الجسم في المضيف 1 ميغابايت، فظلّت عمليات الرفع تفشل بصمت إلى أن اتفقت كل الطبقات: client_max_body_size في الوكيلين، وpost_max_size وupload_max_filesize في PHP. هذه مشكلة موثوقية، لكنها تغري البعض برفع الحد عشوائيًا. حدّد الحد الأقصى مرة واحدة، وطبّقه في كل طبقة، ودوّنه.
شبكة خاصة: العام هو الحافة فقط
أثمن قاعدة هي أيضًا أكثرها رتابة: لا شيء عام سوى الحافة وموزّع الأحمال. عقد الويب والعمّال وقاعدة البيانات والذاكرة المؤقتة وعقدة البحث كلها في شبكة خاصة. وقاعدة البيانات والذاكرة المُدارتان لا تقبلان اتصالًا إلا من تلك الشبكة، فلا تكفي كلمة مرور مسرّبة وحدها لتمنح المهاجم طريقًا إلى الداخل.
أضف قواعد جدار ناري تحدد من يحق له الحديث مع من، مثلًا:
المكوّن
من يحق له الاتصال
عام؟
CDN / WAF
الإنترنت
نعم
موزّع الأحمال
نطاقات شبكة الحافة فقط
نعم، مقيّد
عقد الويب وSSR
موزّع الأحمال فقط
لا
العمّال والمجدول
لا أحد من الخارج
لا
قاعدة البيانات والذاكرة المؤقتة
عقد الويب والعمّال، عبر عناوين خاصة
لا
SSH والوصول الإداري
خادم قفز أو VPN، بالمفاتيح فقط
لا
راجع هذا الجدول كلما تغيّرت البنية. فأغلب قصص «بقينا مكشوفين شهرًا» تبدأ بتعديل سريع لم يدوّنه أحد.
حادثة الاسم المشترك: العزل داخل المضيف
العزل الشبكي لا يخص الإنترنت وحده. وجدتُ على أحد المضيفات عدة مشاريع متجاورة تتشارك شبكة Docker واحدة، وكل مشروع سمّى خدمة PHP فيه app. كان الوكيل مضبوطًا على إرسال الطلبات إلى app على المنفذ 9000، وكان هذا الاسم يُحلّ إلى عدة حاويات. فوصل نصف الطلبات إلى PHP-FPM يتبع مشروعًا آخر.
لم ينهَر شيء. كانت الصفحات تأتي من شيفرة خاطئة، بإعدادات خاطئة، وأحيانًا من قاعدة بيانات خاطئة. هذه مشكلة موثوقية وتسرّب بيانات في آن واحد.
الحل بسيط. امنح كل مشروع شبكته الخاصة، واستخدم أسماء خدمات فريدة، ولا تكشف خدمة لشبكة لا تحتاج إلا الوصول إلى شيء آخر. وعند مراجعة أي نشر اسأل بوضوح: هل تستطيع هذه الحاوية الوصول إلى ما لا مبرر لوصولها إليه؟
الأسرار والحسابات والتحديثات الأمنية
ثلاث عادات تمنع جزءًا كبيرًا من الضرر.
أبقِ الأسرار خارج الصور. تُنسخ الصورة إلى السجلات والحواسيب المحمولة وذاكرات البناء. احقن الأسرار وقت التشغيل من البيئة أو من مخزن أسرار، وبدّلها عند مغادرة أي موظف.
أقل صلاحية لكل حساب. يجب ألا يستطيع مستخدم قاعدة البيانات الخاص بالتطبيق حذف الجداول أو إنشاء مستخدمين. ويمكن للعمّال والتقارير والترحيلات أن تعمل بحسابات مختلفة وصلاحيات مختلفة. ويحصل الموظفون على أدوار لا على حساب إداري مشترك، ويُضاف عامل ثانٍ للوصول الإداري.
حدّث وفق جدول. أضافت OWASP «إخفاقات سلسلة توريد البرمجيات» إلى قائمة 2025 لسبب وجيه: ما تعتمد عليه جزء من سطح هجومك. أعد بناء الصور بانتظام، وافحصها بحثًا عن الثغرات المعروفة، وأبقِ قائمة الاعتماديات قصيرة بما تستخدمه فعلًا. ويمكنك مقارنة إعدادات الخوادم والحاويات بمعايير CIS Benchmarks الخاصة بـ Docker وتوزيعات Linux الشائعة.
وأخيرًا افترض أن شيئًا ما سيسوء في النهاية. احتفظ بنسخ احتياطية لا يصل إليها المهاجم بالبيانات نفسها، ودرّب نفسك على الاستعادة. شرحتُ ذلك في نسخ احتياطية جاهزة لبرمجيات الفدية لأنظمة الأعمال، ويكفي هنا أن أضيف: استعادة لم تجرّبها قط أمنية لا خطة.
الشحن دون توقف: ابنِ مرة واحدة وانقل الناتج نفسه
النشر المخيف يدفع الناس إلى تأجيل التحديثات وتجنب التغيير، لذا اجعل هدفك نشرًا مملًا. القاعدة الأولى: ابنِ مرة واحدة وأرسل الناتج نفسه بالضبط. ابنِ الصورة على جهاز واحد، وادفعها إلى السجل، ودع كل عقدة تسحب تلك الصورة بعينها. قارن معرّفات الصور في كل العقد قبل تحويل الحركة. ولا تنفّذ تحديث المصدر والبناء على عقدة تخدم الزوار: فقد تختلف عقدتان بُنيتا بينهما ساعة دون أن يلاحظ أحد، وقد يترك بناء فاشل في منتصفه خادمًا حيًّا معطوبًا.
تنتقل الإعدادات بمعزل عن الصورة، فيمرّ الناتج نفسه من بيئة الاختبار إلى الإنتاج دون تغيير. وهذا ما يتيح لك القول إن ما اختبرته هو ما أطلقته.
ترحيلات قاعدة البيانات والموقع يعمل: التوسيع ثم الانكماش
قاعدة البيانات هي المكان الذي ينكسر فيه «انعدام التوقف» عادةً. فأثناء النشر التدريجي تعمل الشيفرة القديمة والجديدة معًا على مخطط واحد، لذا يجب أن يصلح الترحيل للاثنتين.
الجواب المعتاد هو نمط expand and contract، ويسمى أيضًا parallel change:
التوسيع (Expand). أضف العمود أو الجدول الجديد بطريقة تتجاهلها الشيفرة القديمة، مثل عمود يقبل القيمة الفارغة.
الترحيل (Migrate). انشر شيفرة تكتب في الشكلين القديم والجديد، واملأ الصفوف الموجودة على دفعات صغيرة في الخلفية.
التحويل (Switch). انقل القراءة إلى الشكل الجديد وراقب الأخطاء.
الانكماش (Contract). حين لا تعود أي شيفرة قيد التشغيل تستخدم الشكل القديم، احذفه في إصدار لاحق.
مثال محسوب بأرقام توضيحية. تريد تغيير اسم العمود phone في جدول عملاء من 2.4 مليون صف إلى contact_phone. التوسيع: أضف contact_phone عمودًا يقبل الفراغ. الترحيل: تكتب الشيفرة الجديدة في العمودين عند كل حفظ، بينما تنسخ مهمة خلفية 5,000 صف في الدفعة. أي 480 دفعة، وبنحو ثانيتين للدفعة تستغرق قرابة 16 دقيقة دون أن يُقفل الجدول أمام أي مستخدم. وقبل نقل القراءة تحقق أن عدد الصفوف التي فيها phone ممتلئ وcontact_phone فارغ يساوي صفرًا. عندها فقط ابدأ الانكماش.
الحذف هو الخطوة الوحيدة التي لا رجعة فيها، لذلك ينتظر. شغّل الترحيلات والموقع يخدم الزوار، وتحقق من النتيجة: عدد الصفوف قبل وبعد، وفحصًا يؤكد ألا شيء بقي نصف محوَّل. وخذ نسخة جديدة من قاعدة البيانات قبل النافذة، واحتفظ بمرجع للتراجع في الشيفرة أيضًا.
تحتاج كل طبقة إلى استراتيجية مختلفة. للخوادم الخلفية PHP عديمة الحالة أستخدم نشرًا تدريجيًا، عقدة واحدة في كل مرة:
لكل مرحلة بوابة. إذا فشلت بوابة توقف الخط وبقي الإصدار السابق يخدم الزوار.
أفرغ عقدة واحدة عند موزّع الأحمال، لتُنهي الطلبات الجارية ولا تستقبل جديدًا.
انشر الصورة الجديدة على تلك العقدة وأعد تشغيل عمّال PHP ليلتقط OPcache الشيفرة الجديدة.
شغّل فحص تشغيل أولي (smoke test) مباشرة على العقدة: سجّل الدخول، افتح منتجًا، أضفه إلى السلة.
أعد العقدة إلى موزّع الأحمال واتركها تحت المراقبة بضع دقائق وأنت تتابع الأخطاء وزمن الاستجابة.
كرّر الخطوات مع العقدة التالية.
القرار الحاسم يقع والعقدة ما زالت خارج موزّع الأحمال، فلا يكلّف فشل الفحص الأولي شيئًا.
مع عقدتين يبقى للموقع دائمًا خادم سليم. وفي الطلبات القابلة للإعادة دون أثر جانبي يمكن لقاعدة إعادة المحاولة في الوكيل أن تخفي عن المستخدمين عطلًا عابرًا، لكن لا تفعّلها للطلبات التي تخصم من بطاقة.
أما للواجهات المرندَرة على الخادم فأفضّل blue/green. شغّل الإصدار الجديد بجانب القديم، واختبره بمعزل عن الزوار، ثم بدّل بتغيير ملف واحد في الوكيل وإعادة تحميل سلسة. والتراجع هو الخطوة نفسها بالعكس، في نحو ثانية. وتشغّل StoreConsole إنتاجها بهذه الطريقة.
العمّال والمجدول لهما عناية خاصة. أفرغ الطوابير قبل تبديل العمّال: أوقف قبول المهام الجديدة، وامنح المهام الجارية مهلة سخية لتنتهي، ثم شغّل الإصدار الجديد. وابدأ المجدول أخيرًا، على عقدة واحدة بالضبط، حتى لا يشغّل نظام نُشر نصفه مهمة دورية مرتين أو على شيفرة خاطئة.
التحقق وفترة المراقبة وقائمة الإطلاق
لا ينتهي النشر حين يعود الأمر إلى سطر الأوامر، بل حين تثبت سلامته. أجرِ فحصًا شاملًا يغطي عملية شراء حقيقية أو تقديمًا حقيقيًا، وتأكد أن الطوابير تتحرك، ثم راقب: واصل متابعة معدل الأخطاء وزمن الاستجابة p95 وعمر الطابور مدة محددة قبل أن تعلن الانتهاء. استخدم لوحات الجزء الرابع من هذه السلسلة، وتحقق من أي تنبيه مخيف على العملية الفعلية قبل التصرف.
هذه القائمة التي أعتمدها قبل أي حدث عالي الحمل:
أجرِ اختبار حمل بالمعدل المستهدف مع حدود إيقاف، واحفظ النتائج.
وسّع طاقة الويب والواجهات مسبقًا قبل الحدث، بدل انتظار التوسع التفاعلي.
تأكد أن الخادم الأصلي لا يقبل إلا حركة الحافة، وأن قواعد WAF والروبوتات وتحديد المعدل فعّالة على تسجيل الدخول والدفع.
تأكد أن قاعدة البيانات والذاكرة المؤقتة وعقدة البحث بلا عنوان عام.
افحص إعدادات TLS وتواريخ انتهاء الشهادات، وتطابق حدود الرفع في كل طبقة.
خذ نسخة جديدة من قاعدة البيانات، وجرّب استعادتها، ودوّن مراجع التراجع.
جمّد التغييرات باستثناء الإصلاحات الطارئة، وحدد من يملك حق الموافقة عليها.
تأكد أن التنبيهات تصل إلى شخص مستيقظ، مع مسار تصعيد مكتوب.
جرّب التراجع تجريبيًا، بما في ذلك تبديل الواجهة بإعادة تحميل واحدة.
تحقق أن المجدول يعمل على عقدة واحدة بالضبط وأن الطوابير خالية من المهام القديمة.
نعود إلى التاسعة وأربع وأربعين دقيقة. مع هذه الترتيبات يوافق المهندس على إصلاح الخطأ الإملائي. فالصورة بُنيت واختُبرت قبل ساعة، والعقد تُنشر واحدة واحدة، ومحاولات الدخول الأربعة آلاف في الدقيقة تصطدم بتحدٍّ على مسار الدخول وحده بينما يتصفح المشترون بلا أي إزعاج. وقاعدة البيانات بلا عنوان عام، فلا يجد السكربت شيئًا خلف الحافة. يصبح الإصلاح حيًّا وتنتهي مراقبته عند 9:55، قبل خمس دقائق من بدء البيع.
يبيّن الجدول التالي كيف تفشل كل طبقة وكيف تفحصها بسرعة.
Anichur Rahaman مهندس برمجيات معماري ومؤسس StoreConsole، يصمّم أنظمة التجارة وERP للشركات النامية، ويهتم خصوصًا بالبنية القائمة على الأحداث وسلامة البيانات وتشغيل الأنظمة على خوادم الشركة نفسها.