اذهب؟ سحق! تعرف على مشغل الغلاف (مراجعة وفيديو للتقرير من KubeCon EU'2020)

هذا العام ، كان مؤتمر Kubernetes الأوروبي الرئيسي - KubeCon + CloudNativeCon Europe 2020 - افتراضيًا. ومع ذلك ، فإن مثل هذا التغيير في الشكل لم يمنعنا من تقديم الحديث المخطط له منذ فترة طويلة "اذهب؟ سحق! تعرف على مشغل Shell "، المخصص لمشروعنا المفتوح المصدر لمشغل shell .



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







نقدم الفيديو مع التقرير (حوالي 23 دقيقة باللغة الإنجليزية ، أكثر إفادة بكثير من المقالة) والمقتطف الرئيسي منه في شكل نصي. اذهب!



في Flant ، نعمل باستمرار على تحسين كل شيء وتشغيله تلقائيًا. اليوم سنتحدث عن مفهوم مثير آخر. تلبية البرمجة النصية shell الأصلية على السحابة !



ومع ذلك ، لنبدأ بالسياق الذي يحدث فيه كل هذا - Kubernetes.



واجهة برمجة تطبيقات Kubernetes ووحدات تحكم



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



  • الحصول على مورد بنوعه واسمه ؛
  • تغيير المورد (في هذه الحالة ، يقوم الخادم بتخزين الكائنات "الصحيحة" فقط - يتم تجاهل جميع العناصر المكونة بشكل غير صحيح أو المخصصة لأدلة أخرى) ؛
  • ( / ).


وبالتالي ، يعمل Kubernetes كنوع من خادم الملفات (لبيانات YAML) بثلاث طرق أساسية (نعم ، في الواقع ، هناك طرق أخرى ، لكننا سنحذفها في الوقت الحالي).







المشكلة هي أن الخادم يمكنه تخزين المعلومات فقط. لإنجاحه ، أنت بحاجة إلى وحدة تحكم - وهي ثاني أهم المفاهيم الأساسية في عالم Kubernetes.



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



دعنا نلقي نظرة فاحصة على عملية إنشاء النشر في Kubernetes:



  • kube-controller-managerتتلقى وحدة التحكم في النشر (المضمنة في ) معلومات حول النشر وتقوم بإنشاء مجموعة النسخ المتماثلة.
  • تقوم ReplicaSet بإنشاء نسختين متماثلتين (وحدتان) بناءً على هذه المعلومات ، ولكن لم تتم جدولة هذه القرون بعد.
  • يقوم المجدول بجدولة البودات ويضيف معلومات العقدة إلى YAMLs الخاصة بهم.
  • Kubelets إجراء تغييرات على مورد خارجي (على سبيل المثال ، Docker).


ثم يتم تكرار هذا التسلسل بأكمله بترتيب عكسي: يقوم kubelet بفحص الحاويات ، ويحسب حالة الكبسولة ، ويرسلها مرة أخرى. تحصل وحدة التحكم ReplicaSet على الحالة وتقوم بتحديث حالة مجموعة النسخ المتماثلة. يحدث نفس الشيء مع Deployment Controller ، ويحصل المستخدم أخيرًا على حالة محدثة (حالية).







مشغل شل



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







مثال بسيط: نسخ الأسرار



دعنا نلقي نظرة على مثال بسيط.



لنفترض أن لدينا مجموعة Kubernetes. يحتوي على مساحة اسم defaultمع بعض السرية mysecret. بالإضافة إلى ذلك ، هناك مساحات أسماء أخرى في المجموعة. بعضها له ملصق محدد مرفق. هدفنا هو نسخ Secret في مساحات الأسماء مع تسمية.



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



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



كيف يعمل مشغل الصدفة



مثل أحمال العمل الأخرى في Kubernetes ، يعمل مشغل shell في حجرة البيانات الخاصة به. /hooksيحتوي هذا الكبسولة على ملفات قابلة للتنفيذ في الدليل . يمكن أن تكون هذه البرامج النصية في Bash و Python و Ruby وما إلى ذلك. ونحن ندعو هذه الملفات التنفيذية السنانير .







يشترك مشغل شل في أحداث Kubernetes ويطلق هذه الخطافات استجابة لأي أحداث نحتاجها.







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



جعل المشغل في باش



نحن الآن جاهزون للتنفيذ. للقيام بذلك ، نحتاج إلى كتابة وظيفتين (بالمناسبة ، نوصي بمكتبة shell_lib ، التي تبسط إلى حد كبير كتابة الخطافات في Bash):



  • الأول مطلوب لمرحلة التكوين - يعرض سياق الربط ؛
  • يحتوي الثاني على المنطق الرئيسي للخطاف.


#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"


الخطوة التالية هي تحديد الأشياء التي نحتاجها. في حالتنا ، نحتاج إلى تتبع:



  • مصدر سري للتغييرات.
  • جميع مساحات الأسماء في المجموعة ، بحيث تعرف أي منها يتم إرفاق التسمية ؛
  • الهدف الأسرار للتأكد من أنها كلها متزامنة مع سر المصدر.


اشترك في مصدر سري



تكوين الربط بسيط بما يكفي بالنسبة له. نشير إلى أننا مهتمون بـ Secret مع وجود اسم mysecretفي مساحة الاسم default:







function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF


نتيجة لذلك ، سيتم تشغيل الخطاف عندما يتغير source secret ( src_secret) ويتلقى سياق الربط التالي:







كما ترى ، فإنه يحتوي على الاسم والكائن بأكمله.



تتبع مساحات الأسماء



أنت الآن بحاجة إلى الاشتراك في مساحات الأسماء. للقيام بذلك ، سنحدد تكوين الربط التالي:



- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false


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







يحتوي على مصفوفة filterResultsلكل مساحة اسم في الكتلة. متغير منطقي hasLabelيشير إلى ما إذا كانت التسمية مرفقة بمساحة الاسم المحددة. keepFullObjectsInMemory: falseيقول المحدد أنه ليست هناك حاجة للاحتفاظ بالكائنات الكاملة في الذاكرة.



أهداف تتبع الأسرار



نشترك في جميع الأسرار التي لها تعليق توضيحي managed-secret: "yes"(هذه هي أسرارنا المستهدفة dst_secrets):



- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false


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



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







بناءً على كل هذه المعلومات ، يمكن تطوير خوارزمية أساسية. يتكرر عبر جميع مساحات الأسماء و:



  • إذا كان hasLabelمناسبًا trueلمساحة الاسم الحالية:
    • يقارن السر العالمي بالسر المحلي:
      • إذا كانوا متماثلين ، فلن يفعلوا شيئًا ؛
      • إذا اختلفوا kubectl replaceأو نفذوا أو create؛
  • إذا كان hasLabelمناسبًا falseلمساحة الاسم الحالية:

    • يتأكد من أن Secret ليس في مساحة الاسم المحددة:
      • إذا كان السر المحلي موجودًا ، فاحذفه باستخدام kubectl delete؛
      • إذا لم يتم العثور على سر محلي ، فلن يفعل شيئًا.






يمكنك تنزيل تطبيق الخوارزمية في Bash في مستودعنا بأمثلة .



هذه هي الطريقة التي تمكنا بها من إنشاء وحدة تحكم Kubernetes بسيطة باستخدام 35 سطرًا من تكوينات YAML ونفس المقدار تقريبًا من كود Bash! وظيفة مشغل الصدفة هو تجميعها معًا.



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



مثال 1: إجراء تغييرات على ConfigMap



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



افترض الآن أن ConfigMap قد تغير (الإصدار 2). ومع ذلك ، ستستخدم البودات الإصدار القديم من ConfigMap (الإصدار 1):







كيف يمكنني حملها على الترحيل إلى ConfigMap الجديد (الإصدار 2)؟ الجواب بسيط: استخدم النموذج. دعنا نضيف تعليقًا توضيحيًا بالمجموع الاختباري إلى قسم templateتكوين النشر:







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



إذا قام المستخدم بإجراء تغييرات على ConfigMap ، فسوف يلاحظها مشغل shell ويعيد حساب المجموع الاختباري. ثم يأتي دور سحر Kubernetes: سيقتل المنسق الكبسولة ، ويخلق واحدة جديدة ، وينتظر حتى يصبح Ready، وينتقل إلى التالي. نتيجة لذلك ، ستتم مزامنة عملية النشر والترحيل إلى الإصدار الجديد من ConfigMap.







مثال 2: العمل مع تعريفات الموارد المخصصة



كما تعلم ، يتيح لك Kubernetes إنشاء أنواع (أنواع) مخصصة من الكائنات. على سبيل المثال ، يمكنك إنشاء نوع MysqlDatabase. لنفترض أن هذا النوع يحتوي على معلمتين للبيانات الوصفية: nameوnamespace.



apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar


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







مثال 3: مراقبة شبكة عنقودية



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



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



configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"


executeHookOnEvent: []تمنع المعلمة إطلاق الخطاف استجابة لأي حدث (أي استجابة للتغييرات والإضافات وحذف العقد). ومع ذلك ، سيتم تشغيله (وتحديث قائمة المضيف) وفقًا لجدول زمني - كل دقيقة ، وفقًا لما يمليه المجال schedule.



الآن السؤال الذي يطرح نفسه ، كيف نعرف بالضبط عن مشاكل مثل فقدان الحزمة؟ دعنا نلقي نظرة على الكود:



function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}


نحن نكرر قائمة العقد ، ونحصل على أسمائها وعناوين IP الخاصة بها ، ونرسل النتائج إلى Prometheus. يمكن لمشغل Shell تصدير المقاييس إلى Prometheus ، وحفظها في ملف يقع وفقًا للمسار المحدد في متغير البيئة $METRICS_PATH.



هذه هي الطريقة التي يمكنك بها تشغيل عامل لمراقبة بسيطة للشبكة في مجموعة.



آلية قائمة الانتظار



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



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


لحسن الحظ ، فإن مشغل القشرة لديه آلية انتظار مدمجة. يتم وضع جميع الأحداث في قائمة الانتظار ومعالجتها بالتسلسل.



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



أيضًا ، يمكن دمج هذه الأحداث في حدث واحد كبير . المعلمة groupفي تكوين الربط هي المسؤولة عن ذلك .







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







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



خاتمة



تحدثنا عن ماهية مشغل shell ، وأوضحنا كيف يمكن استخدامه لإنشاء مشغلات Kubernetes بسرعة وبسهولة ، وقدمنا ​​العديد من الأمثلة على استخدامه.



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



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







مقاطع الفيديو والشرائح



فيديو من العرض (حوالي 23 دقيقة):





عرض التقرير:







ملاحظة



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






All Articles