Kubernetes Operator Pythonban keretrendszerek és SDK nélkül
A Go jelenleg monopóliummal rendelkezik azokon a programozási nyelveken, amelyeket az emberek a Kubernetes számára írnak. Ennek objektív okai vannak, például:
Van egy hatékony keretrendszer az operátorok fejlesztésére a Go-ban - Operator SDK.
A játékot megváltoztató alkalmazások, például a Docker és a Kubernetes Go-ban vannak írva. Ha beírja az operátort a Go-ba, azt jelenti, hogy ugyanazt a nyelvet beszéli az ökoszisztémával.
Nagy teljesítményű Go-alkalmazások és egyszerű eszközök a párhuzamos munkavégzéshez.
NB: Apropó, hogyan írja meg saját nyilatkozatát a Go, mi már leírták külföldi szerzők egyik fordításában.
De mi van akkor, ha időhiány vagy egyszerűen csak a motiváció akadályoz meg a Go tanulásban? A cikk példát mutat arra, hogyan írhat jó nyilatkozatot az egyik legnépszerűbb nyelven, amelyet szinte minden DevOps mérnök ismer - Piton.
Ismerje meg: Másoló - másolókezelő!
Példaként fontolja meg egy egyszerű utasítás kidolgozását, amely a ConfigMap másolására szolgál új névtér megjelenése vagy két entitás változása esetén: ConfigMap és Secret. Gyakorlati szempontból az operátor hasznos lehet az alkalmazáskonfigurációk tömeges frissítéséhez (a ConfigMap frissítésével), vagy a titkos adatok frissítéséhez - például a Docker Registry-vel való együttműködéshez szükséges kulcsok frissítéséhez (amikor a Titkot hozzáadja a névtérhez).
Az operátor konfigurálható. Ehhez parancssori zászlókat és környezeti változókat fogunk használni.
A Docker-tároló és Helm-diagram felépítését úgy tervezték, hogy a felhasználók könnyen (szó szerint egyetlen paranccsal) telepíthessék az operátort a Kubernetes-fürtjükbe.
CRD
Ahhoz, hogy az üzemeltető tudja, milyen erőforrásokat és hol keressen, egy szabályt kell felállítanunk számára. Minden szabály egyetlen CRD objektumként jelenik meg. Milyen mezőket kell tartalmaznia ennek a CRD-nek?
Erőforrás típusa, amit keresni fogunk (ConfigMap vagy Secret).
Névterek listája, amelyben az erőforrásokat kell elhelyezni.
Választó, amellyel erőforrásokat fogunk keresni a névtérben.
Kész! Most valahogy információt kell szereznünk a szabályunkról. Azonnal le kell foglalnom, hogy mi magunk nem írunk kéréseket a fürt API-kiszolgálóra. Ehhez egy kész Python könyvtárat fogunk használni kubernetes-kliens:
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')}
A kód futtatásának eredményeként a következőket kapjuk:
Remek: sikerült szabályt szereznünk az üzemeltető számára. És ami a legfontosabb, megcsináltuk az úgynevezett Kubernetes-módot.
Környezeti változók vagy zászlók? Mindent viszünk!
Térjünk át a fő kezelői konfigurációra. Két alapvető megközelítés létezik az alkalmazások konfigurálására:
parancssori opciók használata;
környezeti változókat használjon.
A parancssori opciók lehetővé teszik a beállítások rugalmasabb olvasását, adattípus-támogatással és érvényesítéssel. A Python szabványos könyvtárának van egy modulja argparser, amit használni fogunk. Részletek és példák a képességeiről itt találhatók hivatalos dokumentáció.
A mi esetünkben így nézne ki egy példa a parancssori olvasási jelzők beállítására:
Másrészt a Kubernetes környezeti változóinak használatával könnyen átviheti a tárolón belüli podról szóló szolgáltatási információkat. Például a következő felépítéssel kaphatunk információkat arról a névtérről, amelyben a pod fut:
Speciális térképeket fogunk használni, hogy megértsük, hogyan lehet szétválasztani a ConfigMap és a titkos módszereket. Ezután megérthetjük, hogy milyen módszerekre van szükségünk az objektum nyomon követéséhez és létrehozásához:
Ezután eseményeket kell fogadnia az API-kiszolgálótól. Valósítsuk meg a következőképpen:
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)
Az esemény kézhezvétele után áttérünk a feldolgozás fő logikájára:
# Типы событий, на которые будем реагировать
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)
A fő logika készen áll! Most mindezt egyetlen Python-csomagba kell csomagolnunk. Előkészítjük a fájlt setup.py, írj oda metainformációkat a projektről:
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: A Pythonhoz készült kubernetes kliens saját verziószámmal rendelkezik. A kliensverziók és a Kubernetes-verziók közötti kompatibilitásról további információ a következő helyen található: kompatibilitási mátrixok.
A projektünk most így néz ki:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker és Helm
A Dockerfile hihetetlenül egyszerű lesz: vegyük a python-alpine alapképet, és telepítsük a csomagunkat. Halasszuk el az optimalizálását jobb időkre:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
A telepítés az üzemeltető számára is nagyon egyszerű:
Így félelem, szemrehányás és Go tanulás nélkül meg tudtuk építeni saját operátorunkat a Kubernetes számára Pythonban. Természetesen van még hova fejlődnie: a jövőben több szabályt is képes lesz feldolgozni, több szálban dolgozni, önállóan figyelni a CRD-i változásait...
Azért, hogy közelebbről is megtekinthesse a kódot, behelyeztük nyilvános adattár. Ha komolyabb, Python használatával megvalósított operátorokra szeretne példákat, akkor a mongodb (первый и második).
PS Ha pedig túl lusta a Kubernetes eseményekkel foglalkozni, vagy egyszerűen csak jobban hozzászokott a Bash használatához, kollégáink kész megoldást készítettek a formában shell-operátor (Mi bejelentett áprilisban).