Go trenutno ima monopol na programske jezike koje ljudi biraju da pišu izjave za Kubernetes. Za to postoje objektivni razlozi, kao što su:
Postoji moćan okvir za razvoj operatera u Go - Operator SDK.
Aplikacije koje mijenjaju igre kao što su Docker i Kubernetes napisane su u Go. Pisati svog operatera u Go znači govoriti istim jezikom sa ekosistemom.
Visoke performanse Go aplikacija i jednostavni alati za rad sa konkurentnošću iz kutije.
NB: Usput, kako napisati svoju izjavu u Go, mi već opisano u jednom od naših prijevoda stranih autora.
Ali šta ako vas u učenju Go spriječi nedostatak vremena ili, jednostavno rečeno, motivacija? Članak daje primjer kako možete napisati dobru izjavu koristeći jedan od najpopularnijih jezika koje gotovo svaki DevOps inženjer zna - piton.
Upoznajte: Kopir aparat - fotokopir operater!
Kao primjer, razmislite o razvoju jednostavne izjave dizajnirane da kopira ConfigMap bilo kada se pojavi novi prostor imena ili kada se promijeni jedan od dva entiteta: ConfigMap i Secret. Sa praktične tačke gledišta, operator može biti koristan za masovno ažuriranje konfiguracija aplikacije (ažuriranjem ConfigMap-a) ili za ažuriranje tajnih podataka - na primjer, ključeva za rad sa Docker Registry-om (prilikom dodavanja Secret u imenski prostor).
Operater se može konfigurisati. Da bismo to uradili, koristićemo zastavice komandne linije i varijable okruženja.
Izrada Docker kontejnera i Helm grafikona je dizajnirana tako da korisnici mogu lako (bukvalno jednom komandom) da instaliraju operatera u svoj Kubernetes klaster.
CRD
Da bi operater znao koje resurse treba tražiti i gdje tražiti, moramo mu postaviti pravilo. Svako pravilo će biti predstavljeno kao jedan CRD objekat. Koja polja treba da ima ovaj CRD?
Vrsta resursa, koju ćemo tražiti (ConfigMap ili Secret).
Lista imenskih prostora, u kojoj bi se resursi trebali nalaziti.
Selektor, pomoću koje ćemo tražiti resurse u imenskom prostoru.
Spremni! Sada moramo nekako doći do informacija o našem pravilu. Odmah da rezervišem da nećemo sami pisati zahteve API serveru klastera. Da bismo to učinili, koristit ćemo gotovu Python biblioteku kubernetes-klijent:
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')}
Kao rezultat pokretanja ovog koda, dobijamo sljedeće:
Odlično: uspjeli smo dobiti pravilo za operatera. I što je najvažnije, uradili smo ono što se zove Kubernetes način.
Varijable okruženja ili zastavice? Uzimamo sve!
Prijeđimo na glavnu konfiguraciju operatera. Postoje dva osnovna pristupa konfigurisanju aplikacija:
koristiti opcije komandne linije;
koristiti varijable okruženja.
Opcije komandne linije omogućavaju vam da fleksibilnije čitate postavke, uz podršku za tip podataka i validaciju. Pythonova standardna biblioteka ima modul argparser, koji ćemo koristiti. Detalji i primjeri njegovih mogućnosti dostupni su u službena dokumentacija.
U našem slučaju, ovako bi izgledao primjer postavljanja zastavica komandne linije za čitanje:
S druge strane, koristeći varijable okruženja u Kubernetesu, možete lako prenijeti servisne informacije o podu unutar kontejnera. Na primjer, možemo dobiti informacije o imenskom prostoru u kojem se pod izvodi sa sljedećom konstrukcijom:
Da bismo razumjeli kako razdvojiti metode za rad sa ConfigMap i Secret, koristit ćemo posebne mape. Tada možemo razumjeti koje metode su nam potrebne za praćenje i kreiranje objekta:
Zatim morate primati događaje sa API servera. Hajde da ga implementiramo na sledeći način:
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)
Nakon što primimo događaj, prelazimo na glavnu logiku njegove obrade:
# Типы событий, на которые будем реагировать
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)
Glavna logika je spremna! Sada sve ovo trebamo spakovati u jedan Python paket. Pripremamo fajl setup.py, tamo napišite meta informacije o projektu:
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: Kubernetes klijent za Python ima svoje verzije. Više informacija o kompatibilnosti između verzija klijenta i verzija Kubernetesa možete pronaći u matrice kompatibilnosti.
Sada naš projekat izgleda ovako:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker i Helm
Dockerfile će biti nevjerovatno jednostavan: uzmite osnovnu python-alpine sliku i instalirajte naš paket. Odložimo njegovu optimizaciju do boljih vremena:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
Postavljanje za operatera je također vrlo jednostavno:
Tako smo, bez straha, prijekora ili učenja Go-a, uspjeli da napravimo vlastiti operator za Kubernetes u Pythonu. Naravno, još uvijek ima prostora za rast: u budućnosti će moći obraditi više pravila, raditi u više niti, nezavisno pratiti promjene u svojim CRD-ovima...
Da bismo vam bolje pogledali kod, stavili smo ga javno spremište. Ako želite primjere ozbiljnijih operatera implementiranih pomoću Pythona, možete skrenuti pažnju na dva operatora za implementaciju mongodb (первый и drugi).
PS A ako ste previše lijeni da se bavite Kubernetes događajima ili ste jednostavno više navikli koristiti Bash, naše kolege su pripremile gotovo rješenje u obliku shell-operator (Mi najavljeno u aprilu).