Kubernetes-operaattori Pythonissa ilman kehyksiä ja SDK:ta
Golla on tällä hetkellä monopoli ohjelmointikielissä, joita ihmiset valitsevat kirjoittaa lausuntoja Kubernetesille. Tähän on objektiivisia syitä, kuten:
Gossa on tehokas kehys operaattoreiden kehittämiseen - Operaattorin SDK.
Peliä muuttavat sovellukset, kuten Docker ja Kubernetes, on kirjoitettu Golla. Operaattorin kirjoittaminen Go-kieleen tarkoittaa, että puhut samaa kieltä ekosysteemin kanssa.
Go-sovellusten korkea suorituskyky ja yksinkertaiset työkalut samanaikaiseen työskentelyyn heti valmiina.
NB: Muuten, kuinka kirjoittaa oma lausunto Go, we -kirjassa jo kuvattu yhdessä ulkomaisten kirjailijoiden käännöksistämme.
Mutta entä jos ajan tai yksinkertaisesti sanottuna motivaation puute estää sinua oppimasta Go? Artikkelissa on esimerkki siitä, kuinka voit kirjoittaa hyvän lausunnon yhdellä suosituimmista kielistä, jonka melkein jokainen DevOps-insinööri osaa - Python.
Tapaa: Kopiokone - kopiooperaattori!
Harkitse esimerkiksi yksinkertaisen käskyn kehittämistä, joka on suunniteltu kopioimaan ConfigMap joko uuden nimitilan ilmestyessä tai kun toinen kahdesta entiteetistä muuttuu: ConfigMap ja Secret. Käytännön näkökulmasta operaattori voi olla hyödyllinen sovelluskokoonpanojen joukkopäivityksessä (päivittämällä ConfigMap) tai päivittäessä salaisia tietoja - esimerkiksi avaimia Docker-rekisterin kanssa työskentelyä varten (kun lisätään Secret nimiavaruuteen).
Operaattori voidaan konfiguroida. Tätä varten käytämme komentorivin lippuja ja ympäristömuuttujia.
Docker-säiliön ja Helm-kaavion rakenne on suunniteltu siten, että käyttäjät voivat helposti (kirjaimellisesti yhdellä komennolla) asentaa operaattorin Kubernetes-klusteriinsa.
CRD
Jotta operaattori tietää, mitä resursseja etsiä ja mistä etsiä, meidän on asetettava hänelle sääntö. Jokainen sääntö esitetään yhtenä CRD-objektina. Mitä kenttiä tässä CRD:ssä pitäisi olla?
Resurssin tyyppi, jota etsimme (ConfigMap tai Secret).
Luettelo nimiavaruuksista, johon resurssit tulisi sijoittaa.
Valitsin, jolla etsimme resursseja nimiavaruudesta.
Valmis! Nyt meidän täytyy jotenkin saada tietoa säännöstämme. Sallikaa minun tehdä varaus heti, ettemme kirjoita pyyntöjä klusterin API-palvelimelle itse. Tätä varten käytämme valmista Python-kirjastoa kubernetes-asiakas:
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')}
Tämän koodin suorittamisen seurauksena saamme seuraavan:
Hienoa: onnistuimme saamaan säännön operaattorille. Ja mikä tärkeintä, teimme niin sanotun Kubernetes-tavan.
Ympäristömuuttujat vai liput? Otamme kaiken!
Siirrytään pääkäyttäjän kokoonpanoon. Sovellusten määrittämiseen on kaksi perustapaa:
käytä komentorivin valintoja;
käyttää ympäristömuuttujia.
Komentorivivalintojen avulla voit lukea asetuksia joustavammin tietotyyppituen ja validoinnin avulla. Pythonin vakiokirjastossa on moduuli argparser, jota käytämme. Yksityiskohdat ja esimerkit sen ominaisuuksista ovat saatavilla osoitteessa virallinen dokumentaatio.
Meidän tapauksessamme esimerkki komentorivin lukemisen määrittämisestä näyttäisi tältä:
Toisaalta Kubernetesin ympäristömuuttujien avulla voit helposti siirtää palvelutietoja podista kontin sisällä. Voimme esimerkiksi saada tietoa nimiavaruudesta, jossa pod toimii seuraavalla rakenteella:
Ymmärtääksemme kuinka erottaa menetelmät ConfigMapin ja Secretin kanssa työskentelyssä, käytämme erityisiä karttoja. Sitten voimme ymmärtää, mitä menetelmiä tarvitsemme objektin seurantaan ja luomiseen:
Seuraavaksi sinun on vastaanotettava tapahtumat API-palvelimelta. Toteutetaan se seuraavasti:
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)
Tapahtuman saatuaan siirrymme sen käsittelyn päälogiikkaan:
# Типы событий, на которые будем реагировать
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)
Päälogiikka on valmis! Nyt meidän on pakattava kaikki tämä yhteen Python-pakettiin. Valmistelemme tiedoston setup.py, kirjoita sinne metatiedot projektista:
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: Pythonin kubernetes-asiakkaalla on oma versiointinsa. Lisätietoja asiakasversioiden ja Kubernetes-versioiden yhteensopivuudesta löytyy osoitteesta yhteensopivuusmatriisit.
Nyt projektimme näyttää tältä:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker ja Helm
Dockerfile on uskomattoman yksinkertainen: ota python-alpine-peruskuva ja asenna pakettimme. Siirretään sen optimointia parempiin aikoihin:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
Käyttöönotto operaattorille on myös hyvin yksinkertaista:
Näin ilman pelkoa, moitteita tai Go:n oppimista pystyimme rakentamaan oman operaattorimme Kubernetesille Pythonissa. Tietysti sillä on vielä tilaa kasvaa: tulevaisuudessa se pystyy käsittelemään useita sääntöjä, työskentelemään useissa säikeissä, seuraamaan itsenäisesti muutoksia CRD-dokumenteissaan...
Jotta voisit tarkastella koodia tarkemmin, olemme lisänneet sen julkinen arkisto. Jos haluat esimerkkejä vakavammista Pythonilla toteutetuista operaattoreista, voit kääntää huomiosi kahteen operaattoriin mongodb (первый и toinen).
PS Ja jos olet liian laiska käsittelemään Kubernetes-tapahtumia tai olet yksinkertaisesti tottunut käyttämään Bashia, kollegamme ovat laatineet valmiin ratkaisun muodossa kuorioperaattori (Me ilmoitti se huhtikuussa).