Kubernetes operators Python bez ietvariem un SDK

Kubernetes operators Python bez ietvariem un SDK

Go Å”obrÄ«d ir monopols programmēŔanas valodu vidÅ«, ko cilvēki izvēlas operatoru rakstīŔanai Kubernetes. Tam ir objektÄ«vi iemesli, piemēram:

  1. Pastāv jaudÄ«gs ietvars operatoru izstrādei, izmantojot Go — Operator SDK.
  2. Operatori uz Go ir izstrādāti tādiem Ā«spēli mainoÅ”iemĀ» lietotnēm kā Docker un Kubernetes. Rakstot savu operatoru uz Go, jÅ«s runājat ar ekosistēmu vienā valodā.
  3. Augsta Go lietotņu veiktspēja un vienkārÅ”i instrumenti darbam ar konkurenci Ā«no kastesĀ».

PiezÄ«me: Starp citu, kā uzrakstÄ«t savu operatoru uz Go, mēs jau esam aprakstÄ«juÅ”i vienā no mÅ«su tulkojumiem no ārzemju autoriem.

Bet ko darÄ«t, ja Go apgūŔana jums traucē laika trÅ«kums vai, vienkārÅ”i, motivācijas trÅ«kums? Å ajā rakstā ir piemērs, kā var uzrakstÄ«t labu operatoru, izmantojot vienu no populārākajām valodām, ko praktiski pazÄ«st katrs DevOps inženieris — Python.

IepazÄ«stieties: Kopirotājs — kopēŔanas operators!

Par piemēru aplÅ«kosim vienkārÅ”a operatora izstrādi, kas paredzēts ConfigMap kopēŔanai vai nu jauna namespace parādīŔanās gadÄ«jumā, vai arÄ« vienas no divām entitātēm: ConfigMap un Secret maiņas gadÄ«jumā. Praktiskās pielietoÅ”anas viedokļa, operators var bÅ«t noderÄ«gs masveida lietojumprogrammu konfigurāciju atjaunināŔanai (atjauninot ConfigMap) vai arÄ« slepeno datu atjaunināŔanai, piemēram, atslēgām darbam ar Docker Registry (pievienojot Secret ietvarā).

Tātad, kas jābūt labam operatoram:

  1. Saziņa ar operatoru notiek, izmantojot ) pārvaldÄ«ba paÅ”a (turpmāk — CRD).
  2. Operators var būt konfigurējams. Tam mēs izmantosim komandlīnijas karogus un vides mainīgos.
  3. Docker konteineru un Helm shēmu izstrāde tiek veikta 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 un kur meklēt, mums ir jānosaka noteikums. Katrs noteikums tiks attēlots kā viens CRD objekts. Kādi lauki jābÅ«t Å”im CRD?

  1. Resursa tips, kuru mēs meklēsim (ConfigMap vai Secret).
  2. Namespace saraksts, kuros jāatrodas resursiem.
  3. Selector, pēc kura mēs meklēsim resursus namespace.

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ā brÄ«dÄ« izveidosim vienkārÅ”u noteikumu — meklēŔanai namespace ar nosaukumu default visiem ConfigMap ar tādām etiÄ·etēm kā copyrator: "true":

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

Gatavs! Tagad ir jānopelna informācija par mÅ«su noteikumu. Uzreiz jānorāda, ka mēs neizveidosim API Server pieprasÄ«jumus paÅ”i. Mēs izmantosim gatavu Python bibliotēku 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')}

Å Ä« koda izpildes rezultātā saņemsim sekojoÅ”o:

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

Lieliski: mēs esam ieguvuÅ”i noteikumu operatoram. Un, kas ir vissvarÄ«gākais — mēs to izdarÄ«jām, kā mēdz teikt, Kubernetes veidā.

Vides mainīgie vai karogi? Ņemam visu!

Pārejam pie operatora pamatkonfigurācijas. Ir divi pamatpieejas veidi lietojumprogrammu konfigurēŔanai:

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

Komandrindas parametri ļauj elastīgāk nolasīt iestatījumus, atbalstot un validējot datu tipus. Standarta Python bibliotēkā ir modulis argparser, ko mēs izmantosim. Sīkāka informācija un piemēri ir pieejami oficiālajā dokumentācijā.

Tā kā mÅ«su gadÄ«jumā izskatÄ«sies piemērs komandrindas karogu nolasīŔanai:

   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, var viegli pārvietot pakalpojuma informāciju par podu iekŔā konteinerā. Piemēram, informāciju par namespace, kurā ir palaists pods, varam iegÅ«t ar sekojoÅ”o konstrukciju:

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

Operatora darbības loģika

Lai saprastu, kā sadalÄ«t metodes darbÄ«bai ar ConfigMap un Secret, izmantosim Ä«paÅ”as kartes. Tādējādi mēs spēsim saprast, kādas metodes mums nepiecieÅ”amas, lai uzraudzÄ«tu un radÄ«tu 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',
}

Tad ir nepiecieÅ”ams saņemt notikumus no API servera. Realizēsim to Ŕādi:

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

    # Iegūstam metodi, lai uzraudzītu objektus
    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 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 ir jāiekļauj viss tas vienā Python paketē. Noformējam failu setup.py, rakstām tur projektu metainformāciju:

from sys import version_info

from setuptools import find_packages, setup

if version_info[:2] < (3, 5):
    raise RuntimeError(
        'Atbalstītā python versija %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),
        ]
    }
)

Piezīme: Kubernetes klients Pythonam piedāvā savu versiju pārvaldību. PlaŔāku informāciju par klienta versiju un Kubernetes versiju saderību var uzzināt no saderības matricas.

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

copyrator
ā”œā”€ā”€ copyrator
│   ā”œā”€ā”€ cli.py # Darba loÄ£ika ar komandu rindu
│   ā”œā”€ā”€ constant.py # Konstantas, kuras mēs iepriekÅ” minējām
│   ā”œā”€ā”€ load_crd.py # CRD ielādes loÄ£ika
│   └── operator.py # Operatora pamatdarbÄ«bas loÄ£ika
└── setup.py # Paketes noformējums

Docker un Helm

Dockerfile bÅ«s vienkārÅ”s: ņemsim pamatattēlu python-alpine un instalēsim mÅ«su paketi. Tās optimizāciju atliksim uz vēlāku laiku:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

IzvietoŔana operatoram 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, ir jāizveido atbilstoŔa loma operatoram 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 }}

Rezultāts

Tādējādi, bez bailēm, pārmetumiem un Go apguves, mēs spējam izveidot savu paÅ”u operatoru Kubernetes platformai, izmantojot Python. Protams, tam vēl ir kur pilnveidoties: nākotnē tam bÅ«s iespēja apstrādāt vairākus noteikumus, strādāt paralēli, patstāvÄ«gi uzraudzÄ«t savas CRD izmaiņas…

Lai varētu tuvāk iepazÄ«ties ar kodu, mēs to ievietojām publiskajā repozitorijā. Ja vēlaties redzēt nopietnāku operatoru piemērus, kas izstrādāti ar Python palÄ«dzÄ«bu, varat pievērst uzmanÄ«bu diviem operatoriem MongoDB izvietoÅ”anai (pirmais un otrais).

P.S. Un, ja jums ir slinkums izprast Kubernetes notikumus vai vienkārÅ”i ir ērtāk izmantot Bash — mÅ«su kolēģi ir sagatavojuÅ”i gatavu risinājumu, . Izmantojot to, var Å”o izveidot bez Go zināŔanām, bet ar administratoriem pierastām metodēm: uz Bash, Python utt. (mēs paziņoja to izdarÄ«jām aprÄ«lÄ«).

P.P.S.

Lasiet arī mūsu emuāra rakstos:

Avots: habr.com

Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem šŸ”„ Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem | ProHoster