خمس أخطاء عند نشر التطبيق الأول على Kubernetes

خمس أخطاء عند نشر التطبيق الأول على Kubernetesفشل بواسطة Aris Dreamer

يعتقد الكثير من الناس أنه يكفي نقل التطبيق إلى Kubernetes (إما باستخدام Helm أو يدويًا) - وستكون هناك سعادة. لكن ليس كل شيء بهذه البساطة.

فريق حلول سحابة Mail.ru ترجم مقالاً بقلم مهندس DevOps جوليان جيندي. يروي ما هي المزالق التي واجهتها شركته أثناء عملية الترحيل حتى لا تخطو على نفس أشعل النار.

الخطوة الأولى: إعداد طلبات Pod وقيودها

لنبدأ بإعداد بيئة نظيفة تعمل فيها كبسولاتنا. Kubernetes رائع في جدولة الكبسولات وتجاوز الفشل. ولكن اتضح أن المجدول في بعض الأحيان لا يمكنه وضع حجرة إذا كان من الصعب تقدير عدد الموارد التي يحتاجها للعمل بنجاح. هذا هو المكان الذي تظهر فيه طلبات الموارد والحدود. هناك الكثير من الجدل حول أفضل نهج لوضع الطلبات والحدود. يبدو أحيانًا أن هذا في الحقيقة فن أكثر منه علم. هذا هو نهجنا.

طلبات جراب هي القيمة الرئيسية التي يستخدمها المجدول لوضع الكبسولة على النحو الأمثل.

من وثائق Kubernetes: تحدد خطوة التصفية مجموعة من العقد حيث يمكن جدولة Pod. على سبيل المثال ، يتحقق عامل التصفية PodFitsResources لمعرفة ما إذا كانت العقدة لديها موارد كافية لتلبية طلبات موارد معينة من حجرة.

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

في هذه الحالة ، غالبًا ما يقوم المجدول "بإخراج" البودات ولن يكون قادرًا على إعادة جدولتها لأن مستوى التحكم لم يكن لديه فكرة عن مقدار الموارد التي يحتاجها التطبيق ، والتي تعد عنصرًا رئيسيًا في خوارزمية الجدولة.

حدود جراب هو حد أوضح لحجرة. يمثل الحد الأقصى من الموارد التي سيخصصها الكتلة للحاوية.

مرة أخرى ، من الوثائق الرسمية: إذا كانت الحاوية تحتوي على ذاكرة بحد 4 جيجا بايت ، فإن kubelet (ووقت تشغيل الحاوية) سوف يفرضها. يمنع وقت التشغيل الحاوية من استخدام أكثر من حد الموارد المحدد. على سبيل المثال ، عندما تحاول عملية في حاوية استخدام أكثر من المقدار المسموح به من الذاكرة ، فإن kernel النظام ينهي العملية بخطأ "نفاد الذاكرة" (OOM).

يمكن للحاوية دائمًا استخدام موارد أكثر مما يحدده طلب المورد ، ولكن لا يمكنها أبدًا استخدام أكثر من الحد. يصعب تعيين هذه القيمة بشكل صحيح ، لكنها مهمة جدًا.

من الناحية المثالية ، نريد أن تتغير متطلبات الموارد لحجرة ما خلال دورة حياة العملية دون التدخل في العمليات الأخرى في النظام - وهذا هو الغرض من وضع الحدود.

لسوء الحظ ، لا يمكنني إعطاء تعليمات محددة حول القيم التي يجب تعيينها ، لكننا أنفسنا نلتزم بالقواعد التالية:

  1. باستخدام أداة اختبار الحمل ، نقوم بمحاكاة المستوى الأساسي لحركة المرور ومراقبة استخدام موارد البودات (الذاكرة والمعالج).
  2. اضبط طلبات البود على قيمة منخفضة بشكل تعسفي (بحد موارد يبلغ حوالي 5 أضعاف قيمة الطلبات) وراقبها. عندما تكون الطلبات في مستوى منخفض جدًا ، لا يمكن بدء العملية ، وغالبًا ما تتسبب في حدوث أخطاء مشفرة في وقت تشغيل Go.

ألاحظ أن حدود الموارد الأعلى تجعل الجدولة أكثر صعوبة لأن البود يحتاج إلى عقدة مستهدفة ذات موارد كافية متاحة.

تخيل موقفًا يكون لديك فيه خادم ويب خفيف الوزن بحد موارد مرتفع للغاية ، مثل 4 جيجابايت من الذاكرة. من المحتمل أن تحتاج هذه العملية إلى توسيع نطاقها أفقيًا ، وسيتعين جدولة كل جراب جديد على عقدة بها 4 جيجابايت على الأقل من الذاكرة المتوفرة. في حالة عدم وجود مثل هذه العقدة ، يجب على الكتلة تقديم عقدة جديدة لمعالجة هذا البود ، الأمر الذي قد يستغرق بعض الوقت. من المهم تحقيق الحد الأدنى من الاختلاف بين طلبات الموارد والحدود لضمان تحجيم سريع وسلس.

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

هذا موضوع خفي آخر غالبًا ما تتم مناقشته في مجتمع Kubernetes. من المهم أن يكون لديك فهم جيد لاختبارات الحيوية والجاهزية لأنها توفر آلية للتشغيل المستقر للبرنامج وتقليل وقت التوقف عن العمل. ومع ذلك ، يمكن أن تؤثر بشكل خطير على أداء التطبيق الخاص بك إذا لم يتم تكوينها بشكل صحيح. فيما يلي ملخص لكلتا العينات.

الحياة يظهر ما إذا كانت الحاوية قيد التشغيل. إذا فشلت ، فإن kubelet تقتل الحاوية ويتم تمكين سياسة إعادة التشغيل لها. إذا لم تكن الحاوية مزودة بمسبار الحياة ، فستكون الحالة الافتراضية ناجحة - كما هو مذكور في وثائق Kubernetes.

يجب أن تكون مجسات الحياة رخيصة ، أي لا تستهلك الكثير من الموارد ، لأنها تعمل بشكل متكرر ويجب أن تخبر Kubernetes أن التطبيق قيد التشغيل.

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

في شركتنا ، تختبر اختبارات Liveness المكونات الأساسية للتطبيق ، حتى إذا كانت البيانات (على سبيل المثال ، من قاعدة بيانات بعيدة أو ذاكرة تخزين مؤقت) غير متوفرة بالكامل.

لقد قمنا بإعداد نقطة نهاية "صحية" في التطبيقات التي تقوم ببساطة بإرجاع رمز استجابة 200. هذا مؤشر على أن العملية قيد التشغيل وقادرة على التعامل مع الطلبات (ولكن ليس حركة المرور حتى الآن).

اختبار استعداد يشير إلى ما إذا كانت الحاوية جاهزة لخدمة الطلبات. إذا فشل مسبار الجاهزية ، فإن وحدة التحكم في نقطة النهاية تزيل عنوان IP الخاص بالجراب من نقاط النهاية لجميع الخدمات المطابقة للحجرة. هذا مذكور أيضًا في وثائق Kubernetes.

تستهلك تحقيقات الجاهزية المزيد من الموارد ، حيث يجب أن تصل إلى الواجهة الخلفية بطريقة تُظهر أن التطبيق جاهز لقبول الطلبات.

هناك الكثير من الجدل في المجتمع حول ما إذا كان يجب الوصول إلى قاعدة البيانات مباشرة. بالنظر إلى النفقات العامة (عمليات التحقق متكررة ، ولكن يمكن التحكم فيها) ، قررنا أنه بالنسبة لبعض التطبيقات ، لا يتم احتساب الاستعداد لخدمة حركة المرور إلا بعد التحقق من إرجاع السجلات من قاعدة البيانات. ضمنت تجارب الجاهزية جيدة التصميم مستويات أعلى من التوافر والقضاء على فترات التوقف أثناء النشر.

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

SELECT small_item FROM table LIMIT 1

فيما يلي مثال على كيفية تكوين هاتين القيمتين في Kubernetes:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

يمكنك إضافة بعض خيارات التكوين الإضافية:

  • initialDelaySeconds - كم ثانية ستمر بين إطلاق الحاوية وبدء إطلاق المجسات.
  • periodSeconds - فترة الانتظار بين تشغيل العينة.
  • timeoutSeconds - عدد الثواني التي يتم بعدها اعتبار الكبسولة حالة طارئة. مهلة عادية.
  • failureThreshold هو عدد حالات فشل الاختبار قبل إرسال إشارة إعادة التشغيل إلى الحجرة.
  • successThreshold هو عدد المحاولات الناجحة قبل انتقال البود إلى حالة الاستعداد (بعد حدوث فشل عند بدء تشغيل الكبسولة أو استعادتها).

الخطوة الثالثة: إعداد سياسات الشبكة الافتراضية الخاصة بجهاز Pod

تحتوي Kubernetes على تضاريس شبكة "مسطحة" ، وتتصل جميع البودات بشكل افتراضي مع بعضها البعض بشكل افتراضي. في بعض الحالات هذا غير مرغوب فيه.

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

على سبيل المثال ، ما يلي عبارة عن سياسة بسيطة ترفض كل حركة المرور الواردة لمساحة اسم معينة:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

تصور هذا التكوين:

خمس أخطاء عند نشر التطبيق الأول على Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
مزيد من التفاصيل هنا.

الخطوة الرابعة: السلوك المخصص باستخدام الخطافات والحاويات الأولية

كان أحد أهدافنا الرئيسية هو توفير عمليات النشر في Kubernetes دون توقف للمطورين. يعد هذا أمرًا صعبًا نظرًا لوجود العديد من الخيارات لإغلاق التطبيقات والإفراج عن مواردها المستخدمة.

نشأت صعوبات خاصة مع إنجن إكس. لاحظنا أنه عند نشر هذه الكبسولات بالتسلسل ، انقطعت الاتصالات النشطة قبل اكتمالها بنجاح.

بعد بحث مكثف على الإنترنت ، اتضح أن Kubernetes لا تنتظر اتصالات Nginx حتى تستنفد نفسها قبل إغلاق الكبسولة. بمساعدة خطاف ما قبل التوقف ، قمنا بتنفيذ الوظائف التالية وتخلصنا تمامًا من وقت التوقف:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

لكن nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

نموذج آخر مفيد للغاية هو استخدام حاويات init للتعامل مع إطلاق تطبيقات معينة. هذا مفيد بشكل خاص إذا كان لديك عملية ترحيل قاعدة بيانات كثيفة الموارد يجب تشغيلها قبل بدء التطبيق. يمكنك أيضًا تحديد حد أعلى للموارد لهذه العملية دون تعيين مثل هذا الحد للتطبيق الرئيسي.

هناك مخطط شائع آخر وهو الوصول إلى الأسرار في حاوية init ، والتي توفر بيانات الاعتماد هذه للوحدة النمطية الرئيسية ، مما يمنع الوصول غير المصرح به إلى الأسرار من وحدة التطبيق الرئيسية نفسها.

كالعادة ، اقتباس من الوثائق: حاويات init تقوم بتشغيل كود المستخدم أو الأدوات المساعدة بأمان والتي من شأنها أن تعرض أمن صورة حاوية التطبيق للخطر. من خلال إبقاء الأدوات غير الضرورية منفصلة ، فإنك تحد من سطح الهجوم لصورة حاوية التطبيق.

الخطوة الخامسة: تكوين النواة

أخيرًا ، لنتحدث عن تقنية أكثر تقدمًا.

Kubernetes عبارة عن نظام أساسي مرن للغاية يتيح لك تشغيل أحمال العمل بالشكل الذي تراه مناسبًا. لدينا عدد من التطبيقات عالية الكفاءة التي تتطلب موارد كثيرة. بعد إجراء اختبار تحميل مكثف ، وجدنا أن أحد التطبيقات كان يواجه صعوبة في مواكبة حمل المرور المتوقع عندما كانت إعدادات Kubernetes الافتراضية سارية.

ومع ذلك ، يتيح لك Kubernetes تشغيل حاوية ذات امتيازات لا تغير سوى معلمات kernel لحجرة معينة. إليك ما استخدمناه لتغيير الحد الأقصى لعدد الاتصالات المفتوحة:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

هذه تقنية أكثر تقدمًا وغالبًا ما تكون غير مطلوبة. ولكن إذا كان التطبيق الخاص بك يكافح للتعامل مع الحمل الثقيل ، يمكنك محاولة تعديل بعض هذه الإعدادات. مزيد من المعلومات حول هذه العملية وتحديد قيم مختلفة - كما هو الحال دائمًا في الوثائق الرسمية.

في الختام

على الرغم من أن Kubernetes قد يبدو حلًا جاهزًا ، إلا أن هناك بعض الخطوات الأساسية التي يجب اتخاذها للحفاظ على تشغيل التطبيقات بسلاسة.

خلال عملية الترحيل إلى Kubernetes ، من المهم اتباع "دورة اختبار الحمل": قم بتشغيل التطبيق ، واختباره تحت الحمل ، ومراقبة المقاييس وسلوك القياس ، وضبط التكوين بناءً على هذه البيانات ، ثم كرر هذه الدورة مرة أخرى.

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

اسأل نفسك دائمًا هذه الأسئلة:

  1. كم عدد الموارد التي تستهلكها التطبيقات وكيف سيتغير هذا المقدار؟
  2. ما هي متطلبات القياس الحقيقية؟ ما مقدار حركة المرور التي سيتعامل معها التطبيق في المتوسط؟ ماذا عن ذروة حركة المرور؟
  3. كم مرة ستحتاج الخدمة إلى التوسع؟ ما مدى السرعة المطلوبة لتشغيل البودات الجديدة لاستقبال حركة المرور؟
  4. كيف برشاقة تغلق القرون؟ هل هو ضروري على الإطلاق؟ هل من الممكن تحقيق النشر دون توقف؟
  5. كيف تقلل المخاطر الأمنية وتحد من الضرر الناجم عن أي بودات مخترقة؟ هل أي خدمات لها أذونات أو وصول لا يحتاجون إليها؟

يوفر Kubernetes نظامًا أساسيًا رائعًا يتيح لك استخدام أفضل الممارسات لنشر آلاف الخدمات في مجموعة. ومع ذلك ، فإن كل التطبيقات مختلفة. يتطلب التنفيذ أحيانًا مزيدًا من العمل.

لحسن الحظ ، يوفر Kubernetes الإعدادات اللازمة لتحقيق جميع الأهداف الفنية. باستخدام مجموعة من طلبات الموارد وحدودها ، وتحقيقات الحيوية والجاهزية ، وحاويات init ، ونُهج الشبكة ، وضبط kernel المخصص ، يمكنك تحقيق أداء عالٍ جنبًا إلى جنب مع التسامح مع الخطأ وقابلية التوسع السريع.

ماذا تقرأ:

  1. أفضل الممارسات وأفضل الممارسات لتشغيل الحاويات و Kubernetes في بيئات الإنتاج.
  2. أكثر من 90 أداة مفيدة لـ Kubernetes: النشر والإدارة والمراقبة والأمان والمزيد.
  3. قناتنا حول Kubernetes في Telegram.

المصدر: www.habr.com

إضافة تعليق