Operatè Kubernetes nan Python san kad ak SDK

Operatè Kubernetes nan Python san kad ak SDK

Go kounye a gen yon monopoli sou langaj pwogramasyon moun chwazi pou ekri deklarasyon pou Kubernetes. Gen rezon objektif pou sa, tankou:

  1. Gen yon fondasyon pwisan pou devlope operatè nan Go - Operatè SDK.
  2. Aplikasyon pou chanje jwèt tankou Docker ak Kubernetes yo ekri nan Go. Ekri operatè ou nan Go vle di pale menm lang ak ekosistèm nan.
  3. Segondè pèfòmans aplikasyon Go ak zouti senp pou travay ak konkou soti nan bwat la.

NB: By wout la, ki jan yo ekri deklarasyon pwòp ou a nan Go, nou deja dekri nan youn nan tradiksyon nou yo pa otè etranje.

Men, e si yo anpeche w aprann Ale pa mank de tan oswa, tou senpleman, motivasyon? Atik la bay yon egzanp sou fason ou ka ekri yon bon deklarasyon lè l sèvi avèk youn nan lang ki pi popilè ke prèske tout enjenyè DevOps konnen - Piton.

Rankontre: Copier - kopi operatè!

Kòm yon egzanp, konsidere devlope yon deklarasyon senp ki fèt pou kopye yon ConfigMap swa lè yon nouvo espas non parèt oswa lè youn nan de antite chanje: ConfigMap ak sekrè. Soti nan yon pwen de vi pratik, operatè a ka itil pou mete ajou konfigirasyon aplikasyon an (pa mete ajou ConfigMap la) oswa pou mete ajou done sekrè - pou egzanp, kle pou travay ak Rejis Docker (lè ajoute sekrè nan espas non an).

Se konsa, kisa yon bon operatè ta dwe genyen:

  1. Se entèraksyon ak operatè a te pote soti lè l sèvi avèk Definisyon Resous Custom (ki refere yo kòm CRD).
  2. Operatè a ka configuré. Pou fè sa, nou pral sèvi ak drapo liy lòd ak varyab anviwònman an.
  3. Konstriksyon veso Docker ak tablo Helm la fèt pou itilizatè yo ka fasilman (literalman ak yon sèl lòd) enstale operatè a nan gwoup Kubernetes yo.

CRD

Pou operatè a konnen ki resous pou chèche ak ki kote yo gade, nou bezwen mete yon règ pou li. Chak règ pral reprezante kòm yon sèl objè CRD. Ki jaden CRD sa a ta dwe genyen?

  1. Kalite resous, ke nou pral chèche (ConfigMap oswa sekrè).
  2. Lis espas non yo, nan ki resous yo ta dwe lokalize.
  3. Selektè, pa ki nou pral chèche resous nan espas non an.

Ann dekri CRD la:

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

Epi nou pral kreye li touswit règ senp — pou fè rechèch nan espas non an ak non an default tout ConfigMap ak etikèt tankou copyrator: "true":

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

Pare! Koulye a, nou bezwen yon jan kanmenm jwenn enfòmasyon sou règ nou an. Kite m 'fè yon rezèvasyon touswit ke nou pa pral ekri demann nan sèvè API gwoup la tèt nou. Pou fè sa, nou pral sèvi ak yon bibliyotèk Python pare kubernetes-kliyan:

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

Kòm yon rezilta nan kouri kòd sa a, nou jwenn bagay sa yo:

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

Gwo: nou jere jwenn yon règ pou operatè a. Ak pi enpòtan, nou te fè sa yo rele fason Kubernetes la.

Varyab anviwònman oswa drapo? Nou pran tout bagay!

Ann ale nan konfigirasyon operatè prensipal la. Gen de apwòch debaz pou konfigirasyon aplikasyon yo:

  1. itilize opsyon liy lòd;
  2. itilize varyab anviwònman yo.

Opsyon liy kòmand pèmèt ou li paramèt plis fleksib, ak sipò kalite done ak validation. Bibliyotèk estanda Python a gen yon modil argparser, ke nou pral itilize. Detay ak egzanp kapasite li yo disponib nan dokiman ofisyèl yo.

Pou ka nou an, sa a se yon egzanp pou mete drapo lekti liy lòd ta sanble:

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

Nan lòt men an, lè l sèvi avèk varyab anviwònman nan Kubernetes, ou ka fasilman transfere enfòmasyon sèvis sou gous la andedan veso a. Pou egzanp, nou ka jwenn enfòmasyon sou espas non kote gous la ap kouri ak konstriksyon sa a:

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

Lojik operatè

Pou konprann kijan pou separe metòd pou travay ak ConfigMap ak sekrè, nou pral sèvi ak kat espesyal. Lè sa a, nou ka konprann ki metòd nou bezwen swiv ak kreye objè a:

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

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

Apre sa, ou bezwen resevwa evènman nan sèvè API a. Ann aplike li jan sa a:

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)

Apre nou fin resevwa evènman an, nou ale nan lojik prensipal la nan trete li:

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

Lojik prensipal la pare! Koulye a, nou bezwen pake tout bagay sa yo nan yon sèl pake Python. Nou prepare dosye a setup.py, ekri meta enfòmasyon sou pwojè a la:

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: Kliyan kubernetes pou Python gen pwòp vèsyon li yo. Ou ka jwenn plis enfòmasyon sou konpatibilite ant vèsyon kliyan ak vèsyon Kubernetes nan matris konpatibilite.

Koulye a, pwojè nou an sanble tankou sa a:

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

Docker ak Helm

Dockerfile a pral ekstrèmman senp: pran imaj la baz python-alpine epi enstale pake nou an. Ann ranvwaye optimize li yo jiskaske pi bon moman:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Deplwaman pou operatè a tou trè senp:

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

Finalman, ou bezwen kreye yon wòl apwopriye pou operatè a ki gen dwa ki nesesè yo:

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

Total

Se konsa, san pè, repwoch, oswa aprann Go, nou te kapab bati pwòp operatè pa nou pou Kubernetes nan Python. Natirèlman, li toujou gen plas yo grandi: nan lavni an li pral kapab trete règ miltip, travay nan plizyè fil, poukont kontwole chanjman nan CRDs li yo ...

Pou ba ou yon gade pi pre nan kòd la, nou te mete l 'nan depo piblik. Si ou vle egzanp operatè ki pi grav aplike lè l sèvi avèk Python, ou ka vire atansyon ou a de operatè pou deplwaye mongodb (premye a и dezyèm).

PS Men, si ou twò parese pou fè fas ak evènman Kubernetes oswa ou tou senpleman plis abitye itilize Bash, kòlèg nou yo te prepare yon solisyon pare-fè nan fòm lan. shell-operateur (Nou te anonse li nan mwa avril).

P

Li tou sou blog nou an:

Sous: www.habr.com

Add nouvo kòmantè