Kubernetes operator Pythonis ilma raamistiketa ja SDK-deta

Kubernetes operator Pythonis ilma raamistiketa ja SDK-deta

Go on hetkel monopol kõigi programmeerimiskeelte seas, mida inimesed valivad Kubernetes'e operaatorite kirjutamiseks. Sellel on objektiivsed põhjused, nagu:

  1. Go jaoks on olemas kõige võimsam raamistik operaatorite arendamiseks — Operator SDK.
  2. Go-s on kirjutatud sellised 'mängu muutevad' rakendused nagu Docker ja Kubernetes. Oma operaatori kirjutamine Go-s tähendab rääkimist ökosüsteemiga ühes keeles.
  3. Go rakenduste kõrge jõudlus ja lihtsad tööriistad concurrency'ga töötamiseks 'karbist välja'.

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

Aga mis juhtub, kui Go õppimist takistab ajapuudus või lihtsalt motivatsiooni puudumine? Artiklis on näidatud, kuidas kirjutada korralik operaator, kasutades ühte populaarseimat keelt, mida tunneb praktiliselt iga DevOps-insener — Python.

Tutvustame: Kopiator — kopeerimisoperaator!

Võtame näiteks lihtsa operaatori, mis on mõeldud ConfigMap'i kopeerimiseks kas uue namespace'i ilmumisel või siis, kui üks kahest entiteedist: ConfigMap ja Secret muutub. Praktikas võib operaator olla kasulik rakenduse konfiguratsioonide massiliseks uuendamiseks (ConfigMap'i uuendamise kaudu) või salajaste andmete, näiteks Docker Registry võtmete uuendamiseks (kui Secret lisatakse namespace'i).

Nii et, mida peaks head operaatorit olema:

  1. Operaatoriga suhtlemine toimub Custom Resource Definitions (edasi — CRD).
  2. Operaatorit saab konfigureerida. Selleks kasutame käsurea lippu ja keskkonnamuutujaid.
  3. Docker-konteineri ja Helm-chart'i koostamine on tehtud nii, et kasutajad saaksid operaatori oma Kubernetes-i klastrisse hõlpsasti (literally ühe käsu) paigaldada.

CRD

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

  1. Ressurssi tüüp, mida me otsime (ConfigMap või Secret).
  2. Namespace'ide nimekiri, milles peavad olema ressursid.
  3. Valija, mille põhjal otsime ressursse namespace's.

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 loome kohe lihtsa reegli — otsimiseks namespace's nimega default kõik ConfigMap'id, millel on sildid kujul 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 kuidagi saama teavet meie reegli kohta. Ütleksin kohe, et me ei hakka iseseisvalt kirjutama päringuid API Server'ile klastris. Selleks kasutame valmis Python'i teeki 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 täitmise tulemusena saame järgmise:

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

Suurepärane: saime operaatori reegli kätte. Ja mis kõige tähtsam - me viisime selle ellu, nagu öeldakse, Kubernetes'i moodi.

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

Liigume edasi operaatori põhikonfiguratsiooni juurde. Rakenduste konfigureerimiseks on kaks põhiviisi:

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

Käsurea parameetrid võimaldavad lugeda seadeid paindlikumalt, toetades andmetüüpide valideerimist. Python'i standardraamatukogus on moodul argparser, mida me ka kasutame. Üksikasjad ja näited selle võimaluste kohta on saadaval ametlikus dokumentatsioonis.

Nii näeb välja meie juhtum, kuidas käsurea lippude lugemise seadistamine välja näeb:

   parser = ArgumentParser(
        description='Copyrator - kopeerimise operaator.',
        prog='copyrator'
    )
    parser.add_argument(
        '--namespace',
        type=str,
        default=getenv('NAMESPACE', 'default'),
        help='Operaatori nimetusruum'
    )
    parser.add_argument(
        '--rule-name',
        type=str,
        default=getenv('RULE_NAME', 'main-rule'),
        help='CRD nimi'
    )
    args = parser.parse_args()

Teisest küljest, Kubernetesis keskkonnamuutujate abil, on lihtne edastada teenuse teavet pod'i konteinerisse. Näiteks, et saada teavet nimetusruumi kohta, milles pod töötab, saame kasutada järgmist konstruktsiooni:

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

Operaatori tööloogika

Kuna mõistame, kuidas eraldada meetodeid ConfigMap'i ja Secret'i töötlemiseks, kasutame spetsiaalseid kaarte. Nii saame aru, milliseid meetodeid vajame 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',
}

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

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

    # Saame meetodi objekti 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)

Esmalt, pärast sündmuse saamist, läheme edasi selle põhiloogikale:

# Типы событий, на которые будем реагировать
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 kõik see pakkida ühte Python paketisse. Loome faili setup.py, kirjutame sinna projekti metainformatsiooni:

from sys import version_info

from setuptools import find_packages, setup

if version_info[:2] < (3, 5):
    raise RuntimeError(
        'Toetamata 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 klient Pythonis on oma versioonimise süsteem. Rohkem teavet kliendi ja Kubernetes versioonide ühilduvuse kohta leiate ühilduvuste tabelist.

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

copyrator
├── copyrator
│   ├── cli.py # Käsureegli töötamise loogika
│   ├── constant.py # Ülaltoodud konstantsed väärtused
│   ├── load_crd.py # CRD laadimise loogika
│   └── operator.py # Operaatori põhiloogika
└── setup.py # Pakendi koostamine

Docker ja Helm

Dockerfile saab olema äärmiselt lihtne: võtame aluseks pildi python-alpine ja installime meie paketi. Selle optimeerimise lükkame edasi paremate aegade jaoks:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operaatori 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 vajalik luua operaatori jaoks sobiv roll 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, kartmata, süüdistamata ja Go õpinguteta suutsime me Pythonit kasutades luua oma Kubernetesi operaatori. Loomulikult on selles veel arenguruumi: tulevikus suudab see töödelda mitmeid reegleid, töötada mitmes voos ja jälgida oma CRD-de muudatusi...

Selleks, et koodiga lähemalt tutvuda, oleme selle kokku pannud avalikku repo. Kui soovite näha tõsisemaid operaatoreid, mis on rakendatud Pythonis, võite pöörata tähelepanu kahele mongodb käitamise operaatorile (esimene ja teine).

P.S. Ja kui teile ei meeldi Kubernetesi sündmuste uurimine või teile on mugavam kasutada Bash'i — meie kolleegid on valmistanud valmis lahenduse shell-operator (me teatasime sellest aprillis).

P.P.S.

Lugege ka meie blogist:

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster