Kubernetes Operator Pythonis ilma raamistikuta ja SDK-dega.

Kubernetes Operator Pythonis ilma raamistikuta ja SDK-dega.

Go on hetkel on ainus programmeerimiskeel, mida inimesed valivad Kubernetes'i operaatorite kirjutamiseks. Sellel on mitmeid objektiivseid põhjuseid, nagu:

  1. Mäletatavasti on olemas võimas raamistik operaatorite arendamiseks Go-s — Operator SDK.
  2. Go-s on kirjutatud mängu muutvaid rakendusi, nagu Docker ja Kubernetes. Oma operaatori kirjutamine Go-s on nagu rääkimine ökosüsteemiga ühisel keelel.
  3. Go rakenduste kõrge jõudlus ja lihtsad tööriistad konkureerimise jaoks "välja pakutud".

NB: Muide, kuidas kirjutada oma operaatorit Go-s, oleme juba kirjeldanud ühes meie tõlkes välisautorite teosest.

Kuid mis siis, kui Go õppimist takistab aeg või lihtsalt motivatsiooni puudumine? Artiklis on näide, kuidas kirjutada kvaliteetne operaator, kasutades ühte populaarseimat keelt, mida iga DevOps-insener tavaliselt tunneb — Python.

Tere tulemast: Kopi operaatort!

Vaatame lihtsa operaatori väljatöötamist, mis on mõeldud ConfigMap'i kopeerimiseks kas uue nimespaani lisamise või ühe kahe entiteedi, ConfigMap'i ja Salajase, muutmise korral. Praktilise kasutamise seisukohalt võib operaator olla kasulik rakenduse konfigureerimisprotsesside massiliseks uuendamiseks (ConfigMap'i uuendamise kaudu) või salajaste andmete, näiteks Docker Registry töötamise võtmete uuendamiseks (nt Salajase lisamine nimespaani).

Nii, mida peab omama hea operaator:

  1. Operaatoriga suhtlemine toimub kaudu Kohandatud Ressursi Määratlused (edasi — CRD).
  2. Operaatorit saab konfigureerida. Selleks kasutame käsurea lippusid ja keskkonnamuutujid.
  3. Docker konteineri ja Helm-charts'i kogumine on kavandatud nii, et kasutajad saavad lihtsalt (literally ühe käsuga) operaatori oma Kubernetes'i klastrisse installida.

CRD

Kuna operaator peab teadma, milliseid ressursse ja kus teda otsida, peame määrama tema reegli. Iga reegel esitatakse ühe CRD objektina. Millised väljad peaksid sellel CRD-l olema?

  1. Otsitava ressursitüüp, mida me otsime (ConfigMap või Secret).
  2. Nimespaanide loetelu, kus ressursid peaksid asuma.
  3. Valija, mille alusel me ressursse nimespaanis otsime.

Kirjeldame 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

Ja loome kohe lihtsa reegli — määratlemiseks nimel Seejärel käivitame exec käsu ja ootame 5000 sekundit, et jätkata jälgimist: kõik ConfigMapid label'idega kujul copyrator: "true":

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

Valmis! Nüüd peame kuidagi saama teavet meie reegli kohta. Täpsustan kohe, et me ei hakka ise API Server'ile päringuid kirjutama. Selleks kasutame valmis Python'i raamatukogu kubernetes-client:

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 tulemuseks on:

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

Suurepärane: me saime reegli operaatorile. Ja mis kõige tähtsam — me tegime seda nii-öelda Kubernetes'i viisil.

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

Liigume edasi operaatori põhikonfiguratsiooni juurde. On kaks põhimeetodit rakenduste konfigureerimiseks:

  1. kasutada käsurea argumente;
  2. kasutada keskkonnamuutujad.

Käsurea argumentide abil saab seadeid lugeda paindlikumalt, andmetüüpide toega ja valideerimisega. Python'i standardraamatukogus on moodul argparser, mida me ka kasutame. Üksikasjad ja näited selle võimalustest on saadaval ametlikust dokumentatsioonist.

Nii näeb meie juhtumi jaoks välja näide käsurea lippude lugemise seadistamisest:

   parser = ArgumentParser(
        description='Copyrator - copy operator.',
        prog='copyrator'
    )
    parser.add_argument(
        '--namespace',
        type=str,
        default=getenv('NAMESPACE', 'default'),
        help='Operaatori nimel'
    )
    parser.add_argument(
        '--rule-name',
        type=str,
        default=getenv('RULE_NAME', 'peamine-reegel'),
        help='CRD nimi'
    )
    args = parser.parse_args()

Teiselt poolt on Kuberneteses keskkonnamuutujate abil lihtne edastada teavet pod'i kohta konteinerisse. Näiteks saame teada namespace'i, milles pod töötab, järgmise konstruktsiooniga:

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

Operaatori töölogika

Kuna mõistame, kuidas eristada meetodeid ConfigMap'i ja Secret'i jaoks, kasutame spetsiaalseid kaarte. Nii saame aru, milliseid meetodeid on meie objektide jälgimiseks ja loomiseks vaja:

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

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

Seejärel on vajalik saada sündmusi API serverilt. Teeme seda järgmiselt:

def handle(specs):
    kubernetes.config.load_incluster_config()
    v1 = kubernetes.client.CoreV1Api()

    # Saame meetodi objektide jälgimiseks
    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)

Sündmuse saamisel liigume põhiloogika töötlemise 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 tuleb kogu see kokku panna ühte Python'i paketti. Vormistame faili setup.py, kirjutame sinna projekti metaandmed:

from sys import version_info

from setuptools import find_packages, setup

if version_info[:2] < (3, 5):
    raise RuntimeError(
        'Toe python versioon %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='maksim.nabokikh@flant.com',
    include_package_data=True,
    install_requires=[
        'kubernetes==9.0.0',
    ],
    entry_points={
        'console_scripts': [
            '{0} = {0}.cli:main'.format(_NAME),
        ]
    }
)

NB: Kubernetes'i klient Python'i jaoks omab oma versioonimist. Klientide ja Kubernetes'e versioonide ühilduvuse kohta saab lisainfot ühilduvusgraafikust.

Praegu näeb meie projekt välja järgmine:

copyrator
├── copyrator
│   ├── cli.py # Command line'i tööloogika
│   ├── constant.py # Ülesanne, mille me varem määrasime
│   ├── load_crd.py # CRD laadimise tööloogika
│   └── operator.py # Operaatori põhiloogika
└── setup.py # Paketi vormistamine

Docker ja Helm

Dockerfile on ülilihtne: võtame põhijuurikaks python-alpine ja installime meie paketi. Selle optimeerimise lükkame edasi paremate aegade tarvis:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operatori juurutamine on samuti 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 on vaja luua sobiv roll operaatorile 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 }}

Kokkuvõte

Nii, ilma hirmu, etteheiteid ja Go õppimata saime ise koduse operaatori kokku panna Kuberneteses Pythonis. Loomulikult on tal veel arenguruumi: tulevikus võiks ta hallata mitmeid reegleid, töötada mitmes lõimes, jälgida oma CRD muutusi iseseisvalt...

Et koodiga lähemalt tutvuda, oleme selle kogunud avalikku hoidlasse. Kui otsite tõsisemaid operaatorite näiteid, mille on loonud Python, võite kaaluda kahte operaatorit mongodb juurutamiseks (esimene ja teises).

P.S. Ja kui teil on liiga raske aru saada Kubernetesest või eelistate lihtsalt Bashit kasutada — meie kolleegid on valmistanud valmis lahenduse shell-operator (meie teatanud selle aprillis).

P.P.S.

Lugege ka meie blogist:

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster