ProHoster > بلوق > إدارة > موازنة التحميل وتوسيع نطاق الاتصالات طويلة الأمد في Kubernetes
موازنة التحميل وتوسيع نطاق الاتصالات طويلة الأمد في Kubernetes
ستساعدك هذه المقالة على فهم كيفية عمل موازنة التحميل في Kubernetes، وما يحدث عند توسيع نطاق الاتصالات طويلة الأمد، ولماذا يجب عليك التفكير في الموازنة من جانب العميل إذا كنت تستخدم HTTP/2، أو gRPC، أو RSockets، أو AMQP، أو بروتوكولات أخرى طويلة الأمد. .
قليلاً عن كيفية إعادة توزيع حركة المرور في Kubernetes
يوفر Kubernetes تجريدين مناسبين لنشر التطبيقات: الخدمات وعمليات النشر.
تصف عمليات النشر كيفية وعدد نسخ تطبيقك التي يجب تشغيلها في أي وقت محدد. يتم نشر كل تطبيق باعتباره Pod ويتم تعيين عنوان IP له.
تتشابه الخدمات في وظيفتها مع موازن التحميل. وهي مصممة لتوزيع حركة المرور عبر القرون المتعددة.
دعونا نرى كيف يبدو.
في الرسم البياني أدناه، يمكنك رؤية ثلاث مثيلات لنفس التطبيق وموازن التحميل:
يُسمى موازن التحميل بالخدمة ويتم تعيين عنوان IP له. تتم إعادة توجيه أي طلب وارد إلى إحدى البودات:
يحدد سيناريو النشر عدد مثيلات التطبيق. لن تضطر أبدًا إلى التوسع مباشرةً ضمن:
يتم تعيين عنوان IP خاص لكل جراب:
من المفيد التفكير في الخدمات باعتبارها مجموعة من عناوين IP. في كل مرة تقوم فيها بالوصول إلى الخدمة، يتم تحديد أحد عناوين IP من القائمة واستخدامه كعنوان الوجهة.
تبدو هكذا.
يتم تلقي طلب حليقة 10.96.45.152 إلى الخدمة:
تختار الخدمة واحدًا من ثلاثة عناوين pod كوجهة لها:
تتم إعادة توجيه حركة المرور إلى جراب معين:
إذا كان تطبيقك يتكون من واجهة أمامية وخلفية، فسيكون لديك خدمة ونشر لكل منهما.
عندما تقدم الواجهة الأمامية طلبًا إلى الواجهة الخلفية، فإنها لا تحتاج إلى معرفة عدد البودات التي تخدمها الواجهة الخلفية بالضبط: يمكن أن يكون هناك واحد أو عشرة أو مائة.
كما أن الواجهة الأمامية لا تعرف شيئًا عن عناوين البودات التي تخدم الواجهة الخلفية.
عندما تقوم الواجهة الأمامية بتقديم طلب إلى الواجهة الخلفية، فإنها تستخدم عنوان IP الخاص بخدمة الواجهة الخلفية، والذي لا يتغير.
وهنا كيف يبدو.
تحت 1 يطلب مكون الواجهة الخلفية الداخلية. بدلاً من تحديد واحد محدد للواجهة الخلفية، فإنه يقدم طلبًا إلى الخدمة:
تحدد الخدمة إحدى حجرات الواجهة الخلفية كعنوان الوجهة:
تنتقل حركة المرور من Pod 1 إلى Pod 5، ويتم تحديدها بواسطة الخدمة:
لا يعرف أقل من 1 بالضبط عدد البودات مثل أقل من 5 المخفية خلف الخدمة:
ولكن كيف تقوم الخدمة بتوزيع الطلبات بالضبط؟ يبدو أنه يتم استخدام التوازن الدائري؟ دعونا معرفة ذلك.
تحقيق التوازن في خدمات Kubernetes
خدمات Kubernetes غير موجودة. لا توجد عملية للخدمة التي تم تعيين عنوان IP ومنفذ لها.
يمكنك التحقق من ذلك عن طريق تسجيل الدخول إلى أي عقدة في المجموعة وتشغيل الأمر netstat -ntlp.
لن تتمكن حتى من العثور على عنوان IP المخصص للخدمة.
يقع عنوان IP الخاص بالخدمة في طبقة التحكم، وفي وحدة التحكم، ويتم تسجيله في قاعدة البيانات - وما إلى ذلك. يتم استخدام نفس العنوان بواسطة مكون آخر - kube-proxy.
يتلقى Kube-proxy قائمة بعناوين IP لجميع الخدمات ويقوم بإنشاء مجموعة من قواعد iptables على كل عقدة في المجموعة.
تقول هذه القواعد: "إذا رأينا عنوان IP الخاص بالخدمة، فسنحتاج إلى تعديل عنوان وجهة الطلب وإرساله إلى إحدى البودات."
يتم استخدام عنوان IP الخاص بالخدمة فقط كنقطة دخول ولا يتم تقديمه بواسطة أي عملية تستمع إلى عنوان IP والمنفذ هذا.
دعونا ننظر إلى هذا.
النظر في مجموعة من ثلاث العقد. تحتوي كل عقدة على كبسولات:
تعتبر القرون المربوطة باللون البيج جزءًا من الخدمة. ونظرًا لعدم وجود الخدمة كعملية، فإنها تظهر باللون الرمادي:
تطلب الكبسولة الأولى خدمة ويجب أن تذهب إلى إحدى البودات المرتبطة بها:
لكن الخدمة غير موجودة والعملية غير موجودة. كيف يعمل؟
قبل أن يغادر الطلب العقدة، فإنه يمر عبر قواعد iptables:
تعلم قواعد iptables أن الخدمة غير موجودة وتستبدل عنوان IP الخاص بها بأحد عناوين IP الخاصة بالبودات المرتبطة بتلك الخدمة:
يتلقى الطلب عنوان IP صالحًا كعنوان الوجهة وتتم معالجته بشكل طبيعي:
اعتمادًا على هيكل الشبكة، يصل الطلب في النهاية إلى الحجرة:
هل يمكن لـ iptables تحميل الرصيد؟
لا، يتم استخدام iptables للتصفية ولم يتم تصميمها لتحقيق التوازن.
ومع ذلك، فمن الممكن كتابة مجموعة من القواعد التي تعمل مثل موازن زائف.
وهذا هو بالضبط ما يتم تنفيذه في Kubernetes.
إذا كان لديك ثلاث كبسولات، فسيقوم kube-proxy بكتابة القواعد التالية:
اختر الفرع الأول باحتمال 33%، وإلا انتقل إلى القاعدة التالية.
اختر الخيار الثاني باحتمال 50%، وإلا انتقل إلى القاعدة التالية.
اختر الثالث تحت.
يؤدي هذا النظام إلى اختيار كل جراب باحتمال 33%.
وليس هناك ما يضمن أنه سيتم اختيار الجراب 2 بعد الجراب 1.
لاحظ: يستخدم iptables وحدة إحصائية ذات توزيع عشوائي. وبالتالي، تعتمد خوارزمية الموازنة على الاختيار العشوائي.
الآن بعد أن فهمت كيفية عمل الخدمات، دعنا نلقي نظرة على سيناريوهات الخدمة الأكثر إثارة للاهتمام.
لا يتم توسيع الاتصالات طويلة الأمد في Kubernetes بشكل افتراضي
يتم تقديم كل طلب HTTP من الواجهة الأمامية إلى الواجهة الخلفية بواسطة اتصال TCP منفصل، والذي يتم فتحه وإغلاقه.
إذا أرسلت الواجهة الأمامية 100 طلب في الثانية إلى الواجهة الخلفية، فسيتم فتح وإغلاق 100 اتصال TCP مختلف.
يمكنك تقليل وقت معالجة الطلب وتحميله عن طريق فتح اتصال TCP واحد واستخدامه لجميع طلبات HTTP اللاحقة.
يحتوي بروتوكول HTTP على ميزة تسمى استمرارية HTTP أو إعادة استخدام الاتصال. في هذه الحالة، يتم استخدام اتصال TCP واحد لإرسال واستقبال طلبات واستجابات HTTP متعددة:
لا يتم تمكين هذه الميزة افتراضيًا: يجب تكوين كل من الخادم والعميل وفقًا لذلك.
الإعداد بحد ذاته بسيط ويمكن الوصول إليه لمعظم لغات وبيئات البرمجة.
ماذا يحدث إذا استخدمنا خاصية البقاء على قيد الحياة في خدمة Kubernetes؟
لنفترض أن كلا من الواجهة الأمامية والخلفية يدعمان البقاء على قيد الحياة.
لدينا نسخة واحدة من الواجهة الأمامية وثلاث نسخ من الواجهة الخلفية. تقوم الواجهة الأمامية بتقديم الطلب الأول وتفتح اتصال TCP بالواجهة الخلفية. يصل الطلب إلى الخدمة، ويتم تحديد إحدى حجرات الواجهة الخلفية كعنوان الوجهة. ترسل الواجهة الخلفية استجابة، وتستقبلها الواجهة الأمامية.
على عكس الموقف المعتاد حيث يتم إغلاق اتصال TCP بعد تلقي استجابة، فإنه يظل الآن مفتوحًا لمزيد من طلبات HTTP.
ماذا يحدث إذا أرسلت الواجهة الأمامية المزيد من الطلبات إلى الواجهة الخلفية؟
لإعادة توجيه هذه الطلبات، سيتم استخدام اتصال TCP مفتوح، وستنتقل جميع الطلبات إلى نفس الواجهة الخلفية التي ذهب إليها الطلب الأول.
ألا يجب على iptables إعادة توزيع حركة المرور؟
ليس في هذه الحالة.
عندما يتم إنشاء اتصال TCP، فإنه يمر عبر قواعد iptables، التي تحدد واجهة خلفية محددة حيث ستنتقل حركة المرور.
نظرًا لأن جميع الطلبات اللاحقة تكون على اتصال TCP مفتوح بالفعل، فلن يتم استدعاء قواعد iptables.
دعونا نرى كيف يبدو.
تقوم الكبسولة الأولى بإرسال طلب إلى الخدمة:
أنت تعرف بالفعل ما سيحدث بعد ذلك. الخدمة غير موجودة، ولكن هناك قواعد iptables التي ستقوم بمعالجة الطلب:
سيتم اختيار إحدى حجرات الواجهة الخلفية كعنوان الوجهة:
يصل الطلب إلى الكبسولة. عند هذه النقطة، سيتم إنشاء اتصال TCP مستمر بين الحجرتين:
أي طلب لاحق من الكبسولة الأولى سوف يمر عبر الاتصال الذي تم إنشاؤه بالفعل:
والنتيجة هي وقت استجابة أسرع وإنتاجية أعلى، لكنك تفقد القدرة على توسيع نطاق الواجهة الخلفية.
حتى لو كان لديك حجرتان في الواجهة الخلفية، مع اتصال مستمر، ستذهب حركة المرور دائمًا إلى أحدهما.
هل يمكن إصلاح هذا؟
وبما أن Kubernetes لا يعرف كيفية موازنة الاتصالات المستمرة، فإن هذه المهمة تقع على عاتقك.
الخدمات عبارة عن مجموعة من عناوين IP والمنافذ تسمى نقاط النهاية.
يمكن لتطبيقك الحصول على قائمة بنقاط النهاية من الخدمة وتحديد كيفية توزيع الطلبات فيما بينها. يمكنك فتح اتصال مستمر لكل حاوية وطلبات التوازن بين هذه الاتصالات باستخدام round-robin.
يجب أن يتبع رمز العميل المسؤول عن التوازن هذا المنطق:
احصل على قائمة بنقاط النهاية من الخدمة.
افتح اتصالاً مستمرًا لكل نقطة نهاية.
عند الحاجة إلى تقديم طلب، استخدم أحد الاتصالات المفتوحة.
قم بتحديث قائمة نقاط النهاية بانتظام، أو قم بإنشاء نقاط نهاية جديدة أو أغلق الاتصالات المستمرة القديمة إذا تغيرت القائمة.
وهذا هو ما سوف تبدو وكأنها.
بدلاً من إرسال الكبسولة الأولى الطلب إلى الخدمة، يمكنك موازنة الطلبات من جانب العميل:
تحتاج إلى كتابة رمز يسألك عن القرون التي تعد جزءًا من الخدمة:
بمجرد حصولك على القائمة، احفظها على جانب العميل واستخدمها للاتصال بالبودات:
أنت مسؤول عن خوارزمية موازنة التحميل:
والسؤال الذي يطرح نفسه الآن: هل تنطبق هذه المشكلة فقط على استمرارية HTTP؟
موازنة التحميل من جانب العميل
HTTP ليس البروتوكول الوحيد الذي يمكنه استخدام اتصالات TCP المستمرة.
إذا كان التطبيق الخاص بك يستخدم قاعدة بيانات، فلن يتم فتح اتصال TCP في كل مرة تحتاج فيها إلى تقديم طلب أو استرداد مستند من قاعدة البيانات.
وبدلاً من ذلك، يتم فتح واستخدام اتصال TCP مستمر بقاعدة البيانات.
إذا تم نشر قاعدة بياناتك على Kubernetes وتم توفير الوصول إليها كخدمة، فسوف تواجه نفس المشكلات الموضحة في القسم السابق.
سيتم تحميل نسخة متماثلة لقاعدة بيانات واحدة أكثر من النسخ الأخرى. لن يساعد Kube-proxy وKubernetes في موازنة الاتصالات. يجب عليك الحرص على موازنة الاستعلامات مع قاعدة البيانات الخاصة بك.
اعتمادًا على المكتبة التي تستخدمها للاتصال بقاعدة البيانات، قد يكون لديك خيارات مختلفة لحل هذه المشكلة.
فيما يلي مثال للوصول إلى مجموعة قاعدة بيانات MySQL من Node.js:
var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();
var endpoints = /* retrieve endpoints from the Service */
for (var [index, endpoint] of endpoints) {
poolCluster.add(`mysql-replica-${index}`, endpoint);
}
// Make queries to the clustered MySQL database
هناك العديد من البروتوكولات الأخرى التي تستخدم اتصالات TCP المستمرة:
WebSockets وWebSockets المضمونة
HTTP / 2
جي آر بي سي
RSockets
AMQP
يجب أن تكون على دراية بمعظم هذه البروتوكولات بالفعل.
ولكن إذا كانت هذه البروتوكولات تحظى بشعبية كبيرة، فلماذا لا يوجد حل موحد للموازنة؟ لماذا يحتاج منطق العميل إلى التغيير؟ هل هناك حل Kubernetes أصلي؟
تم تصميم Kube-proxy وiptables لتغطية حالات الاستخدام الأكثر شيوعًا عند النشر إلى Kubernetes. هذا من أجل الراحة.
إذا كنت تستخدم خدمة ويب تعرض REST API، فأنت محظوظ - في هذه الحالة، لا يتم استخدام اتصالات TCP المستمرة، ويمكنك استخدام أي خدمة من خدمات Kubernetes.
ولكن بمجرد البدء في استخدام اتصالات TCP المستمرة، سيتعين عليك معرفة كيفية توزيع الحمل بالتساوي عبر الواجهات الخلفية. لا يحتوي Kubernetes على حلول جاهزة لهذه الحالة.
ومع ذلك، هناك بالتأكيد خيارات يمكن أن تساعد.
موازنة الاتصالات طويلة الأمد في Kubernetes
هناك أربعة أنواع من الخدمات في Kubernetes:
ClusterIP
منفذ العقدة
LoadBalancer
بلا رأس
تعمل الخدمات الثلاث الأولى بناءً على عنوان IP افتراضي، والذي يستخدمه kube-proxy لإنشاء قواعد iptables. لكن الأساس الأساسي لجميع الخدمات هو خدمة مقطوعة الرأس.
لا تحتوي الخدمة مقطوعة الرأس على أي عنوان IP مرتبط بها وتوفر فقط آلية لاسترداد قائمة عناوين IP ومنافذ القرون (نقاط النهاية) المرتبطة بها.
تعتمد جميع الخدمات على الخدمة مقطوعة الرأس.
خدمة ClusterIP هي خدمة بدون رأس مع بعض الإضافات:
تقوم طبقة الإدارة بتعيين عنوان IP لها.
يقوم Kube-proxy بإنشاء قواعد iptables الضرورية.
بهذه الطريقة يمكنك تجاهل kube-proxy واستخدام قائمة نقاط النهاية التي تم الحصول عليها من الخدمة مقطوعة الرأس مباشرةً لموازنة التحميل لتطبيقك.
ولكن كيف يمكننا إضافة منطق مماثل لجميع التطبيقات المنتشرة في المجموعة؟
إذا تم نشر تطبيقك بالفعل، فقد تبدو هذه المهمة مستحيلة. ومع ذلك، هناك خيار بديل.
سوف تساعدك شبكة الخدمة
ربما لاحظت بالفعل أن استراتيجية موازنة التحميل من جانب العميل قياسية تمامًا.
عند بدء التطبيق، فإنه:
يحصل على قائمة بعناوين IP من الخدمة.
يفتح ويحافظ على تجمع الاتصال.
يقوم بتحديث التجمع بشكل دوري عن طريق إضافة نقاط النهاية أو إزالتها.
بمجرد أن يريد التطبيق تقديم طلب، فإنه:
يحدد اتصالاً متاحًا باستخدام بعض المنطق (على سبيل المثال، round-robin).
ينفذ الطلب.
تعمل هذه الخطوات مع اتصالات WebSockets وgRPC وAMQP.
يمكنك فصل هذا المنطق إلى مكتبة منفصلة واستخدامه في تطبيقاتك.
ومع ذلك، يمكنك استخدام شبكات الخدمة مثل Istio أو Linkerd بدلاً من ذلك.
تعمل Service Mesh على تعزيز تطبيقك من خلال عملية:
يبحث تلقائيًا عن عناوين IP الخاصة بالخدمة.
اختبارات الاتصالات مثل WebSockets وgRPC.
أرصدة الطلبات باستخدام البروتوكول الصحيح.
تساعد شبكة الخدمة على إدارة حركة المرور داخل المجموعة، ولكنها تستهلك الكثير من الموارد. تستخدم الخيارات الأخرى مكتبات الجهات الخارجية مثل Netflix Ribbon أو الوكلاء القابلين للبرمجة مثل Envoy.
ماذا يحدث إذا تجاهلت قضايا التوازن؟
يمكنك اختيار عدم استخدام موازنة التحميل مع عدم ملاحظة أي تغييرات. دعونا نلقي نظرة على بعض سيناريوهات العمل.
إذا كان لديك عملاء أكثر من الخوادم، فهذه ليست مشكلة كبيرة.
لنفترض أن هناك خمسة عملاء يتصلون بخادمين. حتى لو لم يكن هناك توازن، سيتم استخدام كلا الخادمين:
قد لا يتم توزيع الاتصالات بالتساوي: ربما أربعة عملاء متصلين بنفس الخادم، ولكن هناك فرصة جيدة لاستخدام كلا الخادمين.
والأمر الأكثر إشكالية هو السيناريو المعاكس.
إذا كان لديك عدد أقل من العملاء وعدد أكبر من الخوادم، فقد لا يتم استغلال مواردك بشكل كافٍ وستظهر اختناقات محتملة.
لنفترض أن هناك عميلين وخمسة خوادم. في أفضل الأحوال، سيكون هناك اتصالان دائمان بخادمين من أصل خمسة.
ستكون الخوادم المتبقية خاملة:
إذا لم يتمكن هذين الخادمين من التعامل مع طلبات العميل، فلن يساعد القياس الأفقي.
اختتام
تم تصميم خدمات Kubernetes للعمل في معظم سيناريوهات تطبيقات الويب القياسية.
ومع ذلك، بمجرد بدء العمل مع بروتوكولات التطبيق التي تستخدم اتصالات TCP المستمرة، مثل قواعد البيانات أو gRPC أو WebSockets، لن تعد الخدمات مناسبة. لا يوفر Kubernetes آليات داخلية لموازنة اتصالات TCP المستمرة.
هذا يعني أنه يجب عليك كتابة التطبيقات مع وضع التوازن من جانب العميل في الاعتبار.