Kubernetes Operator Pythonis ilma raamistike ja SDKta

Kubernetes Operator Pythonis ilma raamistike ja SDKta

Go-l on praegu monopol programmeerimiskeelte osas, mida inimesed Kubernetese jaoks avaldusi kirjutavad. Sellel on objektiivsed põhjused, näiteks:

  1. Go-s on operaatorite arendamiseks võimas raamistik - Operaator SDK.
  2. Mängu muutvad rakendused, nagu Docker ja Kubernetes, on kirjutatud Go-s. Operaatori Go-sse kirjutamine tähendab ökosüsteemiga samas keeles rääkimist.
  3. Go rakenduste kõrge jõudlus ja lihtsad tööriistad samaaegsusega töötamiseks.

NB: Muide, kuidas kirjutada oma avaldust sisse Go, we juba kirjeldatud ühes meie välisautorite tõlkes.

Aga mis siis, kui Go õppimist takistab ajapuudus või lihtsalt öeldes motivatsioon? Artiklis on näide selle kohta, kuidas saate kirjutada hea avalduse, kasutades ühte kõige populaarsemat keelt, mida peaaegu iga DevOpsi insener teab - Python.

Tutvuge: Koopiamasin - koopiaoperaator!

Näiteks kaaluge lihtsa avalduse väljatöötamist, mis on mõeldud ConfigMapi kopeerimiseks kas uue nimeruumi ilmumisel või siis, kui muutub üks kahest olemist: ConfigMap ja Secret. Praktilisest vaatenurgast võib operaator olla kasulik rakenduste konfiguratsioonide hulgivärskendamisel (ConfigMapi värskendamisel) või salaandmete värskendamisel – näiteks võtmed Dockeri registriga töötamiseks (nimeruumi Secret lisamisel).

Niisiis, mis ühel heal operaatoril olema peaks:

  1. Operaatoriga suhtlemine toimub kasutades Kohandatud ressursi määratlused (edaspidi CRD).
  2. Operaatorit saab konfigureerida. Selleks kasutame käsurea lippe ja keskkonnamuutujaid.
  3. Dockeri konteineri ja Helmi diagrammi ehitus on loodud nii, et kasutajad saaksid hõlpsalt (sõna otseses mõttes ühe käsuga) installida operaatori oma Kubernetese klastrisse.

CRD

Selleks, et operaator teaks, milliseid ressursse otsida ja kust otsida, peame tema jaoks paika panema reegli. Iga reegel esitatakse ühe CRD-objektina. Millised väljad peaksid selles CRD-s olema?

  1. Ressursi tüüp, mida me otsime (ConfigMap või Secret).
  2. Nimeruumide loend, milles ressursid peaksid asuma.
  3. Valija, mille abil otsime nimeruumist ressursse.

Kirjeldame CRD-d:

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

Ja me loome selle kohe lihtne reegel – nimeruumist otsimiseks nimega default kõik ConfigMap koos siltidega nagu copyrator: "true":

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

Valmis! Nüüd peame oma reegli kohta kuidagi teavet hankima. Lubage mul kohe teha broneering, et me ise klastri API serverisse päringuid ei kirjuta. Selleks kasutame valmis Pythoni teeki kubernetes-klient:

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

Selle koodi käitamise tulemusena saame järgmise:

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

Suurepärane: meil õnnestus saada operaatorile reegel. Ja mis kõige tähtsam, me tegime seda, mida nimetatakse Kubernetese viisiks.

Keskkonnamuutujad või lipud? Võtame kõik!

Liigume edasi operaatori põhikonfiguratsiooni juurde. Rakenduste konfigureerimiseks on kaks peamist lähenemisviisi:

  1. kasutada käsurea valikuid;
  2. kasutada keskkonnamuutujaid.

Käsurea valikud võimaldavad teil sätteid paindlikumalt lugeda koos andmetüübi toe ja valideerimisega. Pythoni standardteegis on moodul argparser, mida me kasutame. Selle võimaluste üksikasjad ja näited on saadaval aadressil ametlik dokumentatsioon.

Meie puhul näeks käsurea lugemise lippude seadistamise näide välja selline:

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

Teisest küljest saate Kubernetese keskkonnamuutujaid kasutades hõlpsasti konteineri sees oleva paketi teenuseteavet üle kanda. Näiteks saame teavet nimeruumi kohta, milles pod töötab, järgmise konstruktsiooniga:

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

Operaatori loogika

Et mõista, kuidas eraldada meetodid ConfigMapi ja Secretiga töötamiseks, kasutame spetsiaalseid kaarte. Siis saame aru, milliseid meetodeid on vaja objekti jälgimiseks ja loomiseks:

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

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

Järgmiseks peate saama sündmusi API serverist. Rakendame seda järgmiselt:

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)

Pärast sündmuse saamist liigume edasi selle töötlemise põhiloogika juurde:

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

Põhiloogika on valmis! Nüüd peame selle kõik ühte Pythoni paketti pakkima. Valmistame faili ette setup.py, kirjuta sinna projekti metainfo:

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: Pythoni kubernetes kliendil on oma versioonid. Lisateavet kliendiversioonide ja Kubernetese versioonide ühilduvuse kohta leiate aadressilt ühilduvusmaatriksid.

Nüüd näeb meie projekt välja selline:

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

Docker ja Helm

Dockerfile saab olema uskumatult lihtne: võtke python-alpine põhipilt ja installige meie pakett. Lükkame selle optimeerimise edasi paremate aegadeni:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Ka operaatori juurutamine on väga lihtne:

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

Lõpuks peate looma operaatorile sobiva rolli koos vajalike õigustega:

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

Summaarne

Nii saime ilma hirmu, etteheiteid ja Go-d õppimata luua Pythonis Kubernetese jaoks oma operaatori. Muidugi on sellel veel ruumi kasvada: tulevikus saab see töödelda mitut reeglit, töötada mitmes lõimes, iseseisvalt jälgida muutusi oma CRD-des...

Koodiga lähemalt tutvumiseks oleme selle sisestanud avalik hoidla. Kui soovite näiteid Pythoni abil rakendatud tõsisematest operaatoritest, võite pöörata tähelepanu kahele operaatorile mongodb (esimene и teine).

PS Ja kui olete liiga laisk Kubernetese sündmustega tegelemiseks või olete lihtsalt rohkem harjunud Bashi kasutama, on meie kolleegid koostanud valmislahenduse kujul kesta operaator (Me teatas see aprillis).

PPS

Loe ka meie blogist:

Allikas: www.habr.com

Lisa kommentaar