Kubernetes Operator í Python án ramma og SDK

Kubernetes Operator í Python án ramma og SDK

Go hefur sem stendur einokun á forritunarmálunum sem fólk velur að skrifa yfirlýsingar fyrir Kubernetes. Fyrir því eru hlutlægar ástæður, svo sem:

  1. Það er öflugur rammi til að þróa rekstraraðila í Go - SDK rekstraraðila.
  2. Leikjabreytandi forrit eins og Docker og Kubernetes eru skrifuð í Go. Að skrifa símafyrirtækið þitt í Go þýðir að tala sama tungumálið við vistkerfið.
  3. Mikil afköst Go forrita og einföld verkfæri til að vinna með samhliða út úr kassanum.

NB: Við the vegur, hvernig á að skrifa þína eigin yfirlýsingu í Go, we þegar lýst er í einni af þýðingum okkar eftir erlenda höfunda.

En hvað ef þú ert hindraður í að læra Go vegna tímaskorts eða einfaldlega hvatningar? Greinin gefur dæmi um hvernig þú getur skrifað góða yfirlýsingu með því að nota eitt vinsælasta tungumálið sem næstum allir DevOps verkfræðingar þekkja - Python.

Hittu: Ljósritunarvél - afritunaraðili!

Sem dæmi skaltu íhuga að þróa einfalda yfirlýsingu sem er hönnuð til að afrita ConfigMap annað hvort þegar nýtt nafnrými birtist eða þegar annar af tveimur einingum breytist: ConfigMap og Secret. Frá hagnýtu sjónarhorni getur rekstraraðilinn verið gagnlegur fyrir magnuppfærslur á stillingum forrita (með því að uppfæra ConfigMap) eða til að uppfæra leynileg gögn - til dæmis lykla til að vinna með Docker Registry (þegar Secret er bætt við nafnrýmið).

Svo, hvað góður rekstraraðili ætti að hafa:

  1. Samskipti við rekstraraðila fara fram með því að nota Sérsniðnar auðlindaskilgreiningar (hér eftir nefnt CRD).
  2. Hægt er að stilla rekstraraðila. Til að gera þetta munum við nota skipanalínuflögg og umhverfisbreytur.
  3. Smíði Docker gámsins og Helm töflunnar er hannað þannig að notendur geti auðveldlega (bókstaflega með einni skipun) sett upp rekstraraðilann í Kubernetes þyrpinguna sína.

CRD

Til þess að rekstraraðili viti hvaða úrræði á að leita að og hvert á að leita þurfum við að setja reglu fyrir hann. Hver regla verður sýnd sem einn CRD hlutur. Hvaða reiti ætti þetta CRD að hafa?

  1. Gerð auðlindar, sem við munum leita að (ConfigMap eða Secret).
  2. Listi yfir nafnrými, þar sem auðlindirnar ættu að vera staðsettar.
  3. Val, þar sem við munum leita að tilföngum í nafnrýminu.

Við skulum lýsa 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

Og við munum búa það til strax einföld regla — til að leita í nafnrýminu með nafninu default allt ConfigMap með merki eins og copyrator: "true":

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

Tilbúið! Nú þurfum við einhvern veginn að fá upplýsingar um regluna okkar. Leyfðu mér að gera fyrirvara strax um að við munum ekki skrifa beiðnir til cluster API Server sjálf. Til að gera þetta munum við nota tilbúið Python bókasafn kubernetes-viðskiptavinur:

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

Sem afleiðing af því að keyra þennan kóða fáum við eftirfarandi:

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

Frábært: okkur tókst að fá reglu fyrir rekstraraðilann. Og síðast en ekki síst, við gerðum það sem kallast Kubernetes leiðin.

Umhverfisbreytur eða fánar? Við tökum allt!

Við skulum halda áfram í aðalstillingu rekstraraðila. Það eru tvær grundvallaraðferðir við að stilla forrit:

  1. notaðu skipanalínuvalkosti;
  2. nota umhverfisbreytur.

Skipanalínuvalkostir gera þér kleift að lesa stillingar á sveigjanlegri hátt, með stuðningi og staðfestingu gagnategunda. Staðlað bókasafn Python er með einingu argparser, sem við munum nota. Upplýsingar og dæmi um getu þess eru fáanleg í opinber skjöl.

Fyrir okkar tilvik, þetta er hvernig dæmi um að setja upp lestur skipanalínuflögg myndi líta út:

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

Á hinn bóginn, með því að nota umhverfisbreytur í Kubernetes, geturðu auðveldlega flutt þjónustuupplýsingar um belg inni í ílátinu. Til dæmis getum við fengið upplýsingar um nafnrýmið sem belgurinn er í gangi með eftirfarandi byggingu:

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

Rógík rekstraraðila

Til að skilja hvernig á að aðskilja aðferðir til að vinna með ConfigMap og Secret, munum við nota sérstök kort. Þá getum við skilið hvaða aðferðir við þurfum til að rekja og búa til hlutinn:

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

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

Næst þarftu að fá viðburði frá API þjóninum. Við skulum útfæra það sem hér segir:

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)

Eftir að við höfum fengið viðburðinn förum við yfir í meginrökfræðina við að vinna úr honum:

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

Helstu rökfræðin er tilbúin! Nú þurfum við að pakka þessu öllu í einn Python pakka. Við undirbúum skrána setup.py, skrifaðu meta upplýsingar um verkefnið þar:

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: Kubernetes viðskiptavinurinn fyrir Python hefur sína eigin útgáfu. Frekari upplýsingar um samhæfni milli útgáfu viðskiptavina og Kubernetes útgáfur er að finna í eindrægni fylki.

Nú lítur verkefnið okkar svona út:

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

Docker og Helm

Dockerfile verður ótrúlega einfalt: taktu grunn python-alpine myndina og settu upp pakkann okkar. Við skulum fresta hagræðingu þess þar til betri tímar:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Uppsetning fyrir rekstraraðila er líka mjög einföld:

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

Að lokum þarftu að búa til viðeigandi hlutverk fyrir rekstraraðila með nauðsynlegum réttindum:

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

Samtals

Þannig gátum við, án þess að óttast, ávíta eða læra Go, byggt upp okkar eigin símafyrirtæki fyrir Kubernetes í Python. Auðvitað hefur það enn pláss til að vaxa: í framtíðinni mun það geta unnið úr mörgum reglum, unnið í mörgum þráðum, sjálfstætt fylgst með breytingum á CRDs þess ...

Til að skoða kóðann nánar höfum við sett hann inn opinber geymsla. Ef þú vilt dæmi um alvarlegri rekstraraðila útfærð með Python geturðu beint athyglinni að tveimur rekstraraðilum til að dreifa mongodb (первый и annað).

PS Og ef þú ert of latur til að takast á við Kubernetes viðburði eða þú ert einfaldlega vanari að nota Bash, hafa samstarfsmenn okkar útbúið tilbúna lausn í formi skel-rekstraraðili (Við tilkynnt það í apríl).

Pps

Lestu líka á blogginu okkar:

Heimild: www.habr.com

Bæta við athugasemd