Go har för närvarande monopol på de programmeringsspråk som folk väljer att skriva uttalanden för Kubernetes. Det finns objektiva skäl till detta, såsom:
Det finns ett kraftfullt ramverk för att utveckla operatörer i Go - Operatörs-SDK.
Spelförändrande applikationer som Docker och Kubernetes är skrivna i Go. Att skriva din operatör i Go innebär att du talar samma språk som ekosystemet.
Hög prestanda för Go-applikationer och enkla verktyg för att arbeta med samtidighet direkt.
NB: Förresten, hur man skriver ett eget uttalande i Go, vi redan beskrivits i en av våra översättningar av utländska författare.
Men vad händer om du hindras från att lära dig Go på grund av tidsbrist eller, enkelt uttryckt, motivation? Artikeln ger ett exempel på hur du kan skriva ett bra uttalande med ett av de mest populära språken som nästan alla DevOps-ingenjörer kan - Python.
Möt: Kopiator - kopieringsoperatör!
Som ett exempel, överväg att utveckla en enkel sats som är utformad för att kopiera en ConfigMap antingen när ett nytt namnområde dyker upp eller när en av två entiteter ändras: ConfigMap och Secret. Ur praktisk synvinkel kan operatören vara användbar för massuppdatering av applikationskonfigurationer (genom att uppdatera ConfigMap) eller för att uppdatera hemlig data - till exempel nycklar för att arbeta med Docker Registry (när du lägger till Secret till namnområdet).
Operatören kan konfigureras. För att göra detta kommer vi att använda kommandoradsflaggor och miljövariabler.
Bygget av Docker-behållaren och rordiagrammet är designat så att användare enkelt (bokstavligen med ett kommando) kan installera operatören i sitt Kubernetes-kluster.
CRD
För att operatören ska veta vilka resurser han ska leta efter och var han ska leta måste vi sätta en regel för honom. Varje regel kommer att representeras som ett enda CRD-objekt. Vilka fält ska denna CRD ha?
Resurstyp, som vi kommer att leta efter (ConfigMap eller Secret).
Lista över namnutrymmen, där resurserna bör finnas.
Selector, med vilken vi kommer att söka efter resurser i namnområdet.
Redo! Nu måste vi på något sätt få information om vår regel. Låt mig göra en reservation omedelbart att vi inte kommer att skriva förfrågningar till kluster API Server själva. För att göra detta kommer vi att använda ett färdigt Python-bibliotek 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')}
Som ett resultat av att köra den här koden får vi följande:
Bra: vi lyckades få en regel för operatören. Och viktigast av allt, vi gjorde det som kallas Kubernetes-sättet.
Miljövariabler eller flaggor? Vi tar allt!
Låt oss gå vidare till huvudoperatörskonfigurationen. Det finns två grundläggande metoder för att konfigurera applikationer:
använd kommandoradsalternativ;
använda miljövariabler.
Kommandoradsalternativ låter dig läsa inställningar mer flexibelt, med datatypsstöd och validering. Pythons standardbibliotek har en modul argparser, som vi kommer att använda. Detaljer och exempel på dess kapacitet finns i officiell dokumentation.
För vårt fall är det så här ett exempel på att ställa in läskommandoradsflaggor skulle se ut:
Å andra sidan, med hjälp av miljövariabler i Kubernetes, kan du enkelt överföra tjänstinformation om podden inuti behållaren. Till exempel kan vi få information om namnutrymmet där podden körs med följande konstruktion:
För att förstå hur man separerar metoder för att arbeta med ConfigMap och Secret kommer vi att använda speciella kartor. Då kan vi förstå vilka metoder vi behöver för att spåra och skapa objektet:
Därefter måste du ta emot händelser från API-servern. Låt oss implementera det enligt följande:
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)
Efter att ha tagit emot händelsen går vi vidare till huvudlogiken för att bearbeta den:
# Типы событий, на которые будем реагировать
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)
Huvudlogiken är klar! Nu måste vi paketera allt detta i ett Python-paket. Vi förbereder filen setup.py, skriv metainformation om projektet där:
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-klienten för Python har sin egen versionshantering. Mer information om kompatibilitet mellan klientversioner och Kubernetes-versioner finns i kompatibilitetsmatriser.
Nu ser vårt projekt ut så här:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker och Helm
Dockerfilen kommer att vara otroligt enkel: ta basen python-alpine-bilden och installera vårt paket. Låt oss skjuta upp optimeringen till bättre tider:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
Installationen för operatören är också mycket enkel:
Det var så vi, utan rädsla, förebråelser eller lära oss Go, kunde bygga vår egen operatör för Kubernetes i Python. Naturligtvis har det fortfarande utrymme att växa: i framtiden kommer det att kunna bearbeta flera regler, arbeta i flera trådar, självständigt övervaka ändringar i sina CRD:er...
För att ge dig en närmare titt på koden har vi lagt in den offentligt förvar. Om du vill ha exempel på mer seriösa operatörer implementerade med Python, kan du rikta uppmärksamheten mot två operatörer för att distribuera mongodb (первый и 2.).
PS Och om du är för lat för att ta itu med Kubernetes-evenemang eller om du helt enkelt är mer van vid att använda Bash, har våra kollegor förberett en färdig lösning i formen skal-operatör (Vi meddelat det i april).