مشغل Kubernetes في بيثون بدون أطر عمل و SDK



تعد Go حاليًا محتكرًا من بين لغات البرمجة التي يختار الأشخاص كتابتها عن Kubernetes. هناك أسباب موضوعية مثل:

  1. هناك إطار قوي لتطوير المشغلين على Go - Operator SDK .
  2. كتب Go تطبيقات مقلوبة مثل Docker و Kubernetes. لكتابة المشغل الخاص بك في Go - تحدث بنفس اللغة مع النظام البيئي.
  3. تطبيقات عالية الأداء على الذهاب وأدوات بسيطة للعمل مع التزامن من خارج منطقة الجزاء.

ملحوظة : بالمناسبة ، وصفنا بالفعل كيفية كتابة المشغل الخاص بك على الذهاب في واحدة من ترجماتنا من قبل المؤلفين الأجانب.

ولكن ماذا لو تم منع تعلم Go بسبب ضيق الوقت أو الدافع بشكل تافه؟ تقدم هذه المقالة مثالاً عن كيفية كتابة عامل تشغيل صلب باستخدام واحدة من أكثر اللغات شعبية التي يعرفها كل مهندس DevOps تقريبًا - Python .

يجتمع: Copywriter - نسخة المشغل!


على سبيل المثال ، ضع في اعتبارك تطوير مشغل بسيط مصمم لنسخ ConfigMap إما عند ظهور مساحة اسم جديدة ، أو عندما يتغير أحد الكيانين: ConfigMap و Secret. من وجهة نظر التطبيق العملي ، يمكن للمشغل أن يكون مفيدًا للتحديث الشامل لتكوينات التطبيق (عن طريق تحديث ConfigMap) أو لتحديث البيانات السرية - على سبيل المثال ، مفاتيح العمل مع Docker Registry (عند إضافة Secret إلى مساحة الاسم).

إذن ما يجب أن يكون لدى المشغل الجيد :

  1. يتم التفاعل مع المشغل باستخدام تعريفات الموارد المخصصة (المشار إليها فيما يلي - CRD).
  2. المشغل يمكن تخصيصها. للقيام بذلك ، سوف نستخدم علامات سطر الأوامر ومتغيرات البيئة.
  3. يجري العمل على تجميع حاوية Docker ومخطط Helm بحيث يمكن للمستخدمين بسهولة (حرفيًا باستخدام أمر واحد) تثبيت المشغل في نظام Kubernetes الخاص بهم.

CRD


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

  1. نوع المورد الذي نبحث عنه (ConfigMap أو Secret).
  2. قائمة مساحة الاسم حيث يجب أن توجد الموارد.
  3. محدد ، والذي سنبحث عن الموارد في مساحة الاسم.

نحن تصف CRD:

apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: copyrator.flant.com spec: group: flant.com versions: - name: v1 served: true storage: true scope: Namespaced names: plural: copyrators singular: copyrator kind: CopyratorRule shortNames: - copyr validation: openAPIV3Schema: type: object properties: ruleType: type: string namespaces: type: array items: type: string selector: type: string 

وعلى الفور إنشاء قاعدة بسيطة - للبحث في مساحة الاسم مع الاسم default لجميع ConfigMap مع تسميات مثل copyrator: "true" :

 apiVersion: flant.com/v1 kind: CopyratorRule metadata: name: main-rule labels: module: copyrator ruleType: configmap selector: copyrator: "true" namespace: default 

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

 import kubernetes from contextlib import suppress CRD_GROUP = 'flant.com' CRD_VERSION = 'v1' CRD_PLURAL = 'copyrators' def load_crd(namespace, name): client = kubernetes.client.ApiClient() custom_api = kubernetes.client.CustomObjectsApi(client) with suppress(kubernetes.client.api_client.ApiException): crd = custom_api.get_namespaced_custom_object( CRD_GROUP, CRD_VERSION, namespace, CRD_PLURAL, name, ) return {x: crd[x] for x in ('ruleType', 'selector', 'namespace')} 

نتيجة لهذا الرمز ، حصلنا على ما يلي:

 {'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']} 

ممتاز: تمكنا من الحصول على قاعدة للمشغل. والأهم من ذلك أننا قمنا بما يسمى طريقة Kubernetes.

متغيرات البيئة أو الأعلام؟ نحن نأخذ كل شيء!


نمر إلى التكوين الرئيسي للمشغل. هناك طريقتان أساسيتان لتكوين التطبيقات:

  1. استخدم خيارات سطر الأوامر
  2. استخدام متغيرات البيئة.

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

إليك ما يبدو عليه مثال إعداد علامات سطر الأوامر على حالتنا:

  parser = ArgumentParser( description='Copyrator - copy operator.', prog='copyrator' ) parser.add_argument( '--namespace', type=str, default=getenv('NAMESPACE', 'default'), help='Operator Namespace' ) parser.add_argument( '--rule-name', type=str, default=getenv('RULE_NAME', 'main-rule'), help='CRD Name' ) args = parser.parse_args() 

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

 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace 

منطق المشغل


لفهم كيفية فصل أساليب العمل مع ConfigMap و Secret ، سنستخدم بطاقات خاصة. بعد ذلك يمكننا أن نفهم ما هي الطرق التي نحتاجها لتتبع وإنشاء الكائن:

 LIST_TYPES_MAP = { 'configmap': 'list_namespaced_config_map', 'secret': 'list_namespaced_secret', } CREATE_TYPES_MAP = { 'configmap': 'create_namespaced_config_map', 'secret': 'create_namespaced_secret', } 

بعد ذلك ، تحتاج إلى تلقي الأحداث من خادم API. نطبقها على النحو التالي:

 def handle(specs): kubernetes.config.load_incluster_config() v1 = kubernetes.client.CoreV1Api() #       method = getattr(v1, LIST_TYPES_MAP[specs['ruleType']]) func = partial(method, specs['namespace']) w = kubernetes.watch.Watch() for event in w.stream(func, _request_timeout=60): handle_event(v1, specs, event) 

بعد تلقي الحدث ، ننتقل إلى المنطق الرئيسي لمعالجته:

 #  ,     ALLOWED_EVENT_TYPES = {'ADDED', 'UPDATED'} def handle_event(v1, specs, event): if event['type'] not in ALLOWED_EVENT_TYPES: return object_ = event['object'] labels = object_['metadata'].get('labels', {}) #    selector' for key, value in specs['selector'].items(): if labels.get(key) != value: return #   namespace' namespaces = map( lambda x: x.metadata.name, filter( lambda x: x.status.phase == 'Active', v1.list_namespace().items ) ) for namespace in namespaces: #  ,  namespace object_['metadata'] = { 'labels': object_['metadata']['labels'], 'namespace': namespace, 'name': object_['metadata']['name'], } #   /  methodcaller( CREATE_TYPES_MAP[specs['ruleType']], namespace, object_ )(v1) 

المنطق الأساسي جاهز! الآن تحتاج إلى حزم كل هذا في حزمة Python واحدة. نحن ننفذ setup.py ، نكتب المعلومات الوصفية حول المشروع هناك:

 from sys import version_info from setuptools import find_packages, setup if version_info[:2] < (3, 5): raise RuntimeError( 'Unsupported python version %s.' % '.'.join(version_info) ) _NAME = 'copyrator' setup( name=_NAME, version='0.0.1', packages=find_packages(), classifiers=[ 'Development Status :: 3 - Alpha', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], author='Flant', author_email='maksim.nabokikh@flant.com', include_package_data=True, install_requires=[ 'kubernetes==9.0.0', ], entry_points={ 'console_scripts': [ '{0} = {0}.cli:main'.format(_NAME), ] } ) 

ملاحظة : عميل kubernetes لبيثون لديه إصدار خاص به. يمكنك معرفة المزيد حول التوافق بين إصدارات العميل وإصدارات Kubernetes من مصفوفة التوافق .

الآن يبدو مشروعنا كما يلي:

 copyrator ├── copyrator │ ├── cli.py #      │ ├── constant.py # ,     │ ├── load_crd.py #   CRD │ └── operator.py #     └── setup.py #   

عامل الميناء وهيلم


سيكون Dockerfile بسيطًا بشكل فظيع: قم بالتقاط صورة python-alpine الأساسية وتثبيت الحزمة الخاصة بنا. سوف نؤجل تحسينه إلى أوقات أفضل:

 FROM python:3.7.3-alpine3.9 ADD . /app RUN pip3 install /app ENTRYPOINT ["copyrator"] 

النشر للمشغل بسيط جدًا أيضًا:

 apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Chart.Name }} spec: selector: matchLabels: name: {{ .Chart.Name }} template: metadata: labels: name: {{ .Chart.Name }} spec: containers: - name: {{ .Chart.Name }} image: privaterepo.yourcompany.com/copyrator:latest imagePullPolicy: Always args: ["--rule-type", "main-rule"] env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace serviceAccountName: {{ .Chart.Name }}-acc 

أخيرًا ، يجب عليك إنشاء الدور المناسب للمشغل باستخدام الحقوق اللازمة:

 apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Chart.Name }}-acc --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: {{ .Chart.Name }} rules: - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["secrets", "configmaps"] verbs: ["*"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: {{ .Chart.Name }} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: {{ .Chart.Name }} subjects: - kind: ServiceAccount name: {{ .Chart.Name }}-acc 

يؤدي


لذلك ، من دون خوف ، والتوبيخ والتعلم ، تمكنا من تشكيل مشغلنا الخاص ل Kubernetes في بيثون. بالطبع ، لا يزال لديه مجال للنمو: في المستقبل ، سيكون قادرًا على معالجة عدة قواعد ، والعمل في عدة سلاسل ، ومراقبة التغييرات في CRD بشكل مستقل ...

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

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

PPS


اقرأ أيضًا في مدونتنا:

Source: https://habr.com/ru/post/ar459320/


All Articles