Kubernetes Operadorea Python-en markorik eta SDKrik gabe

Kubernetes Operadorea Python-en markorik eta SDKrik gabe

Gaur egun, Go-k jendeak Kubernetes-en adierazpenak idazteko aukeratzen dituen programazio-lengoaien monopolioa du. Horretarako arrazoi objektiboak daude, hala nola:

  1. Operadoreak garatzeko esparru indartsua dago Go-n. SDK eragilea.
  2. Docker eta Kubernetes bezalako jokoak aldatzen dituzten aplikazioak Go-n idatzita daude. Zure operadorea Go-n idazteak ekosistemarekin hizkuntza bera hitz egitea esan nahi du.
  3. Go aplikazioen errendimendu handia eta aldiberekotasunarekin lan egiteko tresna errazak.

NB: Bide batez, nola idatzi zure adierazpena Go-n, guk dagoeneko deskribatuta atzerriko egileen gure itzulpenetako batean.

Baina zer gertatzen da Goa ikastea galarazten badizute denbora faltagatik edo, besterik gabe, motibazioagatik? Artikuluak ia DevOps ingeniari guztiek ezagutzen duten hizkuntza ezagunenetako bat erabiliz adierazpen on bat idazteko adibide bat eskaintzen du - Python.

Ezagutu: Kopiatzailea - kopiatzailea!

Adibide gisa, kontuan hartu ConfigMap bat kopiatzeko diseinatutako adierazpen sinple bat garatzea izen-espazio berri bat agertzen denean edo bi entitateetako bat aldatzen denean: ConfigMap eta Secret. Ikuspegi praktikotik, operadorea erabilgarria izan daiteke aplikazioen konfigurazioen eguneratze masiboa egiteko (ConfigMap eguneratuz) edo datu sekretuak eguneratzeko -adibidez, Docker Erregistroarekin lan egiteko gakoak (Sekretua izen-espazioan gehitzean).

Horrela, operadore on batek izan behar duena:

  1. Operadorearekin interakzioa erabiliz egiten da Baliabideen definizio pertsonalizatuak (aurrerantzean CRD deitua).
  2. Eragilea konfiguratu daiteke. Horretarako, komando-lerroko banderak eta ingurune-aldagaiak erabiliko ditugu.
  3. Docker edukiontziaren eta Helm diagramaren eraikuntza erabiltzaileek erraz (literalki komando batekin) instalatu ahal izango dute operadorea Kubernetes klusterrean.

CRD

Operadoreak zer baliabide bilatu eta non begiratu jakin dezan, arau bat ezarri behar diogu. Arau bakoitza CRD objektu bakar gisa irudikatuko da. Zein eremu izan behar ditu CRD honek?

  1. Baliabide mota, bilatuko duguna (ConfigMap edo Secret).
  2. Izen-espazioen zerrenda, zeinetan kokatu behar diren baliabideak.
  3. hautatzailea, zeinaren bidez baliabideak bilatuko ditugu izenen eremuan.

Deskriba dezagun 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

Eta berehala sortuko dugu arau sinplea β€” izenarekin izen-eremuan bilatzeko default guztiak bezalako etiketak dituzten ConfigMap copyrator: "true":

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

Prest! Orain nolabait gure arauari buruzko informazioa lortu behar dugu. Utzidazu erreserba bat egin berehala klusterreko API Zerbitzariari eskaerarik idatziko ez geniola. Horretarako, prest egindako Python liburutegia erabiliko dugu kubernetes-bezeroa:

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

Kode hau exekutatzearen ondorioz, honako hau lortzen dugu:

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

Bikaina: operadorearentzat arau bat lortzea lortu dugu. Eta garrantzitsuena, Kubernetes modua deitzen dena egin genuen.

Inguruko aldagaiak edo banderak? Dena hartzen dugu!

Joan gaitezen operadore nagusiaren konfiguraziora. Aplikazioak konfiguratzeko oinarrizko bi ikuspegi daude:

  1. erabili komando-lerroko aukerak;
  2. ingurune-aldagaiak erabili.

Komando-lerroko aukerek ezarpenak malgutasun handiagoz irakurtzeko aukera ematen dute, datu-moten laguntzarekin eta balioztatzearekin. Python-en liburutegi estandarrak modulu bat du argparser, erabiliko duguna. Bere gaitasunen xehetasunak eta adibideak hemen daude eskuragarri dokumentazio ofiziala.

Gure kasuan, hauxe izango litzateke irakurketa-lerroko banderak konfiguratzeko adibide bat:

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

Bestalde, Kubernetes-en ingurune-aldagaiak erabiliz, erraz transferi dezakezu edukiontzi barruko podari buruzko zerbitzu-informazioa. Adibidez, pod-a exekutatzen ari den izen-espazioari buruzko informazioa lor dezakegu honako eraikuntza honekin:

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

Operadorearen logika

ConfigMap eta Secret-ekin lan egiteko metodoak nola bereizten ulertzeko, mapa bereziak erabiliko ditugu. Ondoren, objektua jarraitzeko eta sortzeko zer metodo behar ditugun ulertu ahal izango dugu:

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

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

Ondoren, API zerbitzaritik gertaerak jaso behar dituzu. Ezar dezagun honela:

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)

Gertaera jaso ondoren, prozesatzeko logika nagusira igaroko gara:

# Π’ΠΈΠΏΡ‹ событий, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π±ΡƒΠ΄Π΅ΠΌ Ρ€Π΅Π°Π³ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ
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)

Logika nagusia prest dago! Orain hau guztia Python pakete batean paketatu behar dugu. Fitxategia prestatzen dugu setup.py, idatzi han proiektuari buruzko meta informazioa:

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-erako kubernetes bezeroak bere bertsioa du. Bezeroen bertsioen eta Kubernetesen bertsioen arteko bateragarritasunari buruzko informazio gehiago hemen aurki daiteke bateragarritasun-matrizeak.

Orain gure proiektua honelakoa da:

copyrator
β”œβ”€β”€ copyrator
β”‚   β”œβ”€β”€ cli.py # Π›ΠΎΠ³ΠΈΠΊΠ° Ρ€Π°Π±ΠΎΡ‚Ρ‹ с ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строкой
β”‚   β”œβ”€β”€ constant.py # ΠšΠΎΠ½ΡΡ‚Π°Π½Ρ‚Ρ‹, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΌΡ‹ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΠ»ΠΈ Π²Ρ‹ΡˆΠ΅
β”‚   β”œβ”€β”€ load_crd.py # Π›ΠΎΠ³ΠΈΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ CRD
β”‚   └── operator.py # Основная Π»ΠΎΠ³ΠΈΠΊΠ° Ρ€Π°Π±ΠΎΡ‚Ρ‹ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π°
└── setup.py # ΠžΡ„ΠΎΡ€ΠΌΠ»Π΅Π½ΠΈΠ΅ ΠΏΠ°ΠΊΠ΅Ρ‚Π°

Docker eta Helm

Dockerfile ikaragarri sinplea izango da: hartu oinarrizko python-alpine irudia eta instalatu gure paketea. Atzera dezagun bere optimizazioa garai hobeetara arte:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operadorearentzat hedapena ere oso erraza da:

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

Azkenik, behar diren eskubideak dituen operadorearentzat rol egokia sortu behar duzu:

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

Guztira

Horrela, beldurrik, gaitzespen edo Go ikasi gabe, Kubernetes-erako gure operadore propioa eraiki ahal izan genuen Python-en. Noski, oraindik badu hazteko lekua: etorkizunean hainbat arau prozesatu, hainbat haritan lan egin, bere CRD-en aldaketak modu independentean kontrolatu...

Kodea hurbilagotik ikusteko, jarri dugu biltegi publikoa. Python erabiliz inplementatutako operadore serioagoen adibideak nahi badituzu, zure arreta jar dezakezu mongodb hedatzeko bi operadoretara (ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈ bigarren).

PS Eta Kuberneteseko gertaerei aurre egiteko alferra bazara edo Bash erabiltzen ohituta bazaude, gure lankideek prest egindako irtenbide bat prestatu dute formularioan. shell-operadore (Gu iragarri du apirilean).

PPS

Irakurri ere gure blogean:

Iturria: www.habr.com

Gehitu iruzkin berria