Kubernetes օպերատոր Python-ում առանց շրջանակների և SDK-ի

Kubernetes օպերատոր Python-ում առանց շրջանակների և SDK-ի

Go-ն ներկայումս մենաշնորհ ունի ծրագրավորման լեզուների վրա, որոնք մարդիկ ընտրում են Kubernetes-ի համար հայտարարություններ գրել: Դրա համար կան օբյեկտիվ պատճառներ, ինչպիսիք են.

  1. Go-ում օպերատորների զարգացման հզոր շրջանակ կա. Օպերատոր SDK.
  2. Խաղը փոխող հավելվածները, ինչպիսիք են Docker-ը և Kubernetes-ը, գրված են Go-ում: Ձեր օպերատորին Go-ում գրելը նշանակում է նույն լեզվով խոսել էկոհամակարգի հետ:
  3. Go հավելվածների բարձր կատարողականություն և պարզ գործիքներ՝ առանց տուփի զուգահեռ աշխատելու համար:

NBԻ դեպ, ինչպես գրել ձեր սեփական հայտարարությունը Go-ում, մենք արդեն նկարագրված օտար հեղինակների մեր թարգմանություններից մեկում:

Բայց ի՞նչ անել, եթե ժամանակի կամ, պարզ ասած, մոտիվացիայի պատճառով խանգարում եք սովորել Go-ին: Հոդվածը տալիս է օրինակ, թե ինչպես կարող եք լավ հայտարարություն գրել՝ օգտագործելով ամենատարածված լեզուներից մեկը, որը գիտի գրեթե յուրաքանչյուր DevOps ինժեներ. Python.

Հանդիպում. Պատճենահանող - պատճենահանող օպերատոր:

Որպես օրինակ, մտածեք մշակել մի պարզ հայտարարություն, որը նախատեսված է ConfigMap-ը պատճենելու համար, երբ հայտնվում է նոր անվանատարածք, կամ երբ փոխվում է երկու միավորներից մեկը՝ ConfigMap և Secret: Գործնական տեսանկյունից, օպերատորը կարող է օգտակար լինել հավելվածի կազմաձևերի զանգվածային թարմացման համար (թարմացնելով ConfigMap) կամ գաղտնի տվյալների թարմացման համար, օրինակ՝ Docker Registry-ի հետ աշխատելու բանալիները (Գաղտնիքը անվանատարածքում ավելացնելիս):

Այնպես որ, ինչ պետք է ունենա լավ օպերատորը:

  1. Օպերատորի հետ փոխազդեցությունն իրականացվում է օգտագործելով Պատվերով ռեսուրսների սահմանումներ (այսուհետ՝ CRD):
  2. Օպերատորը կարող է կազմաձևվել: Դա անելու համար մենք կօգտագործենք հրամանի տողի դրոշներ և շրջակա միջավայրի փոփոխականներ:
  3. Docker կոնտեյների և Helm աղյուսակի կառուցվածքը նախագծված է այնպես, որ օգտվողները կարողանան հեշտությամբ (բառացիորեն մեկ հրամանով) տեղադրել օպերատորը իրենց Kubernetes կլաստերում:

ՏԶԿ

Որպեսզի օպերատորը իմանա, թե ինչ ռեսուրսներ փնտրել և որտեղ փնտրել, մենք պետք է նրա համար կանոն սահմանենք։ Յուրաքանչյուր կանոն կներկայացվի որպես մեկ 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

Պատրաստ. Հիմա մենք պետք է ինչ-որ կերպ տեղեկատվություն ստանանք մեր կանոնի մասին։ Թույլ տվեք անմիջապես վերապահել, որ մենք ինքներս հարցումներ չենք գրի կլաստերի 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-ը հետևյալ կառուցվածքով.

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),
        ]
    }
)

NBPython-ի համար 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 }}

Լրիվ

Ահա թե ինչպես, առանց վախի, կշտամբանքի կամ Go-ն սովորելու, մենք կարողացանք Python-ում կառուցել մեր սեփական օպերատորը Kubernetes-ի համար: Իհարկե, այն դեռ աճելու տեղ ունի. ապագայում այն ​​կկարողանա մշակել բազմաթիվ կանոններ, աշխատել բազմաթիվ թելերով, ինքնուրույն վերահսկել իր CRD-ների փոփոխությունները...

Կոդին ավելի մոտիկից ծանոթանալու համար մենք այն տեղադրել ենք հանրային շտեմարան. Եթե ​​ցանկանում եք Python-ի միջոցով իրականացվող ավելի լուրջ օպերատորների օրինակներ, կարող եք ձեր ուշադրությունը դարձնել mongodb-ի տեղակայման երկու օպերատորների վրա (первый и երկրորդ).

Հ.Գ. Իսկ եթե դուք չափազանց ծույլ եք զբաղվել Kubernetes-ի իրադարձություններով կամ պարզապես ավելի սովոր եք օգտագործել Bash-ը, ապա մեր գործընկերները պատրաստել են պատրաստի լուծում այս տեսքով. shell-օպերատոր (Մենք հայտարարեց ապրիլին):

PPS

Կարդացեք նաև մեր բլոգում.

Source: www.habr.com

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