Operator Kubernetes dina Python tanpa kerangka sareng SDK

Operator Kubernetes dina Python tanpa kerangka sareng SDK

Go ayeuna gaduh monopoli dina basa pamrograman anu dipilih ku jalma pikeun nyerat pernyataan pikeun Kubernetes. Aya alesan obyektif pikeun ieu, sapertos:

  1. Aya kerangka anu kuat pikeun ngembangkeun operator di Go - Operator SDK.
  2. Aplikasi anu ngarobih kaulinan sapertos Docker sareng Kubernetes ditulis dina Go. Nulis operator anjeun dina Go hartina nyarios basa anu sami sareng ékosistem.
  3. Kinerja luhur aplikasi Go sareng alat saderhana pikeun damel sareng concurrency out of the box.

NB: Ku jalan kitu, kumaha carana nulis pernyataan sorangan di Go, urang geus digambarkeun dina salah sahiji tarjamahan urang ku pangarang asing.

Tapi kumaha upami anjeun dicegah tina diajar Go ku kakurangan waktos atanapi, kantun nempatkeun, motivasi? Tulisan éta nyayogikeun conto kumaha anjeun tiasa nyerat pernyataan anu saé nganggo salah sahiji basa anu pang populerna anu ampir unggal insinyur DevOps terang - Python.

Papanggih: Copier - copy operator!

Salaku conto, mertimbangkeun ngamekarkeun hiji pernyataan basajan dirancang pikeun nyalin a ConfigMap boh lamun spasi ngaran anyar nembongan atawa lamun salah sahiji dua éntitas robah: ConfigMap jeung Rahasia. Tina sudut pandang praktis, operator tiasa mangpaat pikeun ngamutahirkeun massal tina konfigurasi aplikasi (ku ngamutahirkeun ConfigMap) atanapi pikeun ngamutahirkeun data rusiah - contona, konci pikeun damel sareng Docker Registry (nalika nambihan Rahasia kana namespace).

Ku kituna, naon operator alus kudu boga:

  1. Interaksi sareng operator dilaksanakeun nganggo Harti Sumberdaya Adat (satuluyna disebut CRD).
  2. Operator tiasa dikonpigurasikeun. Jang ngalampahkeun ieu, urang bakal nganggo bendera garis paréntah sareng variabel lingkungan.
  3. Wangunan wadah Docker sareng bagan Helm dirarancang supados pangguna tiasa (sacara harfiah nganggo hiji paréntah) masang operator kana klaster Kubernetes na.

CRD

Supados operator uninga sumberdaya naon néangan jeung dimana néangan, urang kudu nangtukeun aturan pikeun anjeunna. Unggal aturan bakal digambarkeun salaku obyék CRD tunggal. Widang naon anu kedah aya CRD ieu?

  1. Jenis sumberdaya, nu urang bakal néangan (ConfigMap atawa Rusiah).
  2. Daptar ngaranspasi, di mana sumberdaya kudu ayana.
  3. Pamilihan, ku mana urang bakal milarian sumber dina rohangan ngaran.

Hayu urang ngajelaskeun 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

Sarta kami bakal nyieun eta langsung aturan saderhana — pikeun milarian dina rohangan ngaran kalayan nami default kabéh ConfigMap kalawan labél kawas copyrator: "true":

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

Siap! Ayeuna urang kudu kumaha bae meunang informasi ngeunaan aturan urang. Hayu atuh nyieun reservasi katuhu jauh yén kami moal nulis requests ka klaster API Server sorangan. Jang ngalampahkeun ieu, urang bakal ngagunakeun perpustakaan Python siap-dijieun 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')}

Salaku hasil tina ngajalankeun kode ieu, urang meunang kieu:

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

Hébat: urang junun meunang aturan pikeun operator. Sareng anu paling penting, urang ngalakukeun naon anu disebut cara Kubernetes.

Variabel lingkungan atanapi bandéra? Urang nyandak sagalana!

Hayu urang ngaléngkah ka konfigurasi operator utama. Aya dua pendekatan dasar pikeun ngonpigurasikeun aplikasi:

  1. ngagunakeun pilihan garis paréntah;
  2. ngagunakeun variabel lingkungan.

Pilihan garis paréntah ngamungkinkeun anjeun pikeun maca setélan langkung fleksibel, kalayan dukungan sareng validasi jinis data. perpustakaan baku Python urang boga modul argparser, anu bakal kami anggo. Rincian sareng conto kamampuanna sayogi di dokuméntasi resmi.

Pikeun kasus urang, ieu mangrupikeun conto kumaha nyetél umbul paréntah maca sapertos kieu:

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

Di sisi séjén, ngagunakeun variabel lingkungan di Kubernetes, anjeun bisa kalayan gampang nransper informasi jasa ngeunaan pod jero wadahna. Salaku conto, urang tiasa nampi inpormasi ngeunaan rohangan ngaran tempat pod dijalankeun kalayan konstruksi ieu:

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

Logika operator

Pikeun ngartos kumaha cara misahkeun metode pikeun gawé bareng ConfigMap sareng Secret, kami bakal nganggo peta khusus. Teras urang tiasa ngartos metode naon anu urang peryogikeun pikeun ngalacak sareng nyiptakeun obyék:

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

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

Salajengna, anjeun kedah nampi acara ti server API. Hayu urang laksanakeun saperti kieu:

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)

Saatos nampi acara, urang teraskeun kana logika utama ngolahna:

# Типы событий, на которые будем реагировать
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 parantos siap! Ayeuna urang kedah ngarangkep sadayana ieu kana hiji pakét Python. Urang nyiapkeun file setup.py, nyerat inpormasi meta ngeunaan proyék di dinya:

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 pikeun Python gaduh versi sorangan. Inpormasi langkung seueur ngeunaan kasaluyuan antara versi klien sareng versi Kubernetes tiasa dipendakan di matriks kasaluyuan.

Ayeuna proyek urang kasampak kawas kieu:

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

Docker jeung Helm

Dockerfile bakal saderhana pisan: cokot gambar python-alpine dasar sareng pasang pakét kami. Hayu urang tunda optimasina dugi ka waktos anu langkung saé:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Panyebaran pikeun operator ogé saderhana pisan:

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

Tungtungna, anjeun kedah nyiptakeun peran anu pas pikeun operator anu ngagaduhan hak anu diperyogikeun:

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

hasil

Éta kumaha, tanpa sieun, hinaan, atanapi diajar Go, kami tiasa ngawangun operator sorangan pikeun Kubernetes dina Python. Tangtosna, éta masih ngagaduhan rohangan pikeun tumbuh: ka hareupna bakal tiasa ngolah sababaraha aturan, damel di sababaraha utas, sacara mandiri ngawas parobahan dina CRD na ...

Pikeun masihan anjeun katingal langkung caket kana kode, kami parantos nempatkeun éta gudang umum. Upami anjeun hoyong conto operator anu langkung serius dilaksanakeun nganggo Python, anjeun tiasa ngalihkeun perhatian anjeun ka dua operator pikeun nyebarkeun mongodb (первый и kadua).

PS Sareng upami anjeun puguh teuing pikeun nganyahokeun acara Kubernetes atanapi anjeun ngan saukur langkung biasa ngagunakeun Bash, kolega kami parantos nyiapkeun solusi anu siap dina bentuk. cangkang-operator (Urang ngumumkeun éta dina April).

PPS

Baca ogé dina blog urang:

sumber: www.habr.com

Tambahkeun komentar