تحتوي هذه المقالة على ضغط قصير من تجربتي الخاصة وتجارب زملائي الذين كنت أقاتل معهم ليل نهار. والعديد من الحوادث لم تكن لتحدث لو تم كتابة كل هذه الخدمات المصغرة التي نحبها كثيرًا على الأقل بعناية أكبر.
لسوء الحظ ، يعتقد بعض المبرمجين بجدية أن Dockerfile مع أي فريق على الإطلاق في الداخل هو microservice في حد ذاته ويمكن نشرها حتى الآن. يعمل عمال الميناء - الأموال واردة. يتحول هذا النهج إلى مشاكل تبدأ من تدهور الأداء ، وعدم القدرة على تصحيح الأخطاء ، وفشل الخدمة وتنتهي في كابوس يسمى عدم تناسق البيانات.
إذا كنت تشعر أن الوقت قد حان لإطلاق تطبيق آخر في Kubernetes / ECS / مهما كان الأمر ، فأنا لدي شيء أعترض عليه.
إخلاء المسئولية: هذه ترجمة لمشاركاتي الأصلية . اللغة الإنجليزية ليست لغتي المشتركة. من فضلك ، ساعدني في تحسين نفسي. إذا رأيت خطأ ، فلا تتردد في إظهاره لي ، سأكون ممتنًا.
لقد شكلت لنفسي مجموعة معينة من المعايير لتقييم جاهزية التطبيقات للإطلاق في الإنتاج. لا يمكن تطبيق بعض العناصر في قائمة التحقق هذه على جميع التطبيقات ، ولكن فقط على التطبيقات الخاصة. البعض الآخر ينطبق عموما على كل شيء. أنا متأكد من أنه يمكنك إضافة الخيارات الخاصة بك في التعليقات أو النزاع على أي من العناصر المتعلقة بالألغام.
إذا لم تفي الخدمة المصغرة الخاصة بك بأحد المعايير على الأقل ، فلن أسمح لك بوضعها في مجموعتي المثالية ، التي بنيت في مستودع بطول 2000 متر تحت سطح الأرض بأرضيات مدفئة (نعم ، ستكون ساخنة) وذاتية مغلقة غير كافية نظام الإنترنت.
هنا يذهب! ..
ملاحظة: ترتيب العناصر لا يهم. على الأقل بالنسبة لي.
وصف قصير في الملف التمهيدي
يحتوي على وصف قصير لنفسه في بداية Readme.md في مستودع git الخاص به.
الله ، يبدو بسيطا جدا. لكن كم مرة تعرّفت إلى أن المستودع لا يحتوي على شرح موجز عن سبب الحاجة إليه ، والمهام التي يحلها ، وما إلى ذلك. وأنا لا أتحدث عن شيء أكثر تعقيدًا ، مثل خيارات التكوين.
التكامل مع نظام الرصد
إرسال المقاييس إلى DataDog و NewRelic و Prometheus ، إلخ.
تحليل استهلاك الموارد ، وتسريبات الذاكرة ، وتراكم العناصر ، وترابط الخدمة ، ومعدل الخطأ ، وما إلى ذلك ... من الصعب للغاية التحكم في ما يحدث في تطبيق كبير موزع دون فهم كل هذا.
تمت تهيئة التنبيهات التي تغطي جميع المواقف القياسية ، بالإضافة إلى المواقف الفريدة المعروفة.
المقاييس جيدة ، لكن لن يتبعها أحد يدويًا. لذلك ، نتلقى المكالمات تلقائيًا / pushes / text إذا:
- زاد استهلاك وحدة المعالجة المركزية / الذاكرة بشكل كبير.
- زادت حركة المرور / انخفضت بشكل كبير.
- لقد تغير عدد المعاملات التي تتم معالجتها في الثانية بشكل مشرق في أي اتجاه.
- لقد تغير حجم قطعة أثر النشر بشكل ملحوظ (exe ، app ، jar ، ...).
- تجاوزت نسبة الأخطاء أو تواترها الحد المسموح به.
- توقفت الخدمة عن إرسال المقاييس (غالبًا ما يتم تجاهل الموقف).
- يتم كسر انتظام بعض الأحداث المتوقعة (لا تعمل وظيفة cron ، ولا تتم معالجة جميع الأحداث ، وما إلى ذلك)
- ...
Runbooks التي تم إنشاؤها
تم إنشاء مستند للخدمة يصف المواقف غير المعروفة المعروفة أو المتوقعة.
- كيفية التأكد من أن الخطأ داخلي ولا يعتمد على جهة خارجية ؛
- إذا كان ذلك يعتمد: أين ، إلى من وماذا يكتب ؛
- كيفية إعادة تشغيله بأمان ؛
- كيفية استعادة من نسخة احتياطية وأين هم ، والنسخ الاحتياطي ؛
- ما هي لوحات المعلومات / استعلامات البحث الخاصة التي تم إنشاؤها لمراقبة هذه الخدمة ؛
- هل لدى الخدمة لوحة إدارة خاصة بها وكيف تصل إلى هناك ؛
- هل هناك API / CLI وكيفية استخدامه لإصلاح المشكلات المعروفة ؛
- و هكذا.
يمكن أن تكون القائمة مختلفة تمامًا في المنظمات المختلفة ، ولكن على الأقل يجب أن تكون الأشياء الأساسية موجودة.
تتم كتابة جميع السجلات إلى STDOUT / STDERR
لا تنتج الخدمة أي ملفات سجل في وضع الإنتاج ، ولا ترسلها إلى أي خدمات خارجية ، ولا تحتوي على أي تجريدات زائدة عن الحاجة مثل تدوير السجل ، إلخ.
عندما ينشئ تطبيق ملفات ذات سجلات - تكون هذه السجلات عديمة الفائدة. لن تقفز إلى 5 حاويات تعمل على التوازي ، على أمل اللحاق بالخطأ الضروري من "الذيل -f" (نعم ، سوف تبكي على الهرب ...). ستؤدي إعادة تشغيل الحاوية إلى فقد سجلاتها تمامًا.
إذا كتب تطبيق ما سجلات إلى نظام تابع لجهة خارجية ، على سبيل المثال في Logstash ، فإن هذا يخلق تكرارًا غير مجدي. لا تستطيع الخدمة المجاورة القيام بذلك لأنها تستند إلى إطار آخر؟ ستحصل على حديقة للحيوانات.
يكتب التطبيق جزءًا من سجلاته إلى الملفات ، وجزء آخر إلى STDOUT لأن المطور يريد أن يرى INFO في وحدة التحكم ولكن DEBUG في الملفات؟ هذا هو عموما الخيار الأسوأ. لا أحد يحتاج إلى هذا التعقيد ويحافظ على الكود والتكوينات الإضافية التي يجب على المرء أن يتعلمها أولاً.
سجلات يعني json
كل سطر من السجل مكتوب بتنسيق Json ويحتوي على مجموعة متفق عليها من الحقول.
الجميع لا يزال يكتب سجلات في نص عادي. هذه كارثة حقيقية. سأكون سعيدًا أبدًا بمعرفة أنماط Grok . أحلم بها أحيانًا وأنا أتجمد ، وأحاول ألا أتحرك ، حتى لا تجذب انتباههم. فقط حاول مرة واحدة تحليل استثناءات Java باستخدام Logstash وتشعر بالألم.
Json نعمة ، إنها نار من السماء. فقط أضف هناك:
- الطابع الزمني بالمللي ثانية وفقًا لـ RFC 3339 ؛
- المستوى: معلومات ، تحذير ، خطأ ، تصحيح
- user_id ؛
- app_name
- وغيرها من المجالات.
قم بتحميلها على أي نظام مناسب (على سبيل المثال ، ميزة البحث المرن (FlexSearch) ، واستمتع بها. قم بتوصيل سجلات العديد من الخدمات المصغرة وشعر مرة أخرى بما كانت عليه التطبيقات المتجانسة.
(يمكنك أيضًا إضافة معرف طلب والحصول على تتبع ...)
سجلات مع مستويات اللامع
يجب أن يدعم التطبيق متغير بيئة ، على سبيل المثال LOG_LEVEL ، مع وجود خيارين على الأقل: ERRORS و DEBUG.
من المستحسن أن تدعم جميع الخدمات في نفس النظام البيئي نفس متغير البيئة. ليس خيار التكوين ، وليس خيار سطر الأوامر (على الرغم من أنه يمكن لف هذا بالطبع) ، ولكن مباشرة من متغير البيئة بشكل افتراضي. يجب أن تكون قادرًا على الحصول على أكبر عدد ممكن من السجلات إذا حدث خطأ ما وأقل عدد ممكن من السجلات إذا كان كل شيء على ما يرام.
إصدارات مؤمنة من التبعيات
يتم إصلاح التبعيات لمديري الحزم ، بما في ذلك الإصدارات الثانوية (على سبيل المثال ، cool_framework = 2.5.3). الأقفال التي ارتكبت هي أيضا وسيلة جيدة للقيام بذلك.
وقد سبق ذكر ذلك كثيرًا ، بالطبع. يقوم شخص ما بتأمين التبعيات على إصداراتها الرئيسية ، على أمل أنه في الإصدارات الثانوية سيكون هناك إصلاحات للأخطاء وإصلاحات أمان فقط. إنه خطأ.
مرسى
يحتوي المستودع على Dockerfile جاهز للإنتاج و docker-compose.yml
أصبح عامل الميناء لفترة طويلة معيارًا للعديد من الشركات. هناك استثناءات ، ولكن حتى إذا لم يكن لديك عامل إنتاج في Docker ، فلا يزال بإمكان أي مهندس أن يكون قادرًا على تشغيل "عامل إنشاء" وألا يفكر في أي شيء آخر للحصول على التطوير للتحقق المحلي. ويجب أن يكون لدى مسؤول النظام قطعة أثرية تم التحقق منها بالفعل من قِبل المطورين بالإصدارات الصحيحة من المكتبات ، والأدوات المساعدة ، وما إلى ذلك ، حيث يعمل التطبيق بطريقة ما على الأقل لتكييفه مع الإنتاج.
التكوين عبر البيئة
تتم قراءة جميع خيارات التكوين المهمة من البيئة ، وتتمتع البيئة بأولوية أعلى على ملفات التكوين (ولكن أقل من وسيطات سطر الأوامر).
لن يرغب أحد في قراءة ملفات التكوين الخاصة بك ودراسة تنسيقها. فقط اقبلها.
مزيد من التفاصيل هنا: https://12factor.net/config
تحقيقات والاستعداد الصلاحيات
يحتوي على نقاط النهاية أو أوامر cli المناسبة لاختبار الاستعداد لخدمة الطلبات عند بدء التشغيل وأثناء دورة الحياة
إذا كان أحد التطبيقات يخدم طلبات HTTP ، فيجب أن يكون له واجهاتان افتراضيًا (من الممكن أيضًا إجراء اختبارات CLI):
للتحقق من أن التطبيق على قيد الحياة وليس عالقًا ، يتم استخدام مسبار الثبات. إذا لم يستجب التطبيق ، يمكن إيقافه تلقائيًا بواسطة أوركسترا مثل Kubernetes. بصراحة ، يمكن أن يؤدي قتل تطبيق معلق إلى ظهور تأثير الدومينو وإيقاف الخدمة بأكملها نهائيًا. لكن هذه ليست مشكلة مطور ، ما عليك سوى تحديد نقطة النهاية هذه وتبديل هاتفك إلى وضع الطيران.
للتحقق من أن التطبيق لم يبدأ للتو ، ولكنه جاهز لقبول الطلبات ، يمكن إجراء اختبار الجاهزية. إذا قام أحد التطبيقات بتأسيس اتصال بقاعدة بيانات ونظام قائمة انتظار وما إلى ذلك ، فيجب أن يستجيب بحالة من 200 إلى 400 (لـ Kubernetes).
حدود الموارد
يحتوي على حدود للذاكرة ووحدة المعالجة المركزية ومساحة القرص وأي موارد أخرى متاحة بالتنسيق المتفق عليه.
سيكون التنفيذ الملموس لهذه النقطة مختلفًا تمامًا في المنظمات المختلفة وللأوركسترا المختلفين. ومع ذلك ، يجب تهيئتها خصيصًا لجميع البيئات المتاحة (prod ، و dev ، و test ، ...) وتخزينها في الخارج من git repo مع رمز التطبيق .
يبني الآلي والتسليم
تم تكوين نظام CI / CD المستخدم في مؤسستك أو مشروعك وقادر على تسليم التطبيق إلى البيئة المطلوبة وفقًا لسير العمل المقبول.
لا يتم تسليم أي شيء إلى الإنتاج يدويًا.
بغض النظر عن مدى صعوبة تنفيذ الإنشاءات وتسليم التطبيق الخاص بك تلقائيًا ، يجب القيام بذلك قبل بدء تشغيل هذا المشروع. يتضمن هذا العنصر بناء وتنفيذ كتب الطبخ Ansible / Chef / Salt / ... ، بناء التطبيقات للأجهزة المحمولة ؛ الشوك من نظام التشغيل. صور من الأجهزة الافتراضية ، أيا كان.
من الصعب أتمتة هذا؟ لا يمكنك جلب هذا إلى العالم بعد ذلك. لن يتمكن أي شخص من بناء هذا يدويًا مرة أخرى بعد المغادرة.
إغلاق رشيقة
يفهم التطبيق SIGTERM والإشارات الأخرى وسيغلق نفسه بأمان بعد معالجة المهمة الحالية.
هذا عنصر مهم للغاية. أصبحت عمليات إرساء الأيتام وتستمر في العمل لعدة أشهر في الخلفية ، حيث لا يراها أحد. تنتهي العمليات غير المتعلقة بالمعاملات في منتصف التنفيذ ، مما يؤدي إلى عدم تناسق البيانات بين الخدمات وبين مخازن البيانات. هذا يؤدي إلى أخطاء لا يمكن التنبؤ بها ويمكن أن تكون مكلفة للغاية.
إذا لم تكن قادرًا على التحكم في بعض تبعيات الطرف الثالث ولا يمكنك ضمان أن الكود الخاص بك سيتعامل مع SIGTERM بشكل صحيح ، فاستخدم شيئًا مثل dumb-init .
مزيد من المعلومات هنا:
يتم فحص اتصال قاعدة البيانات بانتظام
يدقق التطبيق باستمرار في قاعدة البيانات ويتفاعل تلقائيًا مع استثناء "فقد الاتصال" الذي يظهر من اختبار ping أو من أي استفسارات أخرى ، في محاولة لاستعادته من تلقاء نفسه أو إكمال عمله بشكل صحيح.
لقد رأيت الكثير من الحالات (ليس مجرد رقم من الكلام) عندما فقدت الخدمات التي تم إنشاؤها لمعالجة قوائم الانتظار أو الأحداث التي تعمل بمثابة شياطين اتصال عن طريق المهلة وبدأت بلا حدود في كتابة الأخطاء للسجلات ، وإعادة الرسائل إلى قائمة الانتظار الأصلية ، وإرسالها إلى قائمة انتظار الرسائل الميتة أو ببساطة لا تقوم بعملها.
تحجيم أفقيا
مع نمو التحميل ، يكفي تشغيل المزيد من مثيلات التطبيق لضمان معالجة جميع الطلبات أو المهام.
ليس كل التطبيقات يمكن تحجيمها أفقيا. مثال جيد على ذلك هو " كافكا كونسومرز" ، وهذا ليس بالأمر السيئ بالضرورة ، لكن إذا لم يكن من الممكن إطلاق تطبيق معين مرتين ، فإن جميع المهتمين بحاجة إلى معرفة ذلك مسبقًا. يجب أن تكون هذه المعلومات مدمرة للعين ، ومنسقة بخط عريض في المستند التمهيدي وتضاف كلما كان ذلك ممكنًا. بعض التطبيقات بطبيعتها لا يمكن إطلاقها بالتوازي تحت أي ظرف من الظروف ، مما يخلق صعوبات خطيرة في الحفاظ عليها.
من الأفضل بكثير إذا كان التطبيق نفسه يتحكم في هذه المواقف أو إذا كان هناك غلاف مكتوب له ، والذي يراقب بفعالية "المنافسين" ويمنع العملية ببساطة من بدء أو بدء عملهم حتى تكمل بعض العمليات الأخرى الخاصة بها أو حتى تسمح بعض التهيئة الخارجية عمليات N للعمل في وقت واحد.
طوابير الحروف الميتة ومقاومة الرسائل "السيئة"
إذا كانت الخدمة تستمع إلى قوائم الانتظار أو تستجيب للأحداث ، فإن تغيير شكل الرسائل أو محتواها لا يؤدي إلى سقوطها. تتكرر المحاولات الفاشلة لمعالجة المهمة مرات N ، وبعدها يتم إرسال الرسالة إلى "قائمة الرسائل الميتة".
في كثير من الأحيان رأيت المستهلكين الذين تم إعادة تشغيلهم إلى ما لا نهاية ، وتضخمت قوائم الانتظار إلى هذا الحجم الكبير بحيث استغرقت معالجتهم اللاحقة عدة أيام. يجب أن يكون أي مستمع لقائمة الانتظار جاهزًا لتغييرات التنسيق أو الأخطاء العشوائية في الرسالة نفسها (أنواع البيانات في json ، على سبيل المثال) أو أثناء معالجة الرسالة بواسطة الرمز الفرعي. لقد واجهت حتى موقفًا حيث لم تدعم المكتبة القياسية لـ RabbitMQ في إطار عمل شائع للغاية عمليات إعادة المحاولة على الإطلاق ، عدادات المحاولات ، إلخ. ولم يكن هناك طريقة سهلة لتوسيع منطقه.
والأسوأ من ذلك بكثير عندما يتم تدمير الرسالة ببساطة في حالة الفشل.
عدد محدود من الرسائل والمهام التي تمت معالجتها بواسطة مثيل واحد
وهو يدعم متغير بيئة يمكن ضبطه عند الضرورة للحد من الحد الأقصى لعدد المهام التي تتم معالجتها ، وبعد ذلك سيتم إيقاف تشغيل الخدمة بأمان.
باستمرار زيادة استهلاك الذاكرة و "OOM قتل" في النهاية هو قاعدة الحياة لعقول Kubernetic الحديثة. تنفيذ فحص بدائي من شأنه أن يوفر لك فقط الحاجة إلى فحص كل هذه التسريبات في الذاكرة من شأنه أن يجعل الحياة أسهل. لقد رأيت في كثير من الأحيان أن الناس يقضون الكثير من الوقت والجهد (والمال) لإيقاف هذه التسريبات ، لكن لا توجد ضمانات بأن الالتزام التالي لزميلك في العمل لن يجعل الأمور أسوأ. إذا كان التطبيق يمكن أن البقاء على قيد الحياة لمدة أسبوع ، وهذا هو مؤشر ممتاز. اتركه ثم توقف عن نفسه ببساطة وسيتم إعادة تشغيله. هذا أفضل من SIGKILL (حول SIGTERM ، انظر أعلاه) أو استثناء "نفاد الذاكرة". لبضعة عقود ، يعد هذا الحل كافيًا لمعظم الحالات.
غير مؤمن من قبل طرف ثالث التكامل مع عنوان IP القائمة البيضاء
إذا قام أحد التطبيقات بتقديم طلبات إلى خدمة تابعة لجهة خارجية تسمح بطلبات من عناوين IP محدودة فقط ، فإن الخدمة تقدم هذه الطلبات بشكل غير مباشر (على سبيل المثال ، من خلال وكيل عكسي).
هذه حالة نادرة ، لكنها غير سارة للغاية. يكون غير مريح للغاية عندما يمنع تطبيق صغير إمكانية تغيير شبكة نظام المجموعة أو الانتقال إلى منطقة أخرى من البنية التحتية بأكملها. إذا كنت بحاجة إلى التواصل مع شيء لا يلعب مع oAuth أو VPN ، فقم بإعداد بروكسي عكسي مقدمًا. لا تنفذ في برامجك الإضافة / الحذف الديناميكي للتكاملات الخارجية لهذه الأغراض التي تستخدم أسماء مضيفات الجهات الخارجية وما إلى ذلك ، لأنك بذلك ستلتزم ببيئة التشغيل الوحيدة المتاحة. من الأفضل أن تبدأ من أتمتة هذه العمليات لإدارة ، على سبيل المثال ، التكوينات Nginx أو بعض ، وفي التطبيق الخاص بك ، الرجوع إليها.
واضح وكيل المستخدم HTTP
تحل الخدمة محل رأس وكيل المستخدم برقم مخصص لجميع الطلبات لأي من واجهات برمجة التطبيقات ، ويحتوي هذا الرأس على معلومات كافية حول الخدمة نفسها وإصدارها.
عندما يكون لديك 100 تطبيق مختلف يتواصلون مع بعضهم البعض ، يمكنك أن تصاب بالجنون ، ورؤية في سجلات شيء مثل "Go-http-client / 1.1" وعنوان IP الديناميكي لحاوية Kubernetes. حدد دائمًا طلبك وإصداره بشكل صريح.
لا ينتهك الترخيص
لا يحتوي على تبعيات تحد من التطبيق وليست نسخة من رمز شخص آخر ، وما إلى ذلك.
هذه حالة بديهية ، لكنني رأيت أشياء حتى أن المحامي الذي كتب NDA قد حصل على الفواق الآن.
لا يستخدم التبعيات غير المدعومة.
عند بدء تشغيل الخدمة لأول مرة ، لا يشمل التبعيات التي انتهت صلاحيتها بالفعل.
إذا لم يعد أي شخص يدعم المكتبة التي أخذتها في المشروع - فابحث عن طريقة أخرى لتحقيق الهدف أو ابدأ في دعم المكتبة نفسها.
الخاتمة
في قائمتي ، هناك بعض الاختبارات المحددة للغاية لتقنيات أو مواقف محددة ، أو ربما نسيت فقط إضافة شيء ما. أنا متأكد من أنك تعرف أيضًا ما يجب تضمينه فيه.