Оператори Kubernetes дар Python бидуни чаҳорчӯба ва SDK

Оператори Kubernetes дар Python бидуни чаҳорчӯба ва SDK

Дар айни замон Go монополияи забонҳои барномасозӣ дорад, ки одамон барои Kubernetes изҳорот менависанд. Сабабҳои объективии ин вуҷуд доранд, аз қабили:

  1. Барои таҳияи операторҳо дар Go як чаҳорчӯбаи пурқувват мавҷуд аст - Оператор SDK.
  2. Барномаҳои тағирёбандаи бозӣ ба монанди Docker ва Kubernetes дар Go навишта шудаанд. Навиштани оператори худ дар Go маънои бо экосистема бо як забон гап заданро дорад.
  3. Иҷрои баланди барномаҳои Go ва асбобҳои оддӣ барои кор бо ҳамзамон берун аз қуттӣ.

NB: Дар омади гап, баёноти худро дар Go чӣ гуна нависед, мо аллакай тасвир шудааст дар яке аз тарчимахои муаллифони хоричй.

Аммо чӣ мешавад, агар ба шумо нарасидани вақт ё ба таври оддӣ гӯем, ҳавасмандӣ монеъ шуда бошед? Дар мақола мисол оварда шудааст, ки чӣ гуна шумо метавонед бо истифода аз яке аз забонҳои маъмултарин, ки қариб ҳар як муҳандиси DevOps медонад, изҳороти хуб нависед - Python.

Вохӯред: нусхабардорӣ - оператори нусхабардорӣ!

Ҳамчун мисол, таҳияи изҳороти оддиеро, ки барои нусхабардории ConfigMap тарҳрезӣ шудааст ё ҳангоми пайдо шудани фазои нави ном ё ҳангоми тағир додани яке аз ду объект: ConfigMap ва Secret баррасӣ кунед. Аз нуқтаи назари амалӣ, оператор метавонад барои навсозии васеи конфигуратсияҳои барномаҳо (бо навсозии ConfigMap) ё барои навсозии маълумоти махфӣ муфид бошад - масалан, калидҳо барои кор бо Реестри Docker (ҳангоми илова кардани Сир ба фазои ном).

Ва ҳамин тавр, оператори хуб бояд чиро дошта бошад:

  1. Муносибати мутақобила бо оператор истифода бурда мешавад Таърифҳои захираҳои фармоишӣ (минбаъд CRD номида мешавад).
  2. Оператор метавонад танзим карда шавад. Барои ин, мо парчамҳои сатри фармон ва тағирёбандаҳои муҳити атрофро истифода мебарем.
  3. Сохтани контейнери Docker ва диаграммаи Helm тавре тарҳрезӣ шудааст, ки корбарон метавонанд ба осонӣ (аслан бо як фармон) операторро дар кластери Kubernetes худ насб кунанд.

CRD

Барои он ки оператор донад, ки кадом захираҳо ва ба куҷо нигоҳ кардан лозим аст, мо бояд барои ӯ қоида муқаррар кунем. Ҳар як қоида ҳамчун объекти ягонаи CRD муаррифӣ карда мешавад. Ин CRD бояд кадом соҳаҳоро дошта бошад?

  1. Навъи захираҳо, ки мо онро ҷустуҷӯ хоҳем кард (ConfigMap ё Secret).
  2. Рӯйхати фазоҳои ном, ки дар он захирахо бояд чойгир шаванд.
  3. Selector, ки тавассути он мо захираҳоро дар фазои ном ҷустуҷӯ хоҳем кард.

Биёед 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

Тайёр! Акнун мо бояд бо кадом роҳе дар бораи қоидаи худ маълумот гирем. Иҷозат диҳед фавран фармоиш диҳам, ки мо худамон ба сервери кластери API дархост наменависем. Барои ин мо аз китобхонаи тайёри Python истифода мебарем 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']}

Аҷоиб: мо тавонистем барои оператор як қоида ба даст орем. Ва муҳимтар аз ҳама, мо он чизеро кардем, ки роҳи Кубернетес номида мешавад.

Тағйирёбандаҳои муҳити зист ё парчамҳо? Мо ҳама чизро мегирем!

Биёед ба конфигуратсияи асосии оператор гузарем. Барои танзими барномаҳо ду равиши асосӣ вуҷуд дорад:

  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 дар дохили контейнер интиқол диҳед. Масалан, мо метавонем дар бораи фазои номе, ки дар он 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='[email protected]',
    include_package_data=True,
    install_requires=[
        'kubernetes==9.0.0',
    ],
    entry_points={
        'console_scripts': [
            '{0} = {0}.cli:main'.format(_NAME),
        ]
    }
)

NB: Мизоҷи kubernetes барои Python версияи худро дорад. Маълумоти бештарро дар бораи мутобиқат байни версияҳои муштарӣ ва версияҳои 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 }}

Натиҷа

Ҳамин тавр, мо бидуни тарсу ҳарос, сарзаниш ё омӯзиши Go, мо тавонистем оператори худро барои Kubernetes дар Python созем. Албатта, он ҳоло ҳам ҷой барои афзоиш дорад: дар оянда вай метавонад қоидаҳои сершуморро коркард кунад, дар риштаҳои гуногун кор кунад, тағиротро дар CRD-ҳои худ мустақилона назорат кунад...

Барои он ки ба шумо бодиққат назар андозем, мо онро ба он дохил кардем анбори ҷамъиятӣ. Агар шумо хоҳед, ки мисолҳои операторҳои ҷиддитаре, ки бо истифода аз Python амалӣ карда мешаванд, шумо метавонед диққати худро ба ду оператор барои ҷойгиркунии mongodb (первый и дуюм).

PS Ва агар шумо барои мубориза бо рӯйдодҳои Kubernetes танбал бошед ё шумо ба истифодаи Bash бештар одат карда бошед, ҳамкорони мо дар шакл ҳалли тайёрро омода кардаанд. оператори снаряд (Мо эълон кард дар моҳи апрел).

PPS

Инчунин дар блоги мо хонед:

Манбаъ: will.com

Илова Эзоҳ