Gweithredwr Kubernetes yn Python heb fframweithiau a SDK

Gweithredwr Kubernetes yn Python heb fframweithiau a SDK

Ar hyn o bryd mae gan Go fonopoli ar yr ieithoedd rhaglennu y mae pobl yn eu dewis i ysgrifennu datganiadau ar gyfer Kubernetes. Mae rhesymau gwrthrychol am hyn, megis:

  1. Mae fframwaith pwerus ar gyfer datblygu gweithredwyr yn Go - Gweithredwr SDK.
  2. Mae cymwysiadau sy'n newid gemau fel Docker a Kubernetes wedi'u hysgrifennu yn Go. Mae ysgrifennu eich gweithredwr yn Go yn golygu siarad yr un iaith Γ’'r ecosystem.
  3. Perfformiad uchel o geisiadau Go ac offer syml ar gyfer gweithio gyda concurrency allan o'r bocs.

NB: Gyda llaw, sut i ysgrifennu eich datganiad eich hun yn Go, ni a ddisgrifiwyd eisoes yn un o'n cyfieithiadau gan awduron tramor.

Ond beth os cewch eich rhwystro rhag dysgu Ewch oherwydd diffyg amser neu, yn syml, cymhelliant? Mae'r erthygl yn rhoi enghraifft o sut y gallwch chi ysgrifennu datganiad da gan ddefnyddio un o'r ieithoedd mwyaf poblogaidd y mae bron pob peiriannydd DevOps yn ei wybod - Python.

Cyfarfod: CopΓ―wr - gweithredwr copi!

Er enghraifft, ystyriwch ddatblygu datganiad syml wedi'i gynllunio i gopΓ―o ConfigMap naill ai pan fydd gofod enw newydd yn ymddangos neu pan fydd un o ddau endid yn newid: ConfigMap a Secret. O safbwynt ymarferol, gall y gweithredwr fod yn ddefnyddiol ar gyfer diweddaru ffurfweddiadau cymwysiadau mewn swmp (trwy ddiweddaru'r ConfigMap) neu ar gyfer diweddaru data cyfrinachol - er enghraifft, allweddi ar gyfer gweithio gyda Chofrestrfa Docker (wrth ychwanegu Secret i'r gofod enw).

Felly, beth ddylai fod gan weithredwr da:

  1. Cyflawnir rhyngweithio Γ’'r gweithredwr gan ddefnyddio Diffiniadau Adnoddau Personol (cyfeirir ato o hyn ymlaen fel CRD).
  2. Gellir ffurfweddu'r gweithredwr. I wneud hyn, byddwn yn defnyddio baneri llinell orchymyn a newidynnau amgylchedd.
  3. Mae adeiladu'r cynhwysydd Docker a siart Helm wedi'i gynllunio fel y gall defnyddwyr yn hawdd (yn llythrennol gydag un gorchymyn) osod y gweithredwr yn eu clwstwr Kubernetes.

CRD

Er mwyn i'r gweithredwr wybod pa adnoddau i chwilio amdanynt a ble i edrych, mae angen inni osod rheol ar ei gyfer. Bydd pob rheol yn cael ei chynrychioli fel un gwrthrych CRD. Pa feysydd ddylai fod gan y CRD hwn?

  1. Math o adnodd, y byddwn yn edrych amdano (ConfigMap neu Secret).
  2. Rhestr o ofodau enwau, y dylid lleoli'r adnoddau ynddo.
  3. Dewisydd, lle byddwn yn chwilio am adnoddau yn y gofod enwau.

Gadewch i ni ddisgrifio'r 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

A byddwn yn ei greu ar unwaith rheol syml β€” i chwilio yn y gofod enwau gyda'r enw default pob ConfigMap gyda labeli fel copyrator: "true":

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

Barod! Nawr mae angen i ni rywsut gael gwybodaeth am ein rheol. Gadewch imi archebu ar unwaith na fyddwn yn ysgrifennu ceisiadau at y Gweinydd API clwstwr ein hunain. I wneud hyn, byddwn yn defnyddio llyfrgell Python parod kubernetes-cleient:

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

O ganlyniad i redeg y cod hwn, rydym yn cael y canlynol:

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

Gwych: llwyddasom i gael rheol i'r gweithredwr. Ac yn bwysicaf oll, gwnaethom yr hyn a elwir yn ffordd Kubernetes.

Newidynnau amgylcheddol neu fflagiau? Rydym yn cymryd popeth!

Gadewch i ni symud ymlaen i gyfluniad y prif weithredwr. Mae dau ddull sylfaenol o ffurfweddu cymwysiadau:

  1. defnyddio opsiynau llinell orchymyn;
  2. defnyddio newidynnau amgylchedd.

Mae opsiynau llinell orchymyn yn caniatΓ‘u ichi ddarllen gosodiadau'n fwy hyblyg, gyda chefnogaeth a dilysiad math o ddata. Mae gan lyfrgell safonol Python fodiwl argparser, y byddwn yn ei ddefnyddio. Mae manylion ac enghreifftiau o'i alluoedd ar gael yn dogfennaeth swyddogol.

Yn ein hachos ni, dyma sut olwg fyddai ar enghraifft o sefydlu fflagiau llinell orchymyn darllen:

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

Ar y llaw arall, gan ddefnyddio newidynnau amgylchedd yn Kubernetes, gallwch chi drosglwyddo gwybodaeth gwasanaeth yn hawdd am y pod y tu mewn i'r cynhwysydd. Er enghraifft, gallwn gael gwybodaeth am y gofod enw y mae'r pod yn rhedeg ynddo gyda'r lluniad canlynol:

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

Rhesymeg gweithredwr

Er mwyn deall sut i wahanu dulliau o weithio gyda ConfigMap a Secret, byddwn yn defnyddio mapiau arbennig. Yna gallwn ddeall pa ddulliau sydd eu hangen arnom i olrhain a chreu'r gwrthrych:

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

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

Nesaf, mae angen i chi dderbyn digwyddiadau gan y gweinydd API. Gadewch i ni ei weithredu fel a ganlyn:

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)

Ar Γ΄l derbyn y digwyddiad, symudwn ymlaen at y brif resymeg o'i brosesu:

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

Mae'r prif resymeg yn barod! Nawr mae angen i ni becynnu hyn i gyd mewn un pecyn Python. Rydym yn paratoi'r ffeil setup.py, ysgrifennwch feta gwybodaeth am y prosiect yno:

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: Mae gan y cleient kubernetes ar gyfer Python ei fersiwn ei hun. Gellir dod o hyd i ragor o wybodaeth am gydnawsedd rhwng fersiynau cleientiaid a fersiynau Kubernetes yn matricsau cydnawsedd.

Nawr mae ein prosiect yn edrych fel hyn:

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

Dociwr a Helm

Bydd y Dockerfile yn hynod o syml: cymerwch y ddelwedd python-alpaidd sylfaenol a gosodwch ein pecyn. Gadewch i ni ohirio ei optimeiddio tan amseroedd gwell:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Mae lleoli ar gyfer y gweithredwr hefyd yn syml iawn:

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

Yn olaf, mae angen i chi greu rΓ΄l briodol ar gyfer y gweithredwr gyda'r hawliau angenrheidiol:

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

Cyfanswm

Dyna sut, heb ofn, gwaradwydd, neu ddysgu Go, roeddem yn gallu adeiladu ein gweithredwr ein hunain ar gyfer Kubernetes yn Python. Wrth gwrs, mae ganddo le i dyfu o hyd: yn y dyfodol bydd yn gallu prosesu rheolau lluosog, gweithio mewn edafedd lluosog, monitro newidiadau yn ei CRDs yn annibynnol ...

Er mwyn rhoi golwg agosach i chi ar y cod, rydym wedi ei roi i mewn ystorfa gyhoeddus. Os ydych chi eisiau enghreifftiau o weithredwyr mwy difrifol yn cael eu gweithredu gan ddefnyddio Python, gallwch chi droi eich sylw at ddau weithredwr ar gyfer defnyddio mongodb (ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈ 2).

PS Ac os ydych chi'n rhy ddiog i ddelio Γ’ digwyddiadau Kubernetes neu os ydych chi'n fwy cyfarwydd Γ’ defnyddio Bash, mae ein cydweithwyr wedi paratoi datrysiad parod ar y ffurf cregyn-weithredwr (Ni cyhoeddi ei fod yn Ebrill).

Pps

Darllenwch hefyd ar ein blog:

Ffynhonnell: hab.com

Ychwanegu sylw