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

ترى 3 مداخل في حالة "الإنهاء". لكن هذا ليس الإزالة المعتادة للموقد - إنهم عالقون في هذه الحالة لأن شيطان الحاوية على عقيدتهم أصيب بشيء شديد الجوع للموارد.
يمكن حل هذه المشكلات عن طريق التعامل مع نقص الموارد بشكل صحيح ، لكن هذا ليس موضوع هذا المقال (هناك مقال جيد) ، بالإضافة إلى أنه ليس رصاصة فضية لحل جميع المشكلات المتعلقة بالموارد.
السبب الرئيسي لمثل هذه المشاكل غير صحيح أو عدم وجود إدارة الموارد في الكتلة. وإذا لم يكن هذا النوع من المشكلات كارثة على عمليات النشر ، لأنها ستنشئ بسهولة واحدة جديدة تعمل في إطارها ، ثم لكيانات مثل DaemonSet ، أو حتى بالنسبة إلى StatefulSet ، فإن مثل هذه التجميد ستكون قاتلة وتتطلب تدخلًا يدويًا.
يمكنك الحصول على كتلة ضخمة مع الكثير من وحدة المعالجة المركزية والذاكرة. عندما تقوم بتشغيل العديد من التطبيقات عليه دون إعدادات موارد مناسبة ، فهناك فرصة لوضع جميع القرون كثيفة الاستخدام للموارد على نفس العقدة. سوف يقاتلون من أجل الموارد ، حتى إذا بقيت العقد المتبقية من الكتلة مجانية عمليا.
يمكنك أيضًا رؤية الحالات الأقل خطورة حيث تتأثر بعض التطبيقات بجيرانها. حتى إذا تم تكوين موارد هذه التطبيقات "البريئة" بشكل صحيح ، يمكن للتجول تحت أن يأتي ويقتلهم. مثال على هذا السيناريو:
- يطلب التطبيق الخاص بك 4 غيغابايت من الذاكرة ، ولكن في البداية لا يستغرق سوى 1 غيغابايت.
- يتم تعيين تجول تحت ، دون تكوين مورد ، إلى العقدة نفسها.
- تجول تحت يستهلك كل الذاكرة المتاحة.
- يحاول التطبيق الخاص بك تخصيص المزيد من الذاكرة وتعطل لأنه لم يعد هناك المزيد.
حالة أخرى شعبية إلى حد ما هي إعادة تقييم. يقدم بعض المطورين طلبات ضخمة في قوائم "في حالة" ولا يستخدمون هذه الموارد مطلقًا. والنتيجة هي مضيعة للمال.
نظرية القرار
الرعب! حقا؟
لحسن الحظ ، تقدم Kubernetes طريقة لفرض بعض القيود على القرون عن طريق تحديد تكوينات الموارد الافتراضية وكذلك الحد الأدنى والحد الأقصى للقيم. يتم تطبيق هذا باستخدام كائن LimitRange . يعد LimitRange أداة مريحة للغاية عندما يكون لديك عدد محدود من مساحات الأسماء أو التحكم الكامل في عملية إنشائها. حتى بدون التكوين الصحيح للموارد ، سيتم تقييد تطبيقاتك في استخدامها. "الأبرياء" ، سوف تكون آمنة الموقد ضبطها آمنة ومحمية من الجيران الضارة. إذا قام شخص ما بنشر تطبيق جشع بدون تكوين مورد ، فسيتلقى هذا التطبيق القيم الافتراضية وربما يتعطل. وهذا كل شيء! لن يقوم التطبيق بسحب أي شخص على طول.
وبالتالي ، لدينا أداة للتحكم وفرض تكوين الموارد للموقد ، والآن يبدو أننا في أمان. إلى هذا الحد؟ ليس حقا والحقيقة هي أنه ، كما وصفنا سابقًا ، يمكن إنشاء مساحات الأسماء الخاصة بنا بواسطة المستخدمين ، وبالتالي ، قد لا يكون LimitRange موجودًا في مساحات الأسماء هذه ، حيث يجب إنشاؤها في كل مساحة اسم على حدة. لذلك ، نحن بحاجة إلى شيء ليس فقط على مستوى مساحة الاسم ، ولكن أيضًا على مستوى الكتلة. ولكن لا توجد وظيفة من هذا القبيل في Kubernetes حتى الآن.
هذا هو السبب في أنني قررت أن أكتب حل لهذه المشكلة. اسمحوا لي أن أقدم لكم - الحد المشغل. هذا هو المشغل الذي تم إنشاؤه على أساس إطار عمل المشغل SDK ، والذي يستخدم المورد المخصص ClusterLimit ويساعد على تأمين جميع التطبيقات "البريئة" في الكتلة. باستخدام عامل التشغيل هذا ، يمكنك التحكم في القيم الافتراضية وحدود الموارد لجميع مساحات الأسماء باستخدام الحد الأدنى من التكوين. كما يسمح لك باختيار مكان تطبيق التكوين بالضبط باستخدام مساحة الاسم.
مثال ClusterLimitapiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: namespaceSelector: matchLabels: limit: "limited" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" min: cpu: "100m" memory: "99Mi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "110m" memory: "111Mi" - type: Pod max: cpu: "2" memory: "2Gi"
باستخدام هذا التكوين ، سينشئ المشغل LimitRange فقط في مساحة الاسم مع limit: limited
التسمية limit: limited
. سيكون هذا مفيدًا لتوفير قيود أكثر صرامة في مجموعة معينة من مساحات الأسماء. إذا لم يتم تحديد مساحة الاسم ، فسيقوم المشغل بتطبيق LimitRange على جميع مساحات الأسماء. إذا كنت ترغب في تكوين LimitRange يدويًا لمساحة محددة ، فيمكنك استخدام التعليق التوضيحي "limit.myafq.com/unlimited": true
هذا سيُخبر المشغل بتخطي مساحة الاسم هذه وعدم إنشاء LimitRange تلقائيًا.
مثال البرنامج النصي لاستخدام المشغل:
- إنشاء ClusterLimit الافتراضي مع القيود الليبرالية وبدون مساحة الاسم - سيتم تطبيقه في كل مكان.
- لمجموعة من مساحات الأسماء ذات التطبيقات الخفيفة ، قم بإنشاء ClusterLimit إضافية وأكثر صرامة باستخدام مساحة الاسم. ضع التسميات على مساحات الأسماء هذه وفقًا لذلك.
- على مساحة اسم ذات تطبيقات كثيفة الاستخدام للموارد ، ضع التعليق التوضيحي "limit.myafq.com/unlimited": صواب وقم بتكوين LimitRange يدويًا بحدود أوسع بكثير من المحدد في ClusteLimit الافتراضي.
الشيء المهم أن تعرفه عن عدة LimitRange في مساحة اسم واحدة:
عندما يتم إنشاء فرعية في مساحة اسم مع عدة LimitRange ، سيتم اتخاذ أكبر الإعدادات الافتراضية لتكوين مواردها. ولكن سيتم فحص الحد الأقصى والحد الأدنى للقيم وفقًا لأدق قيود LimitRange.
مثال عملي
سيقوم المشغل بتتبع جميع التغييرات في كل مساحة الاسم ، و ClusterLimits ، و LimitRanges التابعة ، وسيبدأ تنسيق حالة الكتلة مع أي تغيير في الكائنات المراقبة. دعونا نرى كيف يعمل في الممارسة العملية.
للبدء ، إنشاء تحت دون أي قيود:
تشغيل kubectl / الحصول على الإخراج ❯() kubectl run --generator=run-pod/v1 --image=bash bash pod/bash created ❯() kubectl get pod bash -o yaml apiVersion: v1 kind: Pod metadata: labels: run: bash name: bash namespace: default spec: containers: - image: bash name: bash resources: {}
ملاحظة: تم حذف جزء من إخراج الأمر لتبسيط المثال.
كما ترى ، فإن حقل "الموارد" فارغ ، مما يعني أنه يمكن إطلاق هذا الفرع الفرعي في أي مكان.
الآن سنقوم بإنشاء ClusterLimit الافتراضي للمجموعة بأكملها بقيم ليبرالية إلى حد ما:
الافتراضية limit.yaml apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: default-limit spec: limitRange: limits: - type: Container max: cpu: "4" memory: "5Gi" default: cpu: "700m" memory: "900Mi" defaultRequest: cpu: "500m" memory: "512Mi"
وأيضًا أكثر صرامة لمجموعة فرعية من مساحات الأسماء:
تقييدا-limit.yaml apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "800m" memory: "1Gi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi"
ثم قم بإنشاء مساحات الأسماء والقرون فيها لترى كيف يعمل.
مساحة الاسم العادية مع التقييد الافتراضي:
apiVersion: v1 kind: Namespace metadata: name: regular
ومساحة أسماء أكثر محدودية قليلاً ، وفقًا للأسطورة - للتطبيقات خفيفة الوزن:
apiVersion: v1 kind: Namespace metadata: labels: limit: "restrictive" name: lightweight
إذا نظرت إلى سجلات المشغل فور إنشاء مساحة الاسم ، يمكنك العثور على شيء من هذا القبيل أسفل المفسد:
سجلات المشغل {...,"msg":"Reconciling ClusterLimit","Triggered by":"/regular"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"regular","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"regular","Name":"default-limit"} {...,"msg":"Reconciling ClusterLimit","Triggered by":"/lightweight"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"default-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"default-limit"} {...,"msg":"Creating new namespace LimitRange.","Namespace":"lightweight","LimitRange":"restrictive-limit"} {...,"msg":"Updating namespace LimitRange.","Namespace":"lightweight","Name":"restrictive-limit"}
يحتوي الجزء المفقود من السجل على 3 حقول أخرى غير ملائمة في الوقت الحالي
كما ترون ، بدأ إنشاء كل مساحة اسم في إنشاء LimitRange جديد. مساحة اسم محدودة أكثر حصلت على اثنين LimitRange - الافتراضي وأكثر صرامة.
الآن دعونا نحاول إنشاء زوج من الموقد في مساحات الأسماء هذه.
تشغيل kubectl / الحصول على الإخراج ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n regular pod/bash created ❯() kubectl get pod bash -o yaml -n regular apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: regular spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi
كما ترون ، على الرغم من حقيقة أننا لم نغير الطريقة التي يتم بها إنشاء pod ، فإن حقل المورد تم ملؤه الآن. قد تلاحظ أيضًا التعليق التوضيحي الذي تم إنشاؤه تلقائيًا بواسطة LimitRanger.
أنشئ الآن في مساحة اسم خفيفة:
تشغيل kubectl / الحصول على الإخراج ❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight pod/bash created ❯() kubectl get pods -n lightweight bash -o yaml apiVersion: v1 kind: Pod metadata: annotations: kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu, memory request for container bash; cpu, memory limit for container bash' labels: run: bash name: bash namespace: lightweight spec: containers: - image: bash name: bash resources: limits: cpu: 700m memory: 900Mi requests: cpu: 500m memory: 512Mi
يرجى ملاحظة أن الموارد في الموقد هي نفسها كما في المثال السابق. هذا لأنه في حالة عدة LimitRange ، سيتم استخدام قيم افتراضية أقل صرامة عند إنشاء القرون. ولكن لماذا إذن نحتاج إلى تقييد محدود؟ سيتم استخدامه للتحقق من الحد الأقصى والحد الأدنى لقيم الموارد. للتوضيح ، سنجعل ClusterLimit المحدودة أكثر محدودية:
تقييدا-limit.yaml apiVersion: limit.myafq.com/v1alpha1 kind: ClusterLimit metadata: name: restrictive-limit spec: namespaceSelector: matchLabels: limit: "restrictive" limitRange: limits: - type: Container max: cpu: "200m" memory: "250Mi" default: cpu: "100m" memory: "128Mi" defaultRequest: cpu: "50m" memory: "64Mi" - type: Pod max: cpu: "2" memory: "2Gi"
انتبه إلى القسم:
- type: Container max: cpu: "200m" memory: "250Mi"
الآن قمنا بتعيين 200m وحدة المعالجة المركزية و 250Mi من الذاكرة كحد أقصى للحاوية في الموقد. والآن مرة أخرى ، حاول إنشاء تحت:
❯() kubectl run --generator=run-pod/v1 --image=bash bash -n lightweight Error from server (Forbidden): pods "bash" is forbidden: [maximum cpu usage per Container is 200m, but limit is 700m., maximum memory usage per Container is 250Mi, but limit is 900Mi.]
تحتوي قيمنا الفرعية على قيم كبيرة تم تعيينها بواسطة LimitRange الافتراضي ، ولا يمكن أن تبدأ لأنها لم تجتاز فحص الحد الأقصى للموارد المسموح بها.
كان هذا مثالًا على استخدام "عامل التشغيل المحدد". جربها بنفسك واللعب مع ClusterLimit في مثيل Kubernetes المحلي.
في مستودع GitHub Limit Operator ، يمكنك العثور على بيان نشر المشغل ، وكذلك شفرة المصدر. إذا كنت ترغب في توسيع وظائف المشغل ، فستكون مهام السحب وسحب المهام موضع ترحيب!
استنتاج
تعد إدارة الموارد في Kubernetes ضرورية لاستقرار وموثوقية التطبيقات الخاصة بك. تخصيص الموارد الخاصة بك الموقد كلما كان ذلك ممكنا. واستخدم LimitRange للتأمين ضد الحالات عندما يكون ذلك غير ممكن. أتمتة إنشاء LimitRange باستخدام Limit Operator.
اتبع هذه النصائح ، وستظل مجموعتك في مأمن دائمًا من فوضى الموارد القاتلة.