Kubernetes operators Python bez ietvariem un SDK

Kubernetes operators Python bez ietvariem un SDK

Pašlaik uzņēmumam Go ir monopols attiecībā uz programmēšanas valodām, kuras cilvēki izvēlas rakstīt Kubernetes paziņojumus. Tam ir objektīvi iemesli, piemēram:

  1. Ir jaudīgs ietvars operatoru attīstībai Go - Operatora SDK.
  2. Spēļu maiņas lietojumprogrammas, piemēram, Docker un Kubernetes, ir rakstītas programmā Go. Ierakstiet operatoru Go nozīmē runāt vienā valodā ar ekosistēmu.
  3. Augsta Go lietojumprogrammu veiktspēja un vienkārši rīki darbam ar vienlaicīgumu.

NB: Starp citu, kā uzrakstīt savu paziņojumu Go, mēs jau aprakstīts kādā no mūsu ārzemju autoru tulkojumiem.

Bet ko darīt, ja mācīties Go traucē laika vai, vienkārši sakot, motivācijas trūkums? Rakstā ir sniegts piemērs tam, kā varat uzrakstīt labu paziņojumu, izmantojot vienu no populārākajām valodām, ko zina gandrīz katrs DevOps inženieris - Pitons.

Iepazīstieties: Kopētājs - kopētājs!

Piemēram, apsveriet iespēju izstrādāt vienkāršu paziņojumu, kas paredzēts ConfigMap kopēšanai, kad parādās jauna nosaukumvieta vai mainās viena no divām entītijām: ConfigMap un Secret. No praktiskā viedokļa operators var būt noderīgs lietojumprogrammu konfigurāciju lielapjoma atjaunināšanai (atjauninot ConfigMap) vai slepeno datu atjaunināšanai - piemēram, atslēgas darbam ar Docker reģistru (pievienojot nosaukumu Secret).

Tātad, kādam jābūt labam operatoram:

  1. Mijiedarbība ar operatoru tiek veikta, izmantojot Pielāgotu resursu definīcijas (turpmāk – CRD).
  2. Operatoru var konfigurēt. Lai to izdarītu, mēs izmantosim komandrindas karogus un vides mainīgos.
  3. Docker konteinera un Helm diagrammas uzbūve ir izstrādāta tā, lai lietotāji varētu viegli (burtiski ar vienu komandu) instalēt operatoru savā Kubernetes klasterī.

CRD

Lai operators zinātu, kādus resursus meklēt un kur meklēt, mums viņam jāizvirza noteikums. Katrs noteikums tiks attēlots kā viens CRD objekts. Kādiem laukiem jābūt šajā CRD?

  1. Resursa veids, kuru mēs meklēsim (ConfigMap vai Secret).
  2. Nosaukumvietu saraksts, kurā jāatrodas resursiem.
  3. Selektors, ar kuru mēs meklēsim resursus nosaukumvietā.

Aprakstīsim 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

Un mēs to tūlīt izveidosim vienkāršs noteikums — lai meklētu nosaukumvietā ar vārdu default visas ConfigMap ar etiķetēm, piemēram copyrator: "true":

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

Gatavs! Tagad mums kaut kā jāiegūst informācija par mūsu noteikumu. Ļaujiet man nekavējoties rezervēt, ka mēs paši nerakstīsim pieprasījumus klastera API serverim. Lai to izdarītu, mēs izmantosim gatavu Python bibliotēku kubernetes-klients:

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

Šī koda palaišanas rezultātā mēs iegūstam sekojošo:

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

Lieliski: mums izdevās iegūt noteikumu operatoram. Un pats galvenais, mēs izdarījām to, ko sauc par Kubernetes ceļu.

Vides mainīgie vai karodziņi? Ņemam visu!

Pāriesim pie galvenā operatora konfigurācijas. Lietojumprogrammu konfigurēšanai ir divas pamata pieejas:

  1. izmantot komandrindas opcijas;
  2. izmantot vides mainīgos.

Komandrindas opcijas ļauj elastīgāk lasīt iestatījumus, izmantojot datu tipu atbalstu un validāciju. Python standarta bibliotēkā ir modulis argparser, ko izmantosim. Sīkāka informācija un tās iespēju piemēri ir pieejami oficiālā dokumentācija.

Mūsu gadījumā šādi izskatītos komandrindas karodziņu lasīšanas iestatīšanas piemērs:

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

No otras puses, izmantojot vides mainīgos Kubernetes, varat ērti pārsūtīt pakalpojuma informāciju par podziņu konteinera iekšpusē. Piemēram, mēs varam iegūt informāciju par nosaukumvietu, kurā darbojas pods, izmantojot šādu konstrukciju:

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

Operatora loģika

Lai saprastu, kā atdalīt metodes darbam ar ConfigMap un Secret, mēs izmantosim īpašas kartes. Tad mēs varam saprast, kādas metodes mums ir vajadzīgas, lai izsekotu un izveidotu objektu:

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

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

Tālāk jums ir jāsaņem notikumi no API servera. Īstenosim to šādi:

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ēc notikuma saņemšanas mēs pārejam pie galvenās tā apstrādes loģikas:

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

Galvenā loģika ir gatava! Tagad mums tas viss jāiepako vienā Python pakotnē. Mēs sagatavojam failu setup.py, ierakstiet tur meta informāciju par projektu:

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 kubernetes klientam ir sava versiju izveide. Papildinformāciju par saderību starp klienta versijām un Kubernetes versijām var atrast saderības matricas.

Tagad mūsu projekts izskatās šādi:

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

Dokers un Helms

Dockerfile būs neticami vienkāršs: paņemiet bāzes attēlu python-alpine un instalējiet mūsu pakotni. Atliksim tās optimizāciju uz labākiem laikiem:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operatora izvietošana ir arī ļoti vienkārša:

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

Visbeidzot, operatoram ir jāizveido atbilstoša loma ar nepieciešamajām tiesībām:

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

Kopsavilkums

Tā mēs bez bailēm, pārmetumiem un Go apgūšanas varējām izveidot savu Kubernetes operatoru Python. Protams, tai vēl ir, kur augt: nākotnē tas varēs apstrādāt vairākus noteikumus, strādāt vairākos pavedienos, neatkarīgi uzraudzīt izmaiņas savos CRD...

Lai jūs varētu tuvāk apskatīt kodu, esam to ievietojuši publiskais repozitorijs. Ja vēlaties nopietnāku operatoru piemērus, kas ieviesti, izmantojot Python, varat pievērst uzmanību diviem operatoriem mongodb (pirmais и otrais).

PS Un ja jums ir slinkums nodarboties ar Kubernetes notikumiem vai vienkārši esat vairāk pieraduši lietot Bash, mūsu kolēģi ir sagatavojuši gatavu risinājumu formā čaulas operators (Mēs paziņoja aprīlī).

PPS

Lasi arī mūsu emuārā:

Avots: www.habr.com

Pievieno komentāru