تصحيح أخطاء تطبيقات Golang المحملة بشكل كبير أو كيف بحثنا عن مشكلة في Kubernetes لم تكن موجودة

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

صورة

اسمي فيكتور ياجوفاروف ، أقوم بتطوير سحابة Kubernetes في DomKlik ، وأريد اليوم أن أتحدث عن كيفية حل المشكلة مع أحد المكونات الرئيسية لمجموعة إنتاج K8s (Kubernetes).

في مجموعتنا القتالية (وقت كتابة هذا التقرير):

  • تم إطلاق 1890 قرنة و 577 خدمة (عدد الخدمات الدقيقة الحقيقية موجود أيضًا في منطقة هذا الشكل)
  • وحدات التحكم - Ingress تخدم حوالي 6k RPS وحوالي نفس المبلغ يمر من قبل Ingress مباشرة إلى hostPort .


مشكلة


منذ بضعة أشهر ، بدأت حواملنا في مواجهة مشكلة في حل أسماء DNS. والحقيقة هي أن DNS يعمل بشكل أساسي على UDP ، وفي نواة Linux هناك بعض المشاكل مع conntrack و UDP. DNAT عند الوصول إلى عناوين خدمة K8s Service يؤدي فقط إلى تفاقم المشكلة مع سباقات conntrack . تجدر الإشارة إلى أنه في مجموعتنا وقت حدوث المشكلة كان هناك حوالي 40 ألف RPS نحو خوادم DNS ، CoreDNS.

صورة

تقرر استخدام خادم DNS الخاص بالتخزين المؤقت المحلي NodeLocal DNS (nodelocaldns) الذي تم إنشاؤه خصيصًا من قبل المجتمع على كل عقدة عامل في المجموعة ، والتي لا تزال في مرحلة تجريبية ومصممة لحل جميع المشكلات. باختصار: تخلص من UDP عند الاتصال بـ DNS للكتلة ، وإزالة NAT ، وإضافة طبقة ذاكرة تخزين مؤقت إضافية.

في الإصدار الأول من تطبيق nodelocaldns استخدمنا الإصدار 1.15.4 (لا يجب الخلط بينه وبين إصدار المكعب ) ، والذي جاء مع «kubernetes-installer» Kubespray - نحن نتحدث عن شركتنا Fork fork من Southbridge.

على الفور تقريبًا بعد المقدمة ، بدأت المشاكل: تدفق الذاكرة وإعادة الموقد وفقًا لحدود الذاكرة (OOM-Kill). في وقت إعادة تشغيل هذا ، تم فقد كل حركة المرور على المضيف ، لأنه في جميع الأقراص /etc/resolv.conf أشار بالضبط إلى عنوان IP الخاص بـ nodelocaldns.

من المؤكد أن هذا الموقف لا يناسب الجميع ، وقد اتخذ فريق OPS لدينا عددًا من الإجراءات للقضاء عليه.

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

نحن نبحث عن حل


إذن هيا بنا نذهب!

تم تنزيل الإصدار 1.15.7 إلى مجموعة dev ، التي تعتبر بالفعل بيتا ، وليس ألفا مثل 1.15.4 ، ولكن البكر ليس لديه مثل هذه الحركة في DNS (40k RPS). شيء محزن.

في هذه العملية ، قمنا بربط nodelocaldns من Kubespray وكتبنا مخططًا خاصًا لـ Helm من أجل طرح أكثر ملاءمة. في الوقت نفسه ، قاموا بكتابة كتاب تشغيل لـ Kubespray ، والذي يسمح لك بتغيير إعدادات kubelet دون هضم حالة الكتلة بالكامل بالساعة ؛ علاوة على ذلك ، يمكن القيام بذلك بشكل نقطي (التحقق أولاً على عدد صغير من العقد).

بعد ذلك ، طرحنا إصدار nodelocaldns 1.15.7 إلى prod. للأسف ، تكرر الوضع. كانت الذاكرة تتدفق.

يحتوي مستودع nodelocaldns الرسمي على نسخة تحمل علامة 1.15. 8، ولكن لسبب ما لم أستطع جعل عامل الميناء يسحب هذا الإصدار واعتقد أنني لم أقم بعد بتجميع صورة Docker الرسمية ، لذلك لا ينبغي استخدام هذا الإصدار. هذه نقطة مهمة وسنعود إليها.

التصحيح: المرحلة 1


لفترة طويلة ، لم أتمكن من معرفة كيفية تجميع إصداري من nodelocaldns من حيث المبدأ ، حيث تحطم ملف Makefile من اللفت بأخطاء غير مفهومة من داخل صورة عامل الميناء ، ولم أفهم حقًا كيفية إنشاء مشروع Go ببراعة مع govendor ، والذي تم ترتيبه بطرق غريبة في الدلائل على الفور للعديد من خيارات خادم DNS المختلفة. الشيء هو أنني بدأت في تعلم Go عندما ظهرت بالفعل إصدارات التبعية العادية من خارج منطقة الجزاء .

ساعدني بافيل سيليفانوف كثيرًا في حل المشكلة.بولجام، والتي بفضله الكثير. تمكنت من تجميع نسختي.

بعد ذلك ، قمنا بلف ملف تعريف pprof ، واختبرنا التجميع على البكر وطرحناه في المنتج .

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

للوهلة الأولى ، استنادًا إلى ناتج المحلل ، كانت العملية تسير على ما يرام - تم تخصيص معظم الذاكرة على المكدس ، وعلى ما يبدو ، تم استخدامها باستمرار من قبل إجراءات Go .

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

صورة

التصحيح: المرحلة 2


أصبح من المثير للاهتمام لماذا يحدث هذا (الخيوط تتدفق) ، وبدأت المرحلة التالية من دراسة عملية nodelocaldns. أظهر الفحص

الثابت لرمز المحلل الثابت أن هناك بعض المشاكل في مرحلة إنشاء خيط في المكتبة ، والتي يتم استخدامها في nodelocaldns (وهي inkluda CoreDNS ، والتي تحبر nodelocaldns'om). كما أفهمها ، في بعض الأماكن لا يتم إرسال مؤشر إلى الهيكل ، ولكن نسخة من قيمهم . تقرر إجراء عملية مرجعية للعملية "السيئة" باستخدام الأداة المساعدة gcore ومعرفة ما بداخلها. عالق في coredump باستخدام أداة dlv مثل gdb



أدركت قوتها ، لكنني أدركت أنني سأبحث عن سبب بهذه الطريقة لفترة طويلة جدًا. لذلك ، قمت بتحميل coredump في IDE Goland وقمت بتحليل حالة ذاكرة العملية.

التصحيح: المرحلة 3


كان من المثير للاهتمام للغاية دراسة بنية البرنامج ، ورؤية الكود الذي ينشئها. في حوالي 10 دقائق ، أصبح من الواضح أن العديد من إجراءات الانتقال تنشئ نوعًا من البنية لاتصالات TCP ، وتمييزها كاذبة ولا تحذفها أبدًا (تذكر حوالي 40 كيلوبايت في الثانية؟).

صورة

صورة

في لقطات الشاشة ، يمكنك رؤية الجزء المثير للمشاكل من التعليمات البرمجية والبنية التي لم يتم مسحها عندما تم إغلاق جلسة UDP.

أيضًا ، من coredump ، أصبح الجاني لهذا العدد من RPS معروفًا بواسطة عناوين IP في هذه الهياكل (شكرًا للمساعدة في العثور على عنق الزجاجة في مجموعتنا :).

القرار


خلال المعركة ضد هذه المشكلة ، وجدت بمساعدة زملاء من مجتمع Kubernetes أن صورة Docker الرسمية لـ nodelocaldns 1.15.8 لا تزال موجودة (وقد أصبت بالفعل في أيدي ملتوية وفعلت بطريقة خاطئة سحب عامل الميناء ، أو كانت WIFI مشاغبة في سحب اللحظة).

في هذا الإصدار ، فإن إصدارات المكتبات التي يستخدمها هي "مستاءة" إلى حد كبير: على وجه التحديد ، "الجاني" "أبطل" حوالي 20 نسخة!

علاوة على ذلك ، فإن الإصدار الجديد يدعم بالفعل التنميط من خلال pprof ويتم تمكينه من خلال Configmap ، لا تحتاج إلى إعادة تجميع أي شيء.

تم تنزيل إصدار جديد أولاً في dev ثم في prod.
ثالثا ... النصر !
بدأت العملية بإعادة ذاكرتها إلى النظام وتوقفت المشاكل.

في الرسم البياني أدناه ، يمكنك رؤية الصورة: "DNS للمدخنين مقابل DNS لشخص سليم ".

صورة

الموجودات


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

روابط مفيدة:

1. www.freecodecamp.org/news/how-i-investigated-memory-leaks-in-go-using-pprof-on-a-large-codebase-4bec4325e192
2 . habr.com/en/company/roistat/blog/413175
3. rakyll.org

All Articles