انتقال Tinder إلى Kubernetes

ملحوظة. ترجمة.: قام موظفو خدمة Tinder المشهورة عالميًا مؤخرًا بمشاركة بعض التفاصيل الفنية حول ترحيل البنية التحتية الخاصة بهم إلى Kubernetes. استغرقت العملية ما يقرب من عامين وأسفرت عن إطلاق منصة واسعة النطاق جدًا على K8s، تتكون من 200 خدمة مستضافة على 48 ألف حاوية. ما هي الصعوبات المثيرة للاهتمام التي واجهها مهندسو Tinder وما هي النتائج التي توصلوا إليها؟اقرأ هذه الترجمة.

انتقال Tinder إلى Kubernetes

لماذا؟

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

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

تبين أن العملية صعبة. أثناء ترحيلنا في أوائل عام 2019، وصلت مجموعة Kubernetes إلى الكتلة الحرجة وبدأنا نواجه مشكلات مختلفة بسبب حجم حركة المرور وحجم المجموعة ونظام DNS. على طول الطريق، قمنا بحل الكثير من المشكلات المثيرة للاهتمام المتعلقة بترحيل 200 خدمة والحفاظ على مجموعة Kubernetes التي تتكون من 1000 عقدة و15000 حاوية و48000 حاوية قيد التشغيل.

كيف؟

منذ يناير 2018، مررنا بمراحل مختلفة من الهجرة. لقد بدأنا بوضع جميع خدماتنا في حاويات ونشرها في بيئات السحابة التجريبية الخاصة بـ Kubernetes. بدءًا من شهر أكتوبر، بدأنا في ترحيل جميع الخدمات الحالية بشكل منهجي إلى Kubernetes. بحلول شهر مارس من العام التالي، أكملنا عملية الترحيل والآن تعمل منصة Tinder حصريًا على Kubernetes.

بناء الصور لـ Kubernetes

لدينا أكثر من 30 مستودعًا لرموز المصدر للخدمات الصغيرة التي تعمل على مجموعة Kubernetes. تتم كتابة التعليمات البرمجية الموجودة في هذه المستودعات بلغات مختلفة (على سبيل المثال، Node.js، Java، Scala، Go) مع بيئات تشغيل متعددة لنفس اللغة.

تم تصميم نظام البناء لتوفير "سياق بناء" قابل للتخصيص بالكامل لكل خدمة صغيرة. يتكون عادةً من ملف Dockerfile وقائمة أوامر shell. محتواها قابل للتخصيص بالكامل، وفي الوقت نفسه، تتم كتابة جميع سياقات البناء هذه وفقًا لتنسيق موحد. يتيح توحيد سياقات البناء لنظام بناء واحد التعامل مع جميع الخدمات الصغيرة.

انتقال Tinder إلى Kubernetes
الشكل 1-1. عملية بناء موحدة عبر حاوية Builder

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

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

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

بنية مجموعة Kubernetes والهجرة

إدارة حجم الكتلة

قررنا استخدام kube-aws للنشر الآلي للمجموعة على مثيلات Amazon EC2. في البداية، كان كل شيء يعمل في مجموعة واحدة مشتركة من العقد. لقد أدركنا بسرعة الحاجة إلى فصل أعباء العمل حسب الحجم ونوع المثيل لتحقيق استخدام أكثر كفاءة للموارد. كان المنطق هو أن تشغيل العديد من القرون المحملة متعددة الخيوط تبين أنه أكثر قابلية للتنبؤ من حيث الأداء من تواجدها مع عدد كبير من القرون ذات الخيوط المفردة.

وفي النهاية استقرينا على:

  • م 5.4xlarge - للمراقبة (بروميثيوس)؛
  • c5.4xlarge - بالنسبة لأحمال عمل Node.js (عبء العمل المفرد)؛
  • c5.2xlarge - لـ Java وGo (عبء العمل متعدد الخيوط)؛
  • c5.4xlarge — للوحة التحكم (3 عقد).

هجرة

كانت إحدى الخطوات التحضيرية للانتقال من البنية التحتية القديمة إلى Kubernetes هي إعادة توجيه الاتصال المباشر الحالي بين الخدمات إلى موازنات التحميل الجديدة (Elastic Load Balancers (ELB). تم إنشاؤها على شبكة فرعية محددة من السحابة الافتراضية الخاصة (VPC). تم توصيل هذه الشبكة الفرعية بـ Kubernetes VPC. سمح لنا هذا بترحيل الوحدات تدريجيًا، دون النظر إلى الترتيب المحدد لتبعيات الخدمة.

تم إنشاء نقاط النهاية هذه باستخدام مجموعات مرجحة من سجلات DNS التي تحتوي على CNAMEs تشير إلى كل ELB جديد. للتبديل، أضفنا إدخالاً جديدًا يشير إلى ELB الجديد لخدمة Kubernetes بوزن 0. ثم قمنا بعد ذلك بتعيين وقت البقاء (TTL) للإدخال المعين على 0. بعد ذلك، تم تحديد الأوزان القديمة والجديدة تم تعديله ببطء، وفي النهاية تم إرسال 100% من التحميل إلى خادم جديد. بعد اكتمال التبديل، عادت قيمة TTL إلى مستوى أكثر ملاءمة.

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

الدروس

حدود نسيج الشبكة

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

هناك ثلاثة خيارات لنظام التشغيل Linux تتعلق بذاكرة التخزين المؤقت لـ ARP:

انتقال Tinder إلى Kubernetes
(مصدر)

gc_thresh3 - وهذا حد صعب. إن ظهور إدخالات "تجاوز سعة الجدول المجاور" في السجل يعني أنه حتى بعد تجميع البيانات المهملة المتزامنة (GC)، لم تكن هناك مساحة كافية في ذاكرة التخزين المؤقت لـ ARP لتخزين الإدخال المجاور. في هذه الحالة، تقوم النواة ببساطة بالتخلص من الحزمة بالكامل.

نحن نستخدم الفانيلي كنسيج شبكي في Kubernetes. يتم إرسال الحزم عبر VXLAN. VXLAN عبارة عن نفق L2 مرفوع فوق شبكة L3. تستخدم التقنية تغليف MAC-in-UDP (بروتوكول مخطط بيانات عنوان MAC للمستخدم) وتسمح بتوسيع قطاعات الشبكة من الطبقة الثانية. بروتوكول النقل على شبكة مركز البيانات الفعلية هو IP بالإضافة إلى UDP.

انتقال Tinder إلى Kubernetes
الشكل 2-1. مخطط الفانيلا (مصدر)

انتقال Tinder إلى Kubernetes
الشكل 2-2. حزمة VXLAN (مصدر)

تقوم كل عقدة عاملة في Kubernetes بتخصيص مساحة عنوان افتراضية بقناع /24 من كتلة /9 أكبر. لكل عقدة هذا وسائل إدخال واحد في جدول التوجيه، وإدخال واحد في جدول ARP (على واجهة Flannel.1)، وإدخال واحد في جدول التبديل (FDB). تتم إضافتها في المرة الأولى التي يتم فيها تشغيل العقدة العاملة أو في كل مرة يتم فيها اكتشاف عقدة جديدة.

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

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

وفي وقت الفشل، كان هناك 605 عقدة في المجموعة. وللأسباب المذكورة أعلاه، كان هذا كافيا للتغلب على الأهمية gc_thresh3، وهو الافتراضي. عندما يحدث هذا، لا يبدأ إسقاط الحزم فحسب، بل تختفي مساحة العنوان الافتراضية لـ Flannel بأكملها مع قناع /24 من جدول ARP. تتم مقاطعة اتصالات Node-Pod واستعلامات DNS (تتم استضافة DNS في مجموعة؛ اقرأ لاحقًا في هذه المقالة للحصول على التفاصيل).

لحل هذه المشكلة، تحتاج إلى زيادة القيم gc_thresh1, gc_thresh2 и gc_thresh3 وأعد تشغيل Flannel لإعادة تسجيل الشبكات المفقودة.

تحجيم DNS غير متوقع

أثناء عملية الترحيل، استخدمنا DNS بشكل فعال لإدارة حركة المرور ونقل الخدمات تدريجيًا من البنية التحتية القديمة إلى Kubernetes. قمنا بتعيين قيم TTL منخفضة نسبيًا لمجموعات السجلات المرتبطة في Route53. عندما كانت البنية التحتية القديمة تعمل على مثيلات EC2، أشار تكوين المحلل لدينا إلى Amazon DNS. لقد أخذنا هذا الأمر كأمر مسلم به، ولم يلاحظ إلى حد كبير تأثير انخفاض مدة البقاء (TTL) على خدماتنا وخدمات Amazon (مثل DynamoDB).

عندما قمنا بترحيل الخدمات إلى Kubernetes، وجدنا أن DNS كان يعالج 250 ألف طلب في الثانية. ونتيجة لذلك، بدأت التطبيقات تواجه مهلات مستمرة وخطيرة لاستعلامات DNS. حدث هذا على الرغم من الجهود المذهلة لتحسين موفر DNS وتحويله إلى CoreDNS (الذي وصل في ذروة الحمل إلى 1000 وحدة تعمل على 120 مركزًا).

أثناء بحثنا عن الأسباب والحلول المحتملة الأخرى، اكتشفنا ذلك статью، يصف ظروف السباق التي تؤثر على إطار عمل تصفية الحزم نت فيلتر في لينكس. المهلات التي لاحظناها، إلى جانب العداد المتزايد Insert_failed في واجهة Flannel كانت متوافقة مع نتائج المقالة.

تحدث المشكلة في مرحلة ترجمة عنوان شبكة المصدر والوجهة (SNAT وDNAT) وإدخالها لاحقًا في الجدول conntrack. كان أحد الحلول التي تمت مناقشتها داخليًا واقترحها المجتمع هو نقل DNS إلى العقدة العاملة نفسها. في هذه الحالة:

  • ليست هناك حاجة إلى SNAT لأن حركة المرور تبقى داخل العقدة. لا تحتاج إلى أن يتم توجيهها من خلال الواجهة eth0.
  • ليست هناك حاجة إلى DNAT نظرًا لأن عنوان IP الوجهة محلي للعقدة، وليس جرابًا تم اختياره عشوائيًا وفقًا للقواعد يبتابليس.

قررنا التمسك بهذا النهج. تم نشر CoreDNS باعتباره DaemonSet في Kubernetes وقمنا بتنفيذ خادم DNS للعقدة المحلية فيه حل كل جراب عن طريق وضع العلم --نظام أسماء النطاقات العنقودية الأوامر مكعبة . تبين أن هذا الحل فعال بالنسبة لمهلات DNS.

ومع ذلك، ما زلنا نرى فقدان الحزمة وزيادة في العداد Insert_failed في واجهة الفانيلا. واستمر هذا بعد تنفيذ الحل البديل لأننا تمكنا من إزالة SNAT و/أو DNAT لحركة مرور DNS فقط. تم الحفاظ على ظروف السباق لأنواع أخرى من حركة المرور. ولحسن الحظ، فإن معظم الحزم لدينا هي بروتوكول TCP، وفي حالة حدوث مشكلة يتم إعادة إرسالها ببساطة. ما زلنا نحاول إيجاد حل مناسب لجميع أنواع حركة المرور.

استخدام Envoy لتحقيق موازنة أفضل للأحمال

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

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

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

لقد توصلنا إلى التكوين التالي: احصل على عربة جانبية للمبعوث لكل حجرة ومسار واحد، وقم بتوصيل المجموعة بالحاوية محليًا عبر المنفذ. لتقليل التتالي المحتمل والحفاظ على نصف قطر إصابة صغير، استخدمنا أسطولًا من حجرات الوكيل الأمامية Envoy، واحدة لكل منطقة توافر خدمات (AZ) لكل خدمة. لقد اعتمدوا على محرك بسيط لاكتشاف الخدمة كتبه أحد مهندسينا والذي قام ببساطة بإرجاع قائمة من الكبسولات في كل منطقة توافر خدمة معينة.

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

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

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

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

انتقال Tinder إلى Kubernetes
الشكل 3-1. تقارب وحدة المعالجة المركزية لخدمة واحدة أثناء الانتقال إلى Envoy

انتقال Tinder إلى Kubernetes

انتقال Tinder إلى Kubernetes

النتيجة النهائية

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

عندما ظهرت الحاجة إلى سعة إضافية في البنية التحتية القديمة، كان علينا الانتظار عدة دقائق حتى يتم إطلاق مثيلات EC2 الجديدة. تبدأ الآن الحاويات في العمل وتبدأ في معالجة حركة المرور خلال ثوانٍ بدلاً من دقائق. توفر جدولة حاويات متعددة على مثيل EC2 واحد أيضًا تركيزًا أفقيًا محسنًا. ونتيجة لذلك، نتوقع انخفاضًا كبيرًا في تكاليف EC2019 في عام 2 مقارنة بالعام الماضي.

استغرقت عملية الهجرة ما يقرب من عامين، لكننا أكملناها في مارس 2019. حاليًا، تعمل منصة Tinder حصريًا على مجموعة Kubernetes التي تتكون من 200 خدمة و1000 عقدة و15 حاوية و000 حاوية قيد التشغيل. لم تعد البنية التحتية المجال الوحيد لفرق العمليات. يتقاسم جميع مهندسينا هذه المسؤولية ويتحكمون في عملية إنشاء ونشر تطبيقاتهم باستخدام التعليمات البرمجية فقط.

PS من المترجم

اقرأ أيضًا سلسلة من المقالات على مدونتنا:

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

إضافة تعليق