Kubernetes Operator yn Python sûnder kaders en SDK
Go hat op it stuit in monopoalje op 'e programmeartalen dy't minsken kieze om ferklearrings foar Kubernetes te skriuwen. Dêr binne objektive redenen foar, lykas:
D'r is in krêftich ramt foar it ûntwikkeljen fan operators yn Go - Operator SDK.
Game-feroarjende applikaasjes lykas Docker en Kubernetes binne skreaun yn Go. It skriuwen fan jo operator yn Go betsjut deselde taal prate mei it ekosysteem.
Hege prestaasjes fan Go-applikaasjes en ienfâldige ark foar it wurkjen mei tagelyk út 'e doaze.
NB: Trouwens, hoe skriuwe jo eigen ferklearring yn Go, wy al beskreaun yn ien fan ús oersettingen fan bûtenlânske skriuwers.
Mar wat as jo foarkomme om Go te learen troch gebrek oan tiid of, gewoanwei, motivaasje? It artikel jout in foarbyld fan hoe't jo in goede ferklearring kinne skriuwe mei ien fan 'e populêrste talen dy't hast elke DevOps-yngenieur wit - Python.
Meet: Copier - kopyoperator!
As foarbyld, beskôgje it ûntwikkeljen fan in ienfâldige ferklearring ûntworpen om in ConfigMap te kopiearjen as in nije nammeromte ferskynt of as ien fan twa entiteiten feroaret: ConfigMap en Secret. Ut in praktysk eachpunt kin de operator nuttich wêze foar bulk bywurkjen fan applikaasjekonfiguraasjes (troch it bywurkjen fan de ConfigMap) of foar it bywurkjen fan geheime gegevens - bygelyks kaaien foar wurkjen mei de Docker Registry (by it tafoegjen fan Geheim oan 'e nammeromte).
De operator kin ynsteld wurde. Om dit te dwaan, sille wy kommandorigelflaggen en omjouwingsfariabelen brûke.
De bou fan 'e Docker-kontener en Helm-kaart is ûntwurpen sadat brûkers de operator maklik (letterlik mei ien kommando) kinne ynstallearje yn har Kubernetes-kluster.
CRD
Om de operator te witten hokker middels om te sykjen en wêr te sykjen, moatte wy in regel foar him ynstelle. Elke regel sil wurde fertsjintwurdige as ien CRD-objekt. Hokker fjilden moat dizze CRD hawwe?
Soart boarne, dêr't wy nei sille sykje (ConfigMap of Secret).
List fan nammeromten, dêr't de middels yn sitte moatte.
Selektor, wêrmei wy sille sykje nei boarnen yn de nammeromte.
Klear! No moatte wy op ien of oare manier ynformaasje krije oer ús regel. Lit my direkt in reservearje meitsje dat wy sels gjin fersiken skriuwe nei de kluster API-tsjinner. Om dit te dwaan, sille wy in klearmakke Python-bibleteek brûke kubernetes-client:
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')}
As gefolch fan it útfieren fan dizze koade krije wy it folgjende:
Geweldich: it is ús slagge om in regel te krijen foar de operator. En it wichtichste, wy diene wat de Kubernetes-manier hjit.
Miljeu fariabelen of flaggen? Wy nimme alles!
Litte wy trochgean nei de konfiguraasje fan 'e haadoperator. D'r binne twa basisbenaderingen foar it konfigurearjen fan applikaasjes:
gebrûk kommandorigelopsjes;
brûke omjouwingsfariabelen.
Opsjes foar kommandorigel kinne jo ynstellings fleksibeler lêze, mei stipe en falidaasje fan gegevenstype. De standertbibleteek fan Python hat in module argparser, dy't wy sille brûke. Details en foarbylden fan syn mooglikheden binne beskikber yn offisjele dokumintaasje.
Foar ús gefal, dit is wat in foarbyld fan it ynstellen fan it lêzen fan kommandorigelflaggen der útsjen soe:
Oan 'e oare kant, mei help fan omjouwingsfariabelen yn Kubernetes, kinne jo maklik tsjinstynformaasje oerbringe oer de pod yn' e kontener. Wy kinne bygelyks ynformaasje krije oer de nammeromte dêr't de pod yn rint mei de folgjende konstruksje:
Om te begripen hoe't jo metoaden skiede kinne foar wurkjen mei ConfigMap en Secret, sille wy spesjale kaarten brûke. Dan kinne wy begripe hokker metoaden wy moatte folgje en meitsje it objekt:
Dêrnei moatte jo eveneminten ûntfange fan 'e API-tsjinner. Litte wy it as folgjend útfiere:
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)
Nei ûntfangst fan it evenemint geane wy troch nei de haadlogika fan it ferwurkjen:
# Типы событий, на которые будем реагировать
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)
De wichtichste logika is klear! No moatte wy dit alles yn ien Python-pakket ferpakke. Wy meitsje it bestân klear setup.py, skriuw dêr meta-ynformaasje oer it projekt:
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: De kubernetes-kliïnt foar Python hat in eigen ferzje. Mear ynformaasje oer kompatibiliteit tusken clientferzjes en Kubernetes-ferzjes is te finen yn komptabiliteit matriks.
No sjocht ús projekt der sa út:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker en Helm
De Dockerfile sil ongelooflijk ienfâldich wêze: nim de basis python-alpine-ôfbylding en ynstallearje ús pakket. Litte wy syn optimalisaasje útstelle oant bettere tiden:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
Dat is hoe't wy, sûnder eangst, smaad of Go learen, ús eigen operator foar Kubernetes yn Python bouwe kinnen. Fansels hat it noch romte om te groeien: yn 'e takomst sil it meardere regels kinne ferwurkje, wurkje yn meardere diskusjes, ûnôfhinklik kontrolearje fan feroaringen yn syn CRD's ...
Om jo de koade fan tichterby te sjen, hawwe wy it ynset iepenbiere repository. As jo foarbylden wolle fan mear serieuze operators ymplementearre mei Python, kinne jo jo oandacht rjochtsje op twa operators foar it ynsetten fan mongodb (первый и de twadde).
PS En as jo te lui binne om te gean mei Kubernetes-eveneminten of as jo gewoan mear wend binne om Bash te brûken, hawwe ús kollega's in klearmakke oplossing taret yn 'e foarm shell-operator (Wy oankundige it yn april).