انتقال تندر إلى 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. في حين أن محتوياتها قابلة للتخصيص بالكامل ، إلا أن سياقات البناء هذه مكتوبة باتباع تنسيق موحد. يسمح توحيد سياقات البناء لنظام بناء واحد للتعامل مع جميع الخدمات الصغيرة.

الشكل 1–1 عملية بناء موحدة من خلال حاوية المنشئ

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

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

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

العمارة والهجرة العنقودية

تحجيم الكتلة

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

استقرنا على:

  • m5.4x تكبير للرصد (بروميثيوس)
  • تكبير c5.4x لحجم عمل Node.js (عبء عمل مترابط واحد)
  • c5.2xlarge لـ Java and Go (عبء عمل متعدد الخيوط)
  • تكبير c5.4x لمستوى التحكم (3 عقد)

الهجرة

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

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

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

الدروس

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

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

توجد ثلاث قيم Linux ذات صلة بذاكرة التخزين المؤقت ARP:

ائتمان

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

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

الشكل 2-1 مخطط الفانيلا (الائتمان)

الشكل 2-2 حزمة VXLAN (ائتمان)

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

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

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

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

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

تشغيل DNS بشكل غير متوقع على نطاق واسع

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

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

أثناء البحث عن الأسباب والحلول المحتملة الأخرى ، وجدنا مقالة تصف حالة سباق تؤثر على netfilter إطار تصفية حزم Linux. مهلة DNS التي كنا نراها ، إلى جانب عداد insert_failed متزايد على واجهة Flannel ، تتماشى مع نتائج المقالة.

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

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

قررنا المضي قدما في هذا النهج. تم نشر CoreDNS كـ DaemonSet في Kubernetes وقمنا بحقن خادم DNS المحلي للعقدة في resolv.conf الخاص بكل pod من خلال تكوين kubelet - علامة أوامر نظام مجموعة - dns. الحل كان فعالا لانتهاء مهلة DNS.

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

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

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

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

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

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

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

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

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

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

الشكل 3-1 تقارب وحدة المعالجة المركزية (CPU) لخدمة واحدة أثناء النقل إلى المبعوث

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

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

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

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