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

فشل بواسطة Aris-Dreamer



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



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



الخطوة الأولى: إعداد طلبات وحدود الجراب



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



طلبات Pod هي القيمة الأساسية التي يستخدمها المجدول لوضع الكبسولة الأمثل.



Kubernetes: , . , PodFitsResources , .


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



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



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



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


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



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



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



  1. باستخدام أداة اختبار الحمل ، نقوم بمحاكاة حركة المرور الأساسية ومراقبة استخدام موارد البود (الذاكرة والمعالج).
  2. ( 5 ) . , , Go.


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



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



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



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



الحياة إذا كانت الحاوية تعمل. إذا فشلت ، فإن kubelet تقتل الحاوية ، ويتم تمكين سياسة إعادة التشغيل لها. إذا لم تكن الحاوية مزودة بمسبار Liveness ، فستكون الحالة الافتراضية ناجحة - كما هو مذكور في وثائق 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 — , ( , ).


:



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



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



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



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


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





(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)

مزيد من التفاصيل هنا .



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



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



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



بعد بحث مكثف على الإنترنت ، اتضح أن 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 .



All Articles