Çərçivə və SDK olmadan Python-da Kubernetes Operatoru

Çərçivə və SDK olmadan Python-da Kubernetes Operatoru

Go hazırda insanların Kubernetes üçün bəyanatlar yazmağı seçdiyi proqramlaşdırma dillərində monopoliyaya malikdir. Bunun obyektiv səbəbləri var, məsələn:

  1. Go-da operatorları inkişaf etdirmək üçün güclü bir çərçivə var - Operator SDK.
  2. Docker və Kubernetes kimi oyunu dəyişdirən proqramlar Go-da yazılmışdır. Operatorunuzu Go-da yazmaq ekosistemlə eyni dildə danışmaq deməkdir.
  3. Go proqramlarının yüksək performansı və qutudan kənar paralelliklə işləmək üçün sadə alətlər.

NB: Yeri gəlmişkən, Go-da öz bəyanatınızı necə yazmaq olar, biz artıq təsvir edilmişdir xarici müəlliflərin tərcümələrimizdən birində.

Bəs vaxtın azlığı və ya sadəcə olaraq motivasiya ilə getməyi öyrənməyə mane olursan? Məqalə, demək olar ki, hər bir DevOps mühəndisinin bildiyi ən populyar dillərdən birini istifadə edərək necə yaxşı bir ifadə yaza biləcəyiniz nümunəsini təqdim edir - Python.

Tanış olun: Kopirayter - surətçıxarma operatoru!

Nümunə olaraq, ya yeni ad məkanı görünəndə və ya iki obyektdən biri dəyişdikdə: ConfigMap və Secret-dən biri dəyişdikdə ConfigMap-ın surətini çıxarmaq üçün nəzərdə tutulmuş sadə bəyanatın hazırlanmasını nəzərdən keçirək. Praktik baxımdan, operator tətbiq konfiqurasiyalarının kütləvi şəkildə yenilənməsi (ConfigMap-ı yeniləməklə) və ya məxfi məlumatların yenilənməsi üçün faydalı ola bilər - məsələn, Docker Registry ilə işləmək üçün açarlar (ad sahəsinə Gizli əlavə edərkən).

Belə ki, yaxşı operator nəyə sahib olmalıdır:

  1. Operatorla qarşılıqlı əlaqə istifadə edərək həyata keçirilir Xüsusi Resurs Tərifləri (bundan sonra CRD adlandırılacaq).
  2. Operator konfiqurasiya edilə bilər. Bunun üçün komanda xətti bayraqlarından və mühit dəyişənlərindən istifadə edəcəyik.
  3. Docker konteynerinin və Helm diaqramının qurulması istifadəçilərin asanlıqla (sözün əsl mənasında bir əmrlə) operatoru Kubernetes klasterinə quraşdıra bilməsi üçün hazırlanmışdır.

CRD

Operatorun hansı resursları axtaracağını və hara baxacağını bilməsi üçün onun üçün bir qayda qoymalıyıq. Hər bir qayda vahid CRD obyekti kimi təqdim olunacaq. Bu CRD hansı sahələrə malik olmalıdır?

  1. Resurs növü, axtaracağımız (ConfigMap və ya Gizli).
  2. Ad boşluqlarının siyahısı, resursların yerləşdiyi yer.
  3. Selector, bunun vasitəsilə ad məkanında resursları axtaracağıq.

CRD-ni təsvir edək:

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

Və biz onu dərhal yaradacağıq sadə qayda — ad məkanında adla axtarış etmək default kimi etiketləri olan bütün ConfigMap copyrator: "true":

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

Hazır! İndi birtəhər qaydamız haqqında məlumat almalıyıq. Dərhal qeyd edim ki, klaster API Serverinə özümüz sorğu yazmayacağıq. Bunun üçün biz hazır Python kitabxanasından istifadə edəcəyik kubernetes-müştəri:

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')}

Bu kodu işlətmək nəticəsində aşağıdakıları əldə edirik:

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

Əla: operator üçün bir qayda əldə edə bildik. Ən əsası, biz Kubernetes yolu deyilən şeyi etdik.

Ətraf mühit dəyişənləri və ya bayraqlar? Hər şeyi alırıq!

Əsas operator konfiqurasiyasına keçək. Tətbiqləri konfiqurasiya etmək üçün iki əsas yanaşma var:

  1. komanda xətti seçimlərindən istifadə edin;
  2. mühit dəyişənlərindən istifadə edin.

Komanda xətti seçimləri məlumat növü dəstəyi və doğrulama ilə parametrləri daha çevik oxumağa imkan verir. Python-un standart kitabxanasında modul var argparser, istifadə edəcəyik. Onun imkanlarının təfərrüatları və nümunələri burada mövcuddur rəsmi sənədlər.

Bizim vəziyyətimiz üçün əmr satırı bayraqlarının oxunuşunun qurulması nümunəsi belə görünür:

   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()

Digər tərəfdən, Kubernetes-də mühit dəyişənlərindən istifadə edərək, konteynerin içərisindəki pod haqqında xidmət məlumatlarını asanlıqla ötürə bilərsiniz. Məsələn, podun aşağıdakı konstruksiya ilə işlədiyi ad sahəsi haqqında məlumat əldə edə bilərik:

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

Operator məntiqi

ConfigMap və Secret ilə işləmək üsullarını necə ayırmağı başa düşmək üçün xüsusi xəritələrdən istifadə edəcəyik. Sonra obyekti izləmək və yaratmaq üçün hansı üsullara ehtiyacımız olduğunu anlaya bilərik:

LIST_TYPES_MAP = {
    'configmap': 'list_namespaced_config_map',
    'secret': 'list_namespaced_secret',
}

CREATE_TYPES_MAP = {
    'configmap': 'create_namespaced_config_map',
    'secret': 'create_namespaced_secret',
}

Sonra, API serverindən hadisələri qəbul etməlisiniz. Bunu aşağıdakı kimi həyata keçirək:

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)

Hadisəni aldıqdan sonra onun işlənməsinin əsas məntiqinə keçirik:

# Типы событий, на которые будем реагировать
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)

Əsas məntiq hazırdır! İndi bütün bunları bir Python paketinə yığmalıyıq. Faylı hazırlayırıq setup.py, orada layihə haqqında meta məlumat yazın:

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='[email protected]',
    include_package_data=True,
    install_requires=[
        'kubernetes==9.0.0',
    ],
    entry_points={
        'console_scripts': [
            '{0} = {0}.cli:main'.format(_NAME),
        ]
    }
)

NB: Python üçün kubernetes müştərisinin öz versiyası var. Müştəri versiyaları ilə Kubernetes versiyaları arasında uyğunluq haqqında daha çox məlumatı burada tapa bilərsiniz uyğunluq matrisləri.

İndi layihəmiz belə görünür:

copyrator
├── copyrator
│   ├── cli.py # Логика работы с командной строкой
│   ├── constant.py # Константы, которые мы приводили выше
│   ├── load_crd.py # Логика загрузки CRD
│   └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета

Docker və Helm

Dockerfile inanılmaz dərəcədə sadə olacaq: əsas piton-alp şəklini götürün və paketimizi quraşdırın. Onun optimallaşdırılmasını daha yaxşı vaxtlara qədər təxirə salaq:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operator üçün yerləşdirmə də çox sadədir:

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

Nəhayət, lazımi hüquqlara malik operator üçün uyğun bir rol yaratmalısınız:

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 }}

Ümumi

Beləliklə, qorxmadan, məzəmmət etmədən və ya Go öyrənmədən Python-da Kubernetes üçün öz operatorumuzu qura bildik. Əlbətdə ki, onun hələ də böyümək üçün yeri var: gələcəkdə o, bir çox qaydaları emal edə, bir neçə mövzuda işləyə, CRD-lərindəki dəyişiklikləri müstəqil izləyə biləcək...

Sizə koda daha yaxından baxmaq üçün onu daxil etdik ictimai depo. Python istifadə edərək həyata keçirilən daha ciddi operatorlara dair nümunələr istəyirsinizsə, mongodb tətbiqi üçün diqqətinizi iki operatora yönəldə bilərsiniz.ilk и ikinci).

PS Kubernetes hadisələri ilə məşğul olmaq üçün çox tənbəlsinizsə və ya sadəcə Bash-dan istifadə etməyə daha çox öyrəşmisinizsə, həmkarlarımız formada hazır bir həll hazırladılar. mərmi operatoru (Biz elan edildi aprel ayında).

PPS

Bloqumuzda da oxuyun:

Mənbə: www.habr.com

Добавить комментарий