Python-dagi Kubernetes operatori ramkalar va SDKsiz

Python-dagi Kubernetes operatori ramkalar va SDKsiz

Go hozirda odamlar Kubernetes uchun bayonot yozishni tanlaydigan dasturlash tillarida monopoliyaga ega. Buning ob'ektiv sabablari bor, masalan:

  1. Go'da operatorlarni ishlab chiqish uchun kuchli ramka mavjud - Operator SDK.
  2. Docker va Kubernetes kabi o'yinni o'zgartiruvchi ilovalar Go'da yozilgan. Go'da operatoringizni yozish ekotizim bilan bir xil tilda gaplashishni anglatadi.
  3. Go ilovalarining yuqori unumdorligi va parallellik bilan ishlash uchun oddiy vositalar.

NB: Aytgancha, Go'da o'z bayonotingizni qanday yozish kerak, biz allaqachon tasvirlangan xorijlik mualliflarning tarjimalarimizdan birida.

Ammo vaqt yo'qligi yoki oddiy qilib aytganda, motivatsiya tufayli borishni o'rganishga to'sqinlik qilsa-chi? Maqolada deyarli har bir DevOps muhandisi biladigan eng mashhur tillardan birini ishlatib, qanday qilib yaxshi bayonot yozishingiz mumkinligiga misol keltirilgan - Python.

Tanishing: Nusxa ko'chirish apparati - nusxa ko'chirish operatori!

Misol sifatida, yangi nom maydoni paydo bo'lganda yoki ikkita ob'ektdan biri o'zgarganda ConfigMap-ni nusxalash uchun mo'ljallangan oddiy bayonotni ishlab chiqishni ko'rib chiqing: ConfigMap va Secret. Amaliy nuqtai nazardan, operator dastur konfiguratsiyasini ommaviy yangilash (ConfigMap-ni yangilash orqali) yoki maxfiy ma'lumotlarni yangilash uchun foydali bo'lishi mumkin - masalan, Docker Registry bilan ishlash uchun kalitlar (nomlar maydoniga Secret qo'shganda).

Va shunday qilib, yaxshi operator nima bo'lishi kerak:

  1. Operator bilan o'zaro aloqalar yordamida amalga oshiriladi Maxsus manba ta'riflari (keyingi o'rinlarda CRD deb yuritiladi).
  2. Operatorni sozlash mumkin. Buning uchun biz buyruq qatori bayroqlari va muhit o'zgaruvchilaridan foydalanamiz.
  3. Docker konteyneri va Helm diagrammasi foydalanuvchilar operatorni Kubernetes klasteriga osongina o'rnatishlari uchun (so'zma-so'z bitta buyruq bilan) yaratilgan.

CRD

Operator qanday resurslarni izlash va qaerga qarash kerakligini bilishi uchun biz unga qoida o'rnatishimiz kerak. Har bir qoida bitta CRD obyekti sifatida taqdim etiladi. Ushbu CRD qanday maydonlarga ega bo'lishi kerak?

  1. Resurs turi, biz qidiramiz (ConfigMap yoki Secret).
  2. Nom maydonlari ro'yxati, unda resurslar joylashishi kerak.
  3. Selektor, bu orqali biz nomlar maydonida resurslarni qidiramiz.

Keling, CRD ni tavsiflaymiz:

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

Va biz uni darhol yaratamiz oddiy qoida — nomlar maydonida nom bilan qidirish uchun default kabi teglar bilan barcha ConfigMap copyrator: "true":

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

Tayyor! Endi biz qandaydir tarzda bizning qoidamiz haqida ma'lumot olishimiz kerak. Darhol band qilishimga ruxsat bering, biz o'zimiz klaster API serveriga so'rov yozmaymiz. Buning uchun biz tayyor Python kutubxonasidan foydalanamiz kubernetes-mijoz:

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

Ushbu kodni ishga tushirish natijasida biz quyidagilarni olamiz:

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

Ajoyib: biz operator uchun qoida olishga muvaffaq bo'ldik. Va eng muhimi, biz Kubernetes usuli deb ataladigan narsani qildik.

Atrof-muhit o'zgaruvchilari yoki bayroqlar? Biz hamma narsani olamiz!

Keling, asosiy operator konfiguratsiyasiga o'tamiz. Ilovalarni sozlashning ikkita asosiy usuli mavjud:

  1. buyruq qatori parametrlaridan foydalanish;
  2. muhit o'zgaruvchilardan foydalaning.

Buyruqlar qatori opsiyalari maʼlumotlar turini qoʻllab-quvvatlash va tekshirish yordamida sozlamalarni yanada moslashuvchan oʻqish imkonini beradi. Python standart kutubxonasida modul mavjud argparser, biz foydalanamiz. Uning imkoniyatlari haqida batafsil ma'lumot va misollar mavjud rasmiy hujjatlar.

Bizning holatimizda buyruq qatori bayroqlarini o'qish misoli quyidagicha ko'rinadi:

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

Boshqa tomondan, Kubernetes-dagi muhit o'zgaruvchilari yordamida siz konteyner ichidagi pod haqida xizmat ma'lumotlarini osongina uzatishingiz mumkin. Masalan, biz quyidagi konstruktsiya bilan pod ishlayotgan nomlar maydoni haqida ma'lumot olishimiz mumkin:

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

Operator mantig'i

ConfigMap va Secret bilan ishlash usullarini qanday ajratish kerakligini tushunish uchun biz maxsus xaritalardan foydalanamiz. Keyin ob'ektni kuzatish va yaratish uchun qanday usullar kerakligini tushunishimiz mumkin:

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

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

Keyinchalik, API serveridan voqealarni qabul qilishingiz kerak. Keling, buni quyidagicha amalga oshiramiz:

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)

Hodisani olgandan so'ng, biz uni qayta ishlashning asosiy mantig'iga o'tamiz:

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

Asosiy mantiq tayyor! Endi biz bularning barchasini bitta Python paketiga to'plashimiz kerak. Biz faylni tayyorlaymiz setup.py, u erda loyiha haqida meta-ma'lumot yozing:

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 uchun kubernetes mijozi o'z versiyasiga ega. Mijoz versiyalari va Kubernetes versiyalari o'rtasidagi muvofiqlik haqida qo'shimcha ma'lumotni quyidagi havolada topishingiz mumkin muvofiqlik matritsalari.

Endi bizning loyihamiz quyidagicha ko'rinadi:

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

Docker va Helm

Dockerfile nihoyatda sodda bo'ladi: asosiy python-alp tasvirini oling va paketimizni o'rnating. Keling, uni optimallashtirishni yaxshiroq vaqtga qoldiramiz:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operator uchun joylashtirish ham juda oddiy:

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

Va nihoyat, kerakli huquqlarga ega bo'lgan operator uchun tegishli rolni yaratishingiz kerak:

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

Xulosa

Shunday qilib, qo'rqmasdan, tanbehsiz yoki Go-ni o'rganmasdan, biz Python-da Kubernetes uchun o'z operatorimizni yaratishga muvaffaq bo'ldik. Albatta, u hali o'sishi uchun joy bor: kelajakda u bir nechta qoidalarni qayta ishlash, bir nechta iplarda ishlash, o'z CRDlaridagi o'zgarishlarni mustaqil ravishda kuzatish imkoniyatiga ega bo'ladi...

Kodni batafsil ko'rib chiqish uchun biz uni kiritdik jamoat ombori. Agar siz Python yordamida amalga oshirilgan jiddiyroq operatorlarga misollar olishni istasangiz, e'tiboringizni mongodb-ni joylashtirish uchun ikkita operatorga qaratishingiz mumkin (первый и ikkinchi).

PS Va agar siz Kubernetes voqealari bilan shug'ullanish uchun juda dangasa bo'lsangiz yoki oddiygina Bash-dan foydalanishga odatlangan bo'lsangiz, bizning hamkasblarimiz shaklda tayyor echimni tayyorladilar. qobiq operatori (Biz e'lon qilindi aprelda).

PPS

Shuningdek, bizning blogimizda o'qing:

Manba: www.habr.com

a Izoh qo'shish