الجزء 2: اثنين Load Balancers، اثنين Pools، و لماذا إضافة خوادم لم تكن كافية
كيف أعدنا بناء طبقة التطبيق لمنصة امتحان: اثنين load balancers، اثنين autoscale pools، frontend Next.js مع اثنين containers لكل عقدة، backend tag-based، CPU metric التي تتخلف 5 إلى 8 دقائق، و rollout مع 219 probes و 0 أخطاء.
ساعة واحدة بعد تبديل DNS إلى frontend جديد، الخادم القديم لا يزال يتقبل حول 36% من الحركة. خفضنا TTL إلى 300 ثانية. اختبرنا الإعداد الجديد عبر مدخل hosts-file. وحتى الآن أكثر من ثلث الزوار مشوا مباشرة بجانب الباب الأمامي الجديد، لأن resolvers الخاصة بهم تذكرت العنوان القديم.
هذا الرقم هو لماذا الخادم القديم بقي حياً كطريقتنا للخلف. كما أنه يختزل هذه المرحلة: كل خطوة بدت بسيطة على الورق، وكل خطوة أخفت مفاجأة مقاسة.
في الجزء 1 وجدنا أن اختناقاتنا الأولى كانت محدِّد معدل وفيضان اتصالات Valkey، ليس نقص خوادم. فقط بعد ذلك إضافة خوادم أصبحت الأداة الصحيحة، وحتى ثم احتاجت معمارية. هذا المقال يغطيها: اثنين load balancers، اثنين autoscale pools، frontend مُعاد بناء، backend حيث أي عقدة يمكن استبدالها، وطريقة deploy لا تسقط الطلبات. النسخة الأولى من تلك الطريقة سقطت.
هذا الجزء 2 من سلسلة خماسية «من خادم واحد إلى جاهز لليلة الامتحان». NovaCommerce اسم خيالي؛ المعمارية والأرقام والأخطاء حقيقية.
لماذا خوادم أكثر لم تكن الإجابة الأولى
في الجزء 1 الاختناق تحرك ثلاث مرات. أولاً محدِّد معدل قام الطلاب بـ عنوان IP، لذا مدرسة كاملة شاركت جرة واحدة و حصلت على HTTP 429. بعدها Valkey، حيث اختبار تحميل أرجع 2,854 خطأ تطبيق لأن كل طلب فتح اتصال TLS جديد إلى خادم لم يستطع قبولها بسرعة كافية. فقط الثالث، backend CPU عادي، هو النوع الذي خوادم أكثر حلون: عقدة واحدة 4 vCPU / 8 GB خدمت حول 65 إلى 70 طلب في الثانية من خليط API حقيقي.
إضافة عقد للأول اثنين كانت أسوأهما. كل عقدة جديدة كانت ستشغل نفس كود محدِّد المعدل وفتح اتصالات خاصة بها للـ Valkey نفس. كنا قد دفعنا لخوادم أكثر ورأينا نفس الأخطاء، أسرع فقط. لذا هذا المقال حول الاختناق الثالث، done properly.
اثنين load balancers، وليس واحد
رسمنا الأول استخدم load balancer واحد لكل شيء. واحد أرخص من اثنين، وهناك أقل لتراقبه. Status: Considered, then Rejected.
DigitalOcean load balancer لا يستطيع توجيه اسم المضيف أو مسار URL، وكل واحد يستهدف وسم واحد بالضبط، مما يعني مجموعة خوادم واحدة. Frontend و backend nodes مجموعات مختلفة، بأحجام مختلفة، فحوصات صحة وقواعد scaling مختلفة. هو استقبال يسلم كل زائر قائمة الغرف نفسها.
DNS بالفعل انقسم موقع الويب و API إلى نطاقات منفصلة، لذا الانقسام جاء مجاناً: load balancer واحد لكل tier، كل واحد أمام برك خاصة به. Status: Implemented. الاثنان معاً تكلّفا $48 شهرياً.
لا warm pool، لذا احتفظنا بالطهاة في المطبخ
مبكراً كتبنا متطلب جذاب: warm standby يستطيع أخذ حركة في 3 إلى 4 ثوان. لا يوجد هنا. DigitalOcean autoscale pools ليس لهم warm pool، و droplet جديد يحتاج دقائق ليُنشأ، يُبوّت، يُفحص و يُعترف به من load balancer. تاكسي خامل بالفعل في بابك شيء مختلف عن تاكسي يجب أن تتصل به.
لذا فعلنا شيئين ممل بدلاً من ذلك.
طاقة احتياطية تخدم بالفعل. كلا البركتين لهما الحد الأدنى من 2 عقدة، وكل واحد يجلس داخل load balancer طول الوقت. فقدان واحد يترك الأخرى تأخذ حركة بالفعل.
Pre-scaling. حول 45 دقيقة قبل امتحان كبير نرفع الحد الأدنى للبركة، لذا العقد الإضافية مُبوّتة، مفحوصة وتخدم قبل أول طالب ينقر ابدأ. عقدة backend إضافية تكلّف حول $0.08 في الساعة.
فكّرنا أيضاً عن Kubernetes (DOKS) لـ scaling مستوى ثوان. كان معناه أكثر كثير لتشغيل، لذا تركناه للاحقة. Status: Considered, not implemented.
Frontend: ثمانية أنوية وأمين صندوق واحد
القديم frontend كان droplet واحد 8 vCPU / 16 GB يشغل حاوية Next.js واحدة، مع TLS من certbot على droplet ولا load balancer. إذا مات، الموقع كان ميتاً. هو أيضاً أهدر أموالاً بصمت: عملية Node.js واحدة تقدِّم على تقريباً core واحد، لذا معظم تلك ثمانية CPUs فعلت لا شيء. سوبر ماركت مع ثمانية أماكن checkout وأمين صندوق واحد.
التصميم الجديد يشغل عدة عقد صغيرة، متطابقة بدلاً من واحدة كبيرة. الشكل 1 يظهر طبقة التطبيق كل؛ نمشي عبرها من اليسار.
اثنين تير، كل واحد مع الخاص load balancer وpool. العامل هو الخادم الثابت الوحيد خارج كلا البركتين.
عقدة frontend واحدة
كل عقدة تشغل nginx أمام اثنين متطابقين Next.js containers، واحد لكل vCPU. الإعدادات التي تهم:
منافذ حاوية مرتبطة بـ localhost فقط، لذا فقط nginx يستطيع الوصول إلى حاوية.
nginx توازن بين اثنين containers مع least_conn: كل طلب يذهب إلى حاوية مع أقل اتصالات نشطة.
كل حاوية لها غطاء ذاكرة 1.5 GB، restart: always و rotating logs.
nginx تأخذ عنوان عميل حقيقي من load balancer، لذا حدود per-student تبقى عاملة بدلاً من رؤية عنوان واحد للجميع.
Micro-cache تخدم ملفات ثابتة، صور والصفحة الرئيسية بدون إيقاظ Next.js.
/lb-health يمر فقط عندما Next.js يجيب، لذا عقدة مع app ميتة تترك الدوران حتى لو nginx حي.
HTTPS ينهي عند frontend load balancer، والـ cloud firewall يدع عقدة frontend قبول منفذ 80 فقط من ذلك load balancer.
تبديل DNS، مع طريقة للخلف
بنينا frontend جديد بجنب الخادم القديم واختبرناه عبر مدخل hosts-file على ماكينات خاصة بنا، لذا النطاق الحقيقي أشار إلى load balancer جديد لنا وحدنا. قارنا 12 صفحة حقيقية مع الخادم القديم، خفضنا TTL DNS إلى 300 ثانية وبدلنا.
الخادم القديم بقي لـ rollback، لأن إعادة توجيه DNS كانت would have been كل التراجع. احتجنا له أطول مما متوقع: بعد ساعة واحدة لا يزال يتقبل حول 36% من الحركة من DNS مخزَّن. TTL طلب، ليس أمر. حذف الخادم مبكراً كان بعث حول ثلث الزوار إلى عنوان لم يعد يجيب.
اختبار الحمل، وعقدة أرخص
اثنين frontend nodes خدما حول 356 طلب في الثانية مع p95 من 0.05 ثوان و 0 أخطاء، على 55 إلى 60% CPU. البركة أول استخدمت droplets dedicated-CPU. هذا الاختبار أظهر الكثير headroom، لذا انتقلنا إلى droplets shared AMD مع 2 vCPU / 4 GB، حول $28 شهرياً كل واحد. قياس، ليس تخمين، جعل عقدة أرخص آمنة.
قبل
بعد
الخوادم
1 droplet، 8 vCPU / 16 GB، 1 Next.js container
بركة من 2 إلى 10 عقدة، 2 vCPU / 4 GB shared AMD، 2 containers كل واحد
إذا خادم واحد مات
الموقع ميت
العقدة الأخرى تبقى تخدم
مقاس مع 2 عقدة
Rendering محدود إلى حول core واحد
حول 356 طلب/س، p95 0.05 س، 0 أخطاء، CPU 55 إلى 60%
Backend: من droplet IDs إلى وسم
قبل، load balancer backend أشار droplets اثنين برقم معرف. خادم ثالث لم يستطع أبداً انضم نفسه، لأن load balancer عرف فقط اسمين. غيّرنا الهدف من IDs إلى وسم. هو الفرق بين قائمة ضيوف وشارة طاقم عمل: أي شخص يرتدي الشارة يدخل.
التغيير كان تحديث API واحد الذي احتفظ مع كل إعدادات أخرى لـ load balancer، مع 0 أخطاء أثناء التبديل. من بعدها، أي عقدة البركة ينشئ يحمل الوسم، ينضم load balancer نفسه، ويسقط تحت قواعد firewall نفس وسم وقواعد database access. HTTPS يجري end to end: load balancer يُعيد تشفير إلى كل backend عبر الشبكة الخاصة. كل عقدة تبوّت من snapshot ذهبي واحد.
Frontend pool
Backend pool
عقدة
2 vCPU / 4 GB shared AMD، حول $28 شهرياً
4 vCPU / 8 GB، حول $56 شهرياً
الحد الأدنى / الأقصى
2 / 10
2 / 10
قاعدة scale-out
70% متوسط CPU، cooldown 5 دقائق
70% متوسط CPU (لاحقاً 55%)، cooldown 5 دقائق
فحص صحة
/lb-health يمر عندما Next.js يجيب
/lb-health أجاب من nginx وحده
عقد بدون حالة، والخادم الواحد الذي ليس
عقد الويب كانت بالفعل بدون حالة، وفحصنا ثانية قبل الثقة بـ برك معهم. جلسات، cache وطوابير تعيش في Valkey مُدار، logs تذهب إلى stderr، وتحميلات، يشمل student written-answer PDFs، تذهب إلى Spaces object storage. في 7 أكتوبر تحققنا: backend nodes كتبت 0 ملفات إلى disk في 24 ساعة. هذا لماذا أي عقدة يمكن حذفها في أي وقت.
كل backend node يشغل فقط web (nginx) و app (php-fpm). طوابير، المجدول، خادم WebSocket و OMR مُطفأة على عقد ويب، لذا خوادم إضافية أبداً لا تشغل job مرتين. كلهم يعيشون على العامل الثابت الوحيد خارج كلا البركتين، نقطة فشل مفردة معروفة أن الجزء 4 عود لها.
فحوصات صحة و draining
على backend، /lb-health أجيب من nginx وحده ولا يبوّت الإطار، لذا فحص صحة لا يمكن أن يكون محبطاً من قبل PHP أو قاعدة البيانات. هو أيضاً يعطينا خيار drain. لأخذ عقدة خارج الخدمة، نجعل /lb-health إرجاع 503. load balancer توقف إرسال طلبات جديدة إليها ضمن حول 30 ثانية، وطلبات بالفعل جارية تنهي عادة.
اختبار autoscale والكذبة خمس دقائق
في 5 أكتوبر، بين 21:17 و 21:33، اختبرنا autoscaling نفسه. k6 أعاد تشغيل خليط طلب ذروة امتحان حقيقي، طلبات GET فقط لذا لا شيء كُتب: 300 إلى 450 طلب في الثانية، بعدها حول 750 طلب في الثانية لـ 6 دقائق.
ما راقبناه
النتيجة
Backend pool
2 إلى 3 عقدة في 21:29، عندما وصل متوسط البركة إلى 75%
Frontend pool
2 إلى 3 عقدة في 21:30، في 71%
أخطاء و timeouts
0 خطأ خادم، 0 timeouts؛ عقد جديدة انضمت load balancers بنفسها
P95 شاملة
132 إلى 153 ملسة
API p95
320 إلى 705 ملسة، بينما backend جلس بالقرب 90% قبل عقدة ثالثة انضمت
HTTP 429
حول 4%: كل الحمل جاء من IP اختبار واحد، لذا حدود per-IP فعلت وظيفتهم
انظر إلى API p95، 320 إلى 705 ملسة. هذا سعر الانتظار. Backend جلس قرب 90% CPU حتى عقدة ثالثة انضمت، لأن CPU metric DigitalOcean يتخلف عن الحمل الحقيقي بـ 5 إلى 8 دقائق. في اختبار backend السابق، عقدة إضافية أولى ظهرت حول 7 دقائق بعد وصول CPU إلى 99%. autoscaler ليس مكسور. هو يقرأ أخبار قديمة.
التأخر يعمل في الاتجاه الآخر أيضاً. عندما الحمل توقف، metric لا يزال بدا عالي، و backend اختصر بـ إلى 4 عقدة. إنها صنبور دش: تلف الصنبور أبعد لأن لا شيء تغير بعد، ثم الماء الساخن يصل دفعة واحدة.
الحمل خطوة، metric منحنى بطيء، و autoscaler يتبع المنحنى. رسم تخطيطي من التأثير، ليس تسجيل.
الحجة العامة في Autoscaling for Traffic Spikes؛ هنا لها أرقام خاصة بنا خلفها. لـ امتحان مجدول نرفع الحد الأدنى حول 45 دقيقة قبل وننخفض فقط بعده. Autoscaling تبقى على كالشبكة الأمان للغير مخطط. Status من الاختبار: Tested.
Deploy بـ استبدال، أبداً بـ تحرير
عقد برك قابلة للتصرف، لذا لا أحد يحرر واحد يدويّاً: سيختفي في next scale-in، والعقدة الجديدة التالية ستبوّت من snapshot، ليس من التحرير. تدفقنا:
غيّر عقدة واحدة واختبرها.
خذ snapshot من ذلك.
وجّه template البركة إلى snapshot.
DigitalOcean ينشئ عقد جديدة، بعدها يحذف القديمة.
نحتفظ بـ آخر 3 صور جيدة لـ rollback و أبداً لا نرول template أثناء ساعات امتحان. نمرر كل إعداد على كل تحديث برك، لذا لا شيء silently يعيد ضبط. حد droplet للحساب من 25 أيضاً كان يجب أن يُرفع قبل كلا البركتين قد تصل أقصى قيمتهما أثناء rollout، عندما عقد قديمة وجديدة موجودة معاً.
Blip 503، والـ rollout محرس
backend rollout الأول كان تغيير template عادي، وهو أنتج burst قصير من أخطاء 503. DigitalOcean حذفت droplets القديمة بينما load balancer كان لا يزال توجيه طلبات إليها. إنها مثل إغلاق غرفة الطعام القديمة بينما المضيف لا يزال يجلس الضيوف هناك.
الإصلاح كان توقف الاعتماد على ترتيب الموفر وتشغيل rollout كسلسلة محروسة. الشكل 2 يظهره.
عقد القديمة تُفرّغ قبل حذفها، لذا load balancer لا يرسل طلب إلى عقدة عن وشك الاختفاء.
غيّر فقط image على template البركة.
انتظر حتى كل عقدة جديدة تجيب /lb-health.
انتظر حول 40 ثانية لـ load balancer لـ تعترف بهم.
فرّغ عقد القديمة أولاً: /lb-health الخاصة بهم ترجع 503.
DigitalOcean يزيل عقد القديمة بعد cooldown، مع لا شيء متبقي للخدمة.
Rollout كامل لاحقاً من كلا البركتين، في 7 أكتوبر، تشغل uptime probe كل 2 ثانية على صفحات حقيقية. النتيجة: 219 probes و 0 أخطاء. Status: Tested, then fixed.
Swap و hardening على كل عقدة
الذاكرة كانت الخطر الهادي على frontend. تلك العقد لم يكن لها swap، وكل حاوية قد تستخدم حتى 1.5 GB من 4 GB عقدة. في 7 أكتوبر أضفنا ملف 2 GB swap مع swappiness 10، لذا kernel يفضل RAM، و طهيناه في image frontend. Backend nodes كانت لديها 4 GB swap بالفعل. مجموعة التغييرات نفسها غطت hardening على كل عقدة.
المجال
ما في المكان
الدخول
Key-only SSH و Fail2Ban على كل العقد
الشبكة
Cloud firewalls بـ وسم: عقد frontend تقبل منفذ 80 فقط من load balancer الخاص بهم؛ عقد backend تقبل 80 و 443 فقط من الخاص بهم
العمليات
الحاويات تشغل عمليات طلب خدمة الخاصة بهم كمستخدمين غير root
الأسرار
ملفات env سرية مع mode 600
قاعدة البيانات
مستخدم التطبيق لديه فقط SELECT، INSERT، UPDATE و DELETE، بدون DDL
Firewalls تتبع الوسم، لذا عقدة البركة ينشئ وسط الليل يحصل على قواعده اللحظة موجودة. لا أحد يجب أن يتذكر أي شيء. Status: Implemented في 7 أكتوبر.
ما تعلمنا
إضافة خوادم ليست معمارية. المحدد و Valkey احتاجا تغيير كود و تصحيح طبقة بيانات؛ عقد إضافية فقط كانت نسخ المشكلة.
لا warm pool؟ بناء الدفء نفسك. طاقة احتياطية تخدم بالفعل، زائد رفع الحد الأدنى حول 45 دقيقة قبل امتحان كبير.
Pre-scale لـ امتحانات مجدولة. Metric CPU يتخلف بـ 5 إلى 8 دقائق، لذا autoscaling الفورية الشبكة الأمان فقط، وليس الخطة.
اجعل عقد قابلة للتصرف. عقد ويب بدون حالة، وسم بدلاً من IDs، استبدال بـ image بدلاً من تحرير.
فرّغ قبل حذف. Rollout محروس إجراء، ليس أمل: 219 probes، 0 أخطاء.
احتفظ بـ الشيء القديم حتى الحركة الخاصة به غادرت. TTL طلب؛ ساعة بعد الخادم القديم لا يزال كانت 36%.
قاعدة واحدة أكثر، تعلمت على مساء امتحان حقيقي و أخبرت في جزء لاحق: scale-in لا يفرّغ نفسه.
حيث كل قطعة تقف
قطعة
Status
اثنين load balancers، اثنين autoscale pools، golden-image deploys، swap و hardening
Implemented
Autoscale test في حول 750 طلب/س؛ أول rollout و blip 503 الخاص به
Tested, then fixed
Load balancer واحد لكلا الطبقتين
Rejected
Warm standby في ثوان؛ Kubernetes (DOKS)
Considered؛ DOKS لـ لاحقاً
الأصول الثابتة من CDN؛ Fluent Bit على عقد frontend، الذي nginx logs تختفي مع العقدة
Planned
طبقة التطبيق الآن تستطيع النمو و استبدال بدون أحد يلاحظ. السؤال التالي هو هل إجابة الطالب تنجو: قاعدة البيانات. في الجزء 3 نتحرك MySQL في نافذة مخطط 26 دقيقة، ونقابل لوحة الإدارة القديمة التي بقيت كتب إلى قاعدة البيانات الخاطئة.