„Kubernetes“ operatorius „Python“ be rėmų ir SDK

„Kubernetes“ operatorius „Python“ be rėmų ir SDK

„Go“ šiuo metu turi monopolį programavimo kalboms, kurias žmonės pasirenka rašyti „Kubernetes“ pareiškimus. Tam yra objektyvių priežasčių, tokių kaip:

  1. Yra galinga „Go“ operatorių kūrimo sistema. Operatoriaus SDK.
  2. Žaidimą keičiančios programos, tokios kaip „Docker“ ir „Kubernetes“, yra parašytos „Go“. Rašyti operatorių Go reiškia kalbėti ta pačia kalba su ekosistema.
  3. Didelis „Go“ programų našumas ir paprasti įrankiai, skirti dirbti su vienu metu.

NB: Beje, kaip parašyti savo pareiškimą Go, mes jau aprašyta viename iš mūsų užsienio autorių vertimų.

Bet ką daryti, jei mokytis Go trukdo laiko arba, paprasčiau tariant, motyvacijos trūkumas? Straipsnyje pateikiamas pavyzdys, kaip galite parašyti gerą pareiškimą naudodami vieną iš populiariausių kalbų, kurią žino beveik kiekvienas „DevOps“ inžinierius - Pitonas.

Susipažinkite: Kopijuoklis – kopijavimo operatorius!

Pavyzdžiui, apsvarstykite galimybę sukurti paprastą teiginį, skirtą nukopijuoti ConfigMap, kai atsiranda nauja vardų sritis arba pasikeičia vienas iš dviejų objektų: ConfigMap ir Secret. Praktiniu požiūriu operatorius gali būti naudingas masiniam programų konfigūracijų atnaujinimui (atnaujinant ConfigMap) arba slaptiems duomenims atnaujinti, pavyzdžiui, raktams, skirtiems darbui su Docker registru (kai vardų erdvėje pridedama Secret).

tokiu būdu, ką turi turėti geras operatorius:

  1. Sąveika su operatoriumi vykdoma naudojant Tinkintų išteklių apibrėžimai (toliau – CRD).
  2. Galima konfigūruoti operatorių. Norėdami tai padaryti, naudosime komandų eilutės vėliavėles ir aplinkos kintamuosius.
  3. „Docker“ konteinerio ir „Helm“ diagramos konstrukcija sukurta taip, kad vartotojai galėtų lengvai (pažodžiui su viena komanda) įdiegti operatorių savo „Kubernetes“ klasteryje.

KRD

Kad operatorius žinotų, kokių išteklių ir kur ieškoti, turime jam nustatyti taisyklę. Kiekviena taisyklė bus pavaizduota kaip vienas CRD objektas. Kokie laukai turėtų būti šiame KPD?

  1. Ištekliaus tipas, kurio ieškosime (ConfigMap arba Secret).
  2. Vardų erdvių sąrašas, kuriame turėtų būti ištekliai.
  3. Rinkėjas, pagal kurią ieškosime išteklių vardų erdvėje.

Apibūdinkime 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

Ir mes jį iškart sukursime paprasta taisyklė — ieškoti vardų srityje su vardu default visi ConfigMap su tokiomis etiketėmis kaip copyrator: "true":

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

Pasiruošę! Dabar turime kažkaip gauti informacijos apie mūsų taisyklę. Leiskite man iš karto rezervuoti, kad patys nerašysime užklausų į klasterio API serverį. Norėdami tai padaryti, naudosime paruoštą Python biblioteką kubernetes-klientas:

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

Vykdydami šį kodą gauname:

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

Puiku: mums pavyko gauti taisyklę operatoriui. Ir svarbiausia, mes padarėme tai, kas vadinama Kubernetes būdu.

Aplinkos kintamieji ar vėliavėlės? Mes priimame viską!

Pereikime prie pagrindinės operatoriaus konfigūracijos. Yra du pagrindiniai programų konfigūravimo būdai:

  1. naudoti komandinės eilutės parinktis;
  2. naudoti aplinkos kintamuosius.

Komandinės eilutės parinktys leidžia lanksčiau skaityti nustatymus, naudojant duomenų tipo palaikymą ir patvirtinimą. Standartinėje Python bibliotekoje yra modulis argparser, kurį naudosime. Išsamią informaciją ir jo galimybių pavyzdžius rasite oficialius dokumentus.

Mūsų atveju taip atrodytų nuskaitymo komandinės eilutės vėliavėlių nustatymo pavyzdys:

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

Kita vertus, naudodami aplinkos kintamuosius „Kubernetes“, galite lengvai perkelti paslaugų informaciją apie talpyklą konteinerio viduje. Pavyzdžiui, galime gauti informacijos apie vardų erdvę, kurioje veikia blokas, naudodami tokią konstrukciją:

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

Operatoriaus logika

Norėdami suprasti, kaip atskirti darbo su ConfigMap ir Secret metodus, naudosime specialius žemėlapius. Tada galime suprasti, kokių metodų reikia norint sekti ir sukurti objektą:

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

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

Tada turite gauti įvykius iš API serverio. Įgyvendinkime tai taip:

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)

Gavę įvykį pereiname prie pagrindinės jo apdorojimo logikos:

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

Pagrindinė logika yra paruošta! Dabar turime visa tai supakuoti į vieną Python paketą. Paruošiame failą setup.py, parašykite ten meta informaciją apie projektą:

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 skirtas kubernetes klientas turi savo versijų kūrimą. Daugiau informacijos apie kliento versijų ir Kubernetes versijų suderinamumą rasite suderinamumo matricos.

Dabar mūsų projektas atrodo taip:

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

Dokeris ir Helmas

„Dockerfile“ bus neįtikėtinai paprastas: paimkite pagrindinį „python-alpine“ vaizdą ir įdiekite mūsų paketą. Atidėkime jos optimizavimą geresniems laikams:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operatoriaus diegimas taip pat labai paprastas:

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

Galiausiai turite sukurti atitinkamą vaidmenį operatoriui su reikiamomis teisėmis:

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

Visas

Taip be baimės, priekaištų ir mokymosi „Go“ galėjome sukurti savo „Kubernetes“ operatorių Python. Žinoma, jis dar turi kur augti: ateityje jis galės apdoroti kelias taisykles, dirbti keliomis gijomis, savarankiškai stebėti savo CRD pokyčius...

Kad galėtumėte atidžiau pažvelgti į kodą, mes jį įdėjome viešoji saugykla. Jei norite rimtesnių operatorių, įdiegtų naudojant Python, pavyzdžių, galite atkreipti dėmesį į du operatorius, skirtus diegti mongodb (первый и antra).

PS O jei esate per daug tingus tvarkytis su Kubernetes renginiais arba tiesiog esate labiau įpratę naudoti Bash, mūsų kolegos paruošė paruoštą sprendimą formoje apvalkalo operatorius (Mes paskelbė balandžio mėnesį).

PGS

Taip pat skaitykite mūsų tinklaraštyje:

Šaltinis: www.habr.com

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