Çerçeveler ve SDK olmadan Python'da Kubernetes Operatörü

Çerçeveler ve SDK olmadan Python'da Kubernetes Operatörü

Go şu anda insanların Kubernetes için açıklamalar yazmak için seçtiği programlama dilleri üzerinde tekele sahip. Bunun nesnel nedenleri var:

  1. Go'da operatörleri geliştirmek için güçlü bir çerçeve var - Operatör SDK'sı.
  2. Docker ve Kubernetes gibi oyunun kurallarını değiştiren uygulamalar Go'da yazılmıştır. Operatörünüzü Go'da yazmak, ekosistemle aynı dili konuşmak anlamına gelir.
  3. Go uygulamalarının yüksek performansı ve kullanıma hazır eşzamanlılıkla çalışmaya yönelik basit araçlar.

NB: Bu arada Go'da kendi ifadenizi nasıl yazacağınızı anlattık. zaten tanımlanmış Yabancı yazarlar tarafından yapılan çevirilerimizden birinde.

Peki ya zaman eksikliği ya da basitçe söylemek gerekirse motivasyon nedeniyle Go'yu öğrenmeniz engelleniyorsa? Makale, hemen hemen her DevOps mühendisinin bildiği en popüler dillerden birini kullanarak nasıl iyi bir ifade yazabileceğinize dair bir örnek sunmaktadır: Python.

Tanışın: Fotokopi Makinesi - fotokopi operatörü!

Örnek olarak, yeni bir ad alanı göründüğünde veya iki varlıktan biri değiştiğinde bir ConfigMap'i kopyalamak için tasarlanmış basit bir ifade geliştirmeyi düşünün: ConfigMap ve Secret. Pratik bir bakış açısına göre operatör, uygulama yapılandırmalarının toplu olarak güncellenmesi (ConfigMap'i güncelleyerek) veya gizli verilerin (örneğin, Docker Kayıt Defteri ile çalışmaya yönelik anahtarlar (ad alanına Secret eklenirken) güncellenmesi için yararlı olabilir.

Bu durumda, iyi bir operatörün sahip olması gerekenler:

  1. Operatörle etkileşim kullanılarak gerçekleştirilir. Özel Kaynak Tanımları (bundan böyle CRD olarak anılacaktır).
  2. Operatör yapılandırılabilir. Bunu yapmak için komut satırı bayraklarını ve ortam değişkenlerini kullanacağız.
  3. Docker konteynerinin ve Helm grafiğinin yapısı, kullanıcıların operatörü Kubernetes kümelerine kolayca (kelimenin tam anlamıyla tek komutla) yükleyebileceği şekilde tasarlanmıştır.

CRD

Operatörün hangi kaynakları arayacağını, nereye bakacağını bilmesi için ona bir kural koymamız gerekiyor. Her kural tek bir CRD nesnesi olarak temsil edilecektir. Bu CRD'nin hangi alanları olmalıdır?

  1. Kaynak tipiarayacağımız (ConfigMap veya Secret).
  2. Ad alanlarının listesiKaynakların nerede bulunması gerektiği.
  3. Seçici, ad alanındaki kaynakları arayacağız.

CRD’yi tanımlayalım:

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

Ve onu hemen yaratacağız basit kural — ad alanında adla arama yapmak için default gibi etiketlere sahip tüm ConfigMap copyrator: "true":

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

Hazır! Artık bir şekilde kuralımız hakkında bilgi almamız gerekiyor. Cluster API Server'a kendimiz istek yazmayacağız diye hemen rezervasyon yaptırayım. Bunu yapmak için hazır bir Python kütüphanesi kullanacağız. kubernetes istemcisi:

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

Bu kodun çalıştırılması sonucunda aşağıdakileri elde ederiz:

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

Harika: Operatör için bir kural almayı başardık. Ve en önemlisi Kubernetes yolu denilen şeyi yaptık.

Ortam değişkenleri mi yoksa bayraklar mı? Her şeyi alıyoruz!

Ana operatör konfigürasyonuna geçelim. Uygulamaları yapılandırmak için iki temel yaklaşım vardır:

  1. komut satırı seçeneklerini kullanın;
  2. ortam değişkenlerini kullanın.

Komut satırı seçenekleri, veri türü desteği ve doğrulamayla ayarları daha esnek bir şekilde okumanıza olanak tanır. Python'un standart kütüphanesinde bir modül var argparser, bunu kullanacağız. Yeteneklerinin ayrıntıları ve örnekleri şurada mevcuttur: resmi belgeler.

Bizim durumumuz için, komut satırı bayraklarının okunmasına ilişkin bir örnek şu şekilde görünecektir:

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

Öte yandan Kubernetes'teki ortam değişkenlerini kullanarak, konteynerin içindeki pod'a ilişkin servis bilgilerini kolaylıkla aktarabilirsiniz. Örneğin aşağıdaki yapıyla pod'un çalıştığı ad alanı hakkında bilgi alabiliriz:

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

Operatör mantığı

ConfigMap ve Secret ile çalışma yöntemlerinin nasıl ayrılacağını anlamak için özel haritalar kullanacağız. Daha sonra nesneyi takip etmek ve oluşturmak için hangi yöntemlere ihtiyacımız olduğunu anlayabiliriz:

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

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

Daha sonra, API sunucusundan olayları almanız gerekir. Aşağıdaki gibi uygulayalım:

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)

Etkinliği aldıktan sonra, onu işlemenin ana mantığına geçiyoruz:

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

Ana mantık hazır! Şimdi tüm bunları tek bir Python paketinde paketlememiz gerekiyor. Dosyayı hazırlıyoruz setup.py, projeyle ilgili meta bilgileri buraya yazın:

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: Python için kubernetes istemcisinin kendi sürümü vardır. İstemci sürümleri ile Kubernetes sürümleri arasındaki uyumluluk hakkında daha fazla bilgiyi şu adreste bulabilirsiniz: uyumluluk matrisleri.

Artık projemiz şuna benziyor:

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

Docker ve Helm

Dockerfile inanılmaz derecede basit olacak: temel python-alpine imajını alın ve paketimizi kurun. Optimizasyonunu daha iyi zamanlara erteleyelim:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Operatör için dağıtım da çok basittir:

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

Son olarak operatör için gerekli haklara sahip uygun bir rol oluşturmanız gerekir:

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

sonuç

Bu sayede korkmadan, suçlamadan veya Go'yu öğrenmeden Python'da Kubernetes için kendi operatörümüzü oluşturabildik. Elbette hala büyüme alanı var: gelecekte birden fazla kuralı işleyebilecek, birden fazla iş parçacığında çalışabilecek, CRD'lerindeki değişiklikleri bağımsız olarak izleyebilecek...

Kodu daha yakından görebilmeniz için onu koyduk halka açık depo. Python kullanılarak uygulanan daha ciddi operatörlerin örneklerini istiyorsanız, mongodb () dağıtımı için dikkatinizi iki operatöre çevirebilirsiniz.ilk и ikinci).

Not: Kubernetes olaylarıyla ilgilenemeyecek kadar tembelseniz veya Bash'i kullanmaya daha alışkınsanız, meslektaşlarımız formda hazır bir çözüm hazırladılar. kabuk operatörü (Biz açıkladı Nisan ayında).

PPS

Blogumuzda da okuyun:

Kaynak: habr.com

Yorum ekle