Operator Kubernetes dalam Python tanpa rangka kerja dan SDK

Operator Kubernetes dalam Python tanpa rangka kerja dan SDK

Go kini mempunyai monopoli ke atas bahasa pengaturcaraan yang dipilih oleh orang ramai untuk menulis kenyataan untuk Kubernetes. Terdapat sebab objektif untuk ini, seperti:

  1. Terdapat rangka kerja yang berkuasa untuk membangunkan pengendali dalam Go - SDK Operator.
  2. Aplikasi yang mengubah permainan seperti Docker dan Kubernetes ditulis dalam Go. Menulis operator anda dalam Go bermakna bercakap bahasa yang sama dengan ekosistem.
  3. Prestasi tinggi aplikasi Go dan alatan mudah untuk bekerja dengan konkurensi di luar kotak.

NB: By the way, bagaimana untuk menulis kenyataan anda sendiri dalam Go, kami sudah diterangkan dalam salah satu terjemahan kami oleh pengarang asing.

Tetapi bagaimana jika anda dihalang daripada belajar Pergi kerana kekurangan masa atau, secara ringkasnya, motivasi? Artikel itu memberikan contoh bagaimana anda boleh menulis pernyataan yang baik menggunakan salah satu bahasa paling popular yang hampir setiap jurutera DevOps tahu - Python.

Bertemu: Penyalin - pengendali salinan!

Sebagai contoh, pertimbangkan untuk membangunkan pernyataan ringkas yang direka bentuk untuk menyalin ConfigMap sama ada apabila ruang nama baharu muncul atau apabila salah satu daripada dua entiti berubah: ConfigMap dan Secret. Dari sudut pandangan praktikal, pengendali boleh berguna untuk mengemas kini secara pukal konfigurasi aplikasi (dengan mengemas kini ConfigMap) atau untuk mengemas kini data rahsia - contohnya, kunci untuk bekerja dengan Docker Registry (apabila menambahkan Rahsia pada ruang nama).

Oleh itu, apa yang perlu ada pada pengendali yang baik:

  1. Interaksi dengan pengendali dijalankan menggunakan Definisi Sumber Tersuai (selepas ini dirujuk sebagai CRD).
  2. Operator boleh dikonfigurasikan. Untuk melakukan ini, kami akan menggunakan bendera baris arahan dan pembolehubah persekitaran.
  3. Binaan bekas Docker dan carta Helm direka bentuk supaya pengguna boleh dengan mudah (secara literal dengan satu arahan) memasang pengendali ke dalam kelompok Kubernetes mereka.

CRD

Agar pengendali mengetahui sumber yang hendak dicari dan di mana hendak dicari, kita perlu menetapkan peraturan untuknya. Setiap peraturan akan diwakili sebagai objek CRD tunggal. Apakah bidang yang perlu ada pada CRD ini?

  1. Jenis sumber, yang akan kami cari (ConfigMap atau Secret).
  2. Senarai ruang nama, di mana sumber harus ditempatkan.
  3. Pemilih, yang mana kami akan mencari sumber dalam ruang nama.

Mari kita huraikan 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

Dan kami akan menciptanya serta-merta peraturan mudah β€” untuk mencari dalam ruang nama dengan nama default semua ConfigMap dengan label seperti copyrator: "true":

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

sedia! Sekarang kita perlu mendapatkan maklumat tentang peraturan kita. Biar saya membuat tempahan dengan segera bahawa kami tidak akan menulis permintaan kepada Pelayan API kluster sendiri. Untuk melakukan ini, kami akan menggunakan perpustakaan Python siap sedia 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')}

Hasil daripada menjalankan kod ini, kami mendapat yang berikut:

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

Hebat: kami berjaya mendapatkan peraturan untuk pengendali. Dan yang paling penting, kami melakukan apa yang dipanggil cara Kubernetes.

Pembolehubah persekitaran atau bendera? Kami mengambil segala-galanya!

Mari kita beralih kepada konfigurasi pengendali utama. Terdapat dua pendekatan asas untuk mengkonfigurasi aplikasi:

  1. gunakan pilihan baris arahan;
  2. menggunakan pembolehubah persekitaran.

Pilihan baris perintah membolehkan anda membaca tetapan dengan lebih fleksibel, dengan sokongan dan pengesahan jenis data. Pustaka standard Python mempunyai modul argparser, yang akan kami gunakan. Butiran dan contoh keupayaannya tersedia dalam dokumentasi rasmi.

Untuk kes kami, ini adalah contoh penyediaan bendera baris arahan membaca:

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

Sebaliknya, menggunakan pembolehubah persekitaran dalam Kubernetes, anda boleh memindahkan maklumat perkhidmatan tentang pod di dalam bekas dengan mudah. Sebagai contoh, kita boleh mendapatkan maklumat tentang ruang nama di mana pod dijalankan dengan pembinaan berikut:

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

Logik operator

Untuk memahami cara mengasingkan kaedah untuk bekerja dengan ConfigMap dan Secret, kami akan menggunakan peta khas. Kemudian kita boleh memahami kaedah yang kita perlukan untuk menjejak dan mencipta objek:

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

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

Seterusnya, anda perlu menerima acara daripada pelayan API. Mari kita laksanakan seperti berikut:

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)

Selepas menerima acara itu, kami beralih kepada logik utama memprosesnya:

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

Logik utama sudah siap! Sekarang kita perlu membungkus semua ini ke dalam satu pakej Python. Kami menyediakan fail setup.py, tulis maklumat meta tentang projek di sana:

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: Pelanggan kubernetes untuk Python mempunyai versinya sendiri. Maklumat lanjut tentang keserasian antara versi klien dan versi Kubernetes boleh didapati dalam matriks keserasian.

Sekarang projek kami kelihatan seperti ini:

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

Docker dan Helm

Fail Docker akan menjadi sangat mudah: ambil imej python-alpine asas dan pasang pakej kami. Mari kita tangguhkan pengoptimumannya sehingga masa yang lebih baik:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Penerapan untuk pengendali juga sangat mudah:

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

Akhir sekali, anda perlu mencipta peranan yang sesuai untuk pengendali dengan hak yang diperlukan:

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

Jumlah

Begitulah, tanpa rasa takut, celaan atau belajar Go, kami dapat membina operator kami sendiri untuk Kubernetes dalam Python. Sudah tentu, ia masih mempunyai ruang untuk berkembang: pada masa hadapan ia akan dapat memproses berbilang peraturan, berfungsi dalam berbilang rangkaian, memantau perubahan dalam CRDnya secara bebas...

Untuk memberi anda pandangan yang lebih dekat pada kod tersebut, kami telah memasukkannya repositori awam. Jika anda mahukan contoh pengendali yang lebih serius dilaksanakan menggunakan Python, anda boleh mengalihkan perhatian anda kepada dua operator untuk menggunakan mongodb (ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈ 2).

PS Dan jika anda terlalu malas untuk berurusan dengan acara Kubernetes atau anda lebih biasa menggunakan Bash, rakan sekerja kami telah menyediakan penyelesaian siap dalam bentuk pengendali shell (Kami diumumkan pada bulan April).

PPS

Baca juga di blog kami:

Sumber: www.habr.com

Tambah komen