Go má v súčasnosti monopol na programovacie jazyky, ktoré si ľudia vyberajú na písanie príkazov pre Kubernetes. Má to objektívne dôvody, ako napr.
V Go existuje výkonný rámec pre vývoj operátorov - SDK operátora.
Aplikácie, ktoré menia hru, ako sú Docker a Kubernetes, sú napísané v Go. Napísať svojho operátora v Go znamená hovoriť rovnakým jazykom s ekosystémom.
Vysoký výkon Go aplikácií a jednoduché nástroje na prácu so súbežnosťou hneď po vybalení.
NB: Mimochodom, ako napísať svoje vlastné vyhlásenie v Go, my už popísané v jednom z našich prekladov zahraničných autorov.
Čo ak vám však v učení Go bráni nedostatok času alebo jednoducho povedané motivácia? Článok poskytuje príklad, ako môžete napísať dobré vyhlásenie pomocou jedného z najpopulárnejších jazykov, ktorý pozná takmer každý inžinier DevOps - Pytón.
Zoznámte sa: Kopírka - operátor kopírovania!
Ako príklad zvážte vytvorenie jednoduchého príkazu určeného na skopírovanie mapy ConfigMap buď vtedy, keď sa objaví nový priestor názvov, alebo keď sa zmení jedna z dvoch entít: ConfigMap a Secret. Z praktického hľadiska môže byť operátor užitočný pri hromadnej aktualizácii konfigurácií aplikácií (aktualizáciou ConfigMap) alebo pri aktualizácii tajných údajov – napríklad kľúčov pre prácu s Docker Registry (pri pridávaní Secret do menného priestoru).
Operátor je možné konfigurovať. Na tento účel použijeme príznaky príkazového riadku a premenné prostredia.
Zostavenie kontajnera Docker a grafu Helm je navrhnuté tak, aby používatelia mohli jednoducho (doslova jedným príkazom) nainštalovať operátora do svojho klastra Kubernetes.
CRD
Aby operátor vedel, aké zdroje má hľadať a kde hľadať, musíme mu nastaviť pravidlo. Každé pravidlo bude reprezentované ako jeden objekt CRD. Aké polia by malo obsahovať toto CRD?
Typ zdroja, ktorý budeme hľadať (ConfigMap alebo Secret).
Zoznam menných priestorov, v ktorej by sa mali zdroje nachádzať.
selektor, pomocou ktorého budeme hľadať zdroje v mennom priestore.
Pripravený! Teraz musíme nejakým spôsobom získať informácie o našom pravidle. Dovoľte mi hneď urobiť rezerváciu, že nebudeme sami zapisovať požiadavky na server klastra API. Na to nám poslúži hotová knižnica Python kubernetes-klient:
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')}
V dôsledku spustenia tohto kódu dostaneme nasledovné:
Skvelé: podarilo sa nám získať pravidlo pre operátora. A čo je najdôležitejšie, urobili sme to, čomu sa hovorí Kubernetesova cesta.
Premenné prostredia alebo príznaky? Berieme všetko!
Prejdime ku konfigurácii hlavného operátora. Existujú dva základné prístupy ku konfigurácii aplikácií:
použite možnosti príkazového riadku;
používať premenné prostredia.
Možnosti príkazového riadka vám umožňujú flexibilnejšie čítať nastavenia s podporou a overovaním typov údajov. Štandardná knižnica Pythonu má modul argparser, ktorý budeme používať. Podrobnosti a príklady jeho schopností sú k dispozícii v oficiálna dokumentácia.
V našom prípade by takto vyzeral príklad nastavenia čítania príznakov príkazového riadku:
Na druhej strane, pomocou premenných prostredia v Kubernetes môžete ľahko prenášať servisné informácie o podu vnútri kontajnera. Napríklad môžeme získať informácie o mennom priestore, v ktorom modul beží, pomocou nasledujúcej konštrukcie:
Aby sme pochopili, ako oddeliť metódy práce s ConfigMap a Secret, použijeme špeciálne mapy. Potom môžeme pochopiť, aké metódy potrebujeme na sledovanie a vytváranie objektu:
Ďalej musíte prijímať udalosti zo servera API. Implementujme to nasledovne:
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)
Po prijatí udalosti prejdeme k hlavnej logike jej spracovania:
# Типы событий, на которые будем реагировать
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)
Hlavná logika je pripravená! Teraz to všetko musíme zabaliť do jedného balíka Python. Pripravíme súbor setup.py, napíšte tam meta informácie o projekte:
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: Klient kubernetes pre Python má svoje vlastné verzovanie. Viac informácií o kompatibilite medzi klientskymi verziami a verziami Kubernetes nájdete v matice kompatibility.
Teraz náš projekt vyzerá takto:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker a Helm
Dockerfile bude neuveriteľne jednoduchý: vezmite základný obraz python-alpine a nainštalujte náš balík. Odložme jeho optimalizáciu na lepšie časy:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
Takto sme si bez strachu, výčitiek alebo učenia sa Go dokázali postaviť vlastného operátora pre Kubernetes v Pythone. Samozrejme, stále má priestor na rast: v budúcnosti bude schopný spracovávať viacero pravidiel, pracovať vo viacerých vláknach, nezávisle sledovať zmeny vo svojich CRD...
Aby sme vám poskytli bližší pohľad na kód, vložili sme ho verejné úložisko. Ak chcete príklady serióznejších operátorov implementovaných pomocou Pythonu, môžete obrátiť svoju pozornosť na dva operátory na nasadenie mongodb (первый и druhý).
PS A ak ste príliš leniví na to, aby ste riešili udalosti Kubernetes alebo ste jednoducho viac zvyknutí používať Bash, naši kolegovia pripravili hotové riešenie vo forme shell-operátor (My oznámil to v apríli).