Кубернетес Оператор у Питхон-у без оквира и СДК-а

Кубернетес Оператор у Питхон-у без оквира и СДК-а

Го тренутно има монопол на програмске језике које људи бирају да пишу изјаве за Кубернетес. За то постоје објективни разлози, као што су:

  1. Постоји моћан оквир за развој оператера у Го - Оператор СДК.
  2. Апликације које мењају игре као што су Доцкер и Кубернетес су написане у Го. Писати свог оператера у Го значи говорити истим језиком са екосистемом.
  3. Високе перформансе Го апликација и једноставних алата за рад са паралелношћу из кутије.

NB: Узгред, како написати сопствену изјаву у Го, ми већ описано у једном од наших превода страних аутора.

Али шта ако вас у учењу Го спрече недостатак времена или, једноставно речено, мотивација? Чланак даје пример како можете написати добру изјаву користећи један од најпопуларнијих језика које скоро сваки ДевОпс инжењер зна - Питон.

Упознајте: Копир апарат - копир оператер!

Као пример, размислите о развоју једноставне изјаве дизајниране да копира ЦонфигМап било када се појави нови простор имена или када се промени један од два ентитета: ЦонфигМап и Сецрет. Са практичне тачке гледишта, оператор може бити користан за масовно ажурирање конфигурација апликације (ажурирањем ЦонфигМап-а) или за ажурирање тајних података – на пример, кључева за рад са Доцкер Регистри-ом (приликом додавања Сецрет у именски простор).

Дакле, шта треба да има добар оператер:

  1. Интеракција са оператером се врши помоћу Прилагођене дефиниције ресурса (у даљем тексту ЦРД).
  2. Оператер се може конфигурисати. Да бисмо то урадили, користићемо заставице командне линије и променљиве окружења.
  3. Израда Доцкер контејнера и Хелм графикона је дизајнирана тако да корисници могу лако (буквално једном командом) да инсталирају оператера у свој Кубернетес кластер.

ЦРД

Да би оператер знао које ресурсе да тражи и где да тражи, морамо да поставимо правило за њега. Свако правило ће бити представљено као један ЦРД објекат. Која поља треба да има овај ЦРД?

  1. Врста ресурса, који ћемо тражити (ЦонфигМап или Сецрет).
  2. Листа именских простора, у којој треба да се налазе ресурси.
  3. Селецтор, помоћу које ћемо тражити ресурсе у именском простору.

Хајде да опишемо ЦРД:

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 све ЦонфигМап са ознакама попут copyrator: "true":

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

Спремни! Сада треба некако да се информишемо о нашем правилу. Одмах да резервишем да нећемо сами писати захтеве АПИ серверу кластера. Да бисмо то урадили, користићемо готову Питхон библиотеку кубернетес-клијент:

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

Одлично: успели смо да добијемо правило за оператера. И што је најважније, урадили смо оно што се зове Кубернетес начин.

Променљиве окружења или заставице? Узимамо све!

Пређимо на главну конфигурацију оператера. Постоје два основна приступа конфигурисању апликација:

  1. користите опције командне линије;
  2. користите променљиве окружења.

Опције командне линије вам омогућавају да флексибилније читате подешавања, уз подршку за типове података и валидацију. Пајтонова стандардна библиотека има модул 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()

С друге стране, користећи променљиве окружења у Кубернетес-у, можете лако пренети сервисне информације о поду унутар контејнера. На пример, можемо добити информације о именском простору у којем се под покреће са следећом конструкцијом:

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

Операторска логика

Да бисмо разумели како да одвојимо методе за рад са ЦонфигМап и Сецрет, користићемо посебне мапе. Тада можемо разумети које методе треба да пратимо и креирамо објекат:

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

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

Затим морате да примате догађаје са АПИ сервера. Хајде да га имплементирамо на следећи начин:

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)

Главна логика је спремна! Сада све ово треба да спакујемо у један Пајтон пакет. Припремамо датотеку 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='[email protected]',
    include_package_data=True,
    install_requires=[
        'kubernetes==9.0.0',
    ],
    entry_points={
        'console_scripts': [
            '{0} = {0}.cli:main'.format(_NAME),
        ]
    }
)

NB: Кубернетес клијент за Питхон има своје верзије. Више информација о компатибилности између верзија клијента и Кубернетес верзија можете пронаћи у матрице компатибилности.

Сада наш пројекат изгледа овако:

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

Доцкер и Хелм

Доцкерфиле ће бити невероватно једноставан: узмите основну питхон-алпине слику и инсталирајте наш пакет. Хајде да одложимо његову оптимизацију до бољих времена:

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

Укупан

Тако смо, без страха, прекора или учења Го, могли да направимо сопствени оператер за Кубернетес у Питхон-у. Наравно, још увек има простора за раст: у будућности ће моћи да обрађује више правила, ради у више нити, независно прати промене у својим ЦРД-овима...

Да бисмо вам ближе погледали код, ставили смо га јавно складиште. Ако желите примере озбиљнијих оператора имплементираних помоћу Питхон-а, можете скренути пажњу на два оператера за примену монгодб (первыи и други).

ПС А ако сте превише лењи да се бавите Кубернетес догађајима или сте једноставно више навикли да користите Басх, наше колеге су припремиле готово решење у облику схелл-оператор (Ми најавио то у априлу).

Ппс

Прочитајте и на нашем блогу:

Извор: ввв.хабр.цом

Додај коментар