Operator Kubernetes ing Python tanpa kerangka lan SDK

Operator Kubernetes ing Python tanpa kerangka lan SDK

Go saiki duwe monopoli ing basa pemrograman sing dipilih wong kanggo nulis pernyataan kanggo Kubernetes. Ana alasan objektif, kayata:

  1. Ana kerangka kuat kanggo ngembangake operator ing Go - Operator SDK.
  2. Aplikasi sing ngganti game kaya Docker lan Kubernetes ditulis ing Go. Nulis operator sampeyan ing Go tegese nganggo basa sing padha karo ekosistem.
  3. Kinerja dhuwur saka aplikasi Go lan alat prasaja kanggo nggarap concurrency metu saka kothak.

NB: Miturut cara, carane nulis statement dhewe ing Go, kita wis diterangake ing salah sawijining terjemahan kita dening penulis asing.

Nanging apa yen sampeyan ora bisa sinau Go amarga ora duwe wektu utawa, kanthi gampang, motivasi? Artikel kasebut menehi conto carane sampeyan bisa nulis statement sing apik nggunakake salah sawijining basa sing paling populer sing meh kabeh insinyur DevOps ngerti - Python.

Meet: Copier - copy operator!

Contone, nimbang ngembangake statement prasaja sing dirancang kanggo nyalin ConfigMap nalika ana spasi jeneng anyar utawa nalika salah siji saka rong entitas diganti: ConfigMap lan Secret. Saka sudut pandang praktis, operator bisa migunani kanggo nganyari konfigurasi aplikasi kanthi akeh (kanthi nganyari ConfigMap) utawa kanggo nganyari data rahasia - contone, tombol kanggo nggarap Registry Docker (nalika nambahake Rahasia menyang ruang jeneng).

Supaya apa operator apik kudu duwe:

  1. Interaksi karo operator digawa metu nggunakake Definisi Sumber Daya Kustom (sabanjurΓ© diarani CRD).
  2. Operator bisa diatur. Kanggo nindakake iki, kita bakal nggunakake panji baris printah lan variabel lingkungan.
  3. Pangwangunan wadhah Docker lan bagan Helm dirancang supaya pangguna bisa kanthi gampang (secara harfiah karo siji printah) nginstal operator menyang kluster Kubernetes.

CRD

Supaya operator ngerti apa sumber daya kanggo nggoleki lan ngendi kanggo katon, kita kudu nyetel aturan kanggo wong. Saben aturan bakal dituduhake minangka obyek CRD siji. Apa lapangan sing kudu diduweni CRD iki?

  1. jinis sumber daya, sing bakal kita goleki (ConfigMap utawa Secret).
  2. Dhaptar namespaces, ing ngendi sumber daya kudu dumunung.
  3. Selector, sing bakal kita goleki sumber daya ing ruang jeneng.

Ayo diterangake 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

Lan kita bakal nggawe langsung aturan sing gampang - kanggo nelusuri ing namespace karo jeneng default kabeh ConfigMap karo label kaya copyrator: "true":

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

siyap! Saiki kita kudu entuk informasi babagan aturan kita. Ayo kula nggawe leladen langsung sing kita ora bakal nulis panjalukan kanggo kluster API Server dhΓ©wΓ©. Kanggo nindakake iki, kita bakal nggunakake perpustakaan Python siap-digawe kubernetes-klien:

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

Minangka asil mbukak kode iki, kita entuk ing ngisor iki:

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

Apik: kita bisa njaluk aturan kanggo operator. Lan sing paling penting, kita nindakake apa sing diarani cara Kubernetes.

Variabel lingkungan utawa gendera? We njupuk kabeh!

Ayo pindhah menyang konfigurasi operator utama. Ana rong pendekatan dhasar kanggo ngatur aplikasi:

  1. nggunakake opsi baris printah;
  2. nggunakake variabel lingkungan.

Opsi baris printah ngidini sampeyan maca setelan kanthi luwih fleksibel, kanthi dhukungan lan validasi jinis data. perpustakaan standar Python wis modul argparser, sing bakal kita gunakake. Rincian lan conto kemampuane kasedhiya ing dokumentasi resmi.

Kanggo kasus kita, iki minangka conto nyetel panji baris perintah maca:

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

Ing sisih liya, nggunakake variabel lingkungan ing Kubernetes, sampeyan bisa kanthi gampang nransfer informasi layanan babagan pod ing wadhah kasebut. Contone, kita bisa entuk informasi babagan ruang jeneng ing ngendi pod mlaku kanthi konstruksi ing ngisor iki:

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

Operator logika

Kanggo mangerteni carane misahake cara kanggo nggarap ConfigMap lan Secret, kita bakal nggunakake peta khusus. Banjur kita bisa ngerti cara apa sing kudu dilacak lan nggawe obyek kasebut:

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

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

Sabanjure, sampeyan kudu nampa acara saka server API. Ayo dileksanakake kaya ing ngisor iki:

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)

Sawise nampa acara kasebut, kita pindhah menyang logika utama ngolah:

# Π’ΠΈΠΏΡ‹ событий, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π±ΡƒΠ΄Π΅ΠΌ Ρ€Π΅Π°Π³ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ
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 utama wis siyap! Saiki kita kudu ngemas kabeh iki dadi siji paket Python. Kita nyiapake file setup.py, tulis informasi meta babagan proyek ing kana:

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: Klien kubernetes kanggo Python duwe versi dhewe. Informasi liyane babagan kompatibilitas antarane versi klien lan versi Kubernetes bisa ditemokake ing matriks kompatibilitas.

Saiki proyek kita katon kaya iki:

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

Docker lan Helm

Dockerfile bakal gampang banget: njupuk gambar python-alpine dhasar lan instal paket kita. Ayo nundha optimasi nganti wektu sing luwih apik:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Panyebaran kanggo operator uga gampang banget:

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

Pungkasan, sampeyan kudu nggawe peran sing cocog kanggo operator kanthi hak sing dibutuhake:

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

Asile

Mangkono, tanpa wedi, cela, utawa sinau Go, kita bisa mbangun operator dhewe kanggo Kubernetes ing Python. Mesthi wae, isih duwe ruang kanggo tuwuh: ing mangsa ngarep bakal bisa ngolah pirang-pirang aturan, bisa digunakake ing pirang-pirang utas, kanthi mandiri ngawasi owah-owahan ing CRDs ...

Kanggo menehi tampilan sing luwih cetha ing kode, kita wis sijine ing gudang umum. Yen sampeyan pengin conto operator sing luwih serius sing dileksanakake nggunakake Python, sampeyan bisa nguripake manungsa waΓ© menyang rong operator kanggo deploying mongodb (ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈ sing kapindho).

PS Lan yen sampeyan kesed kanggo ngatasi acara Kubernetes utawa sampeyan mung luwih biasa nggunakake Bash, kanca-kanca wis nyiapake solusi sing wis siap ing wangun cangkang-operator (We diumumake ing April).

PPS

Waca uga ing blog kita:

Source: www.habr.com

Add a comment