Operatore Kubernetes in Python senza framework e SDK
Go attualmente ha il monopolio sui linguaggi di programmazione che le persone scelgono per scrivere istruzioni per Kubernetes. Ci sono ragioni oggettive per questo, come ad esempio:
Esiste un potente framework per lo sviluppo di operatori in Go - SDK dell'operatore.
Le applicazioni rivoluzionarie come Docker e Kubernetes sono scritte in Go. Scrivere il tuo operatore in Go significa parlare la stessa lingua con l'ecosistema.
Elevate prestazioni delle applicazioni Go e strumenti semplici per lavorare con la concorrenza fuori dagli schemi.
NB: A proposito, come scrivere la tua dichiarazione in Go, we già descritto in una delle nostre traduzioni di autori stranieri.
Ma cosa succede se ti viene impedito di imparare il Go per mancanza di tempo o, in poche parole, di motivazione? L'articolo fornisce un esempio di come scrivere una buona dichiarazione utilizzando uno dei linguaggi più popolari che quasi tutti gli ingegneri DevOps conoscono: Python.
Incontra: Copier - operatore di copia!
Ad esempio, considera lo sviluppo di una semplice istruzione progettata per copiare una ConfigMap quando appare un nuovo spazio dei nomi o quando cambia una delle due entità: ConfigMap e Secret. Da un punto di vista pratico, l'operatore può essere utile per l'aggiornamento in blocco delle configurazioni dell'applicazione (aggiornando ConfigMap) o per l'aggiornamento dei dati segreti, ad esempio le chiavi per lavorare con il registro Docker (quando si aggiunge Secret allo spazio dei nomi).
L'operatore può essere configurato. Per fare ciò, utilizzeremo i flag della riga di comando e le variabili di ambiente.
La build del contenitore Docker e del grafico Helm è progettata in modo che gli utenti possano facilmente (letteralmente con un comando) installare l'operatore nel proprio cluster Kubernetes.
CRD
Affinché l'operatore sappia quali risorse cercare e dove cercare, dobbiamo impostargli una regola. Ciascuna regola verrà rappresentata come un singolo oggetto CRD. Quali campi dovrebbe avere questo CRD?
Tipo di risorsa, che cercheremo (ConfigMap o Secret).
Elenco degli spazi dei nomi, in cui dovrebbero essere collocate le risorse.
Selettore, mediante il quale cercheremo le risorse nello spazio dei nomi.
Pronto! Ora dobbiamo in qualche modo ottenere informazioni sulla nostra regola. Consentitemi subito di prenotare che non scriveremo noi stessi le richieste al server API del cluster. Per fare ciò, utilizzeremo una libreria Python già pronta client-kubernetes:
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')}
Come risultato dell'esecuzione di questo codice, otteniamo quanto segue:
Ottimo: siamo riusciti a ottenere una regola per l'operatore. E, cosa più importante, abbiamo adottato quello che viene chiamato il metodo Kubernetes.
Variabili o flag d'ambiente? Prendiamo tutto!
Passiamo alla configurazione principale dell'operatore. Esistono due approcci di base per la configurazione delle applicazioni:
utilizzare le opzioni della riga di comando;
utilizzare variabili d'ambiente.
Le opzioni della riga di comando consentono di leggere le impostazioni in modo più flessibile, con supporto e convalida del tipo di dati. La libreria standard di Python ha un modulo argparser, che utilizzeremo. Dettagli ed esempi delle sue capacità sono disponibili in documentazione ufficiale.
Nel nostro caso, ecco come apparirebbe un esempio di impostazione della lettura dei flag della riga di comando:
D'altra parte, utilizzando le variabili di ambiente in Kubernetes, puoi trasferire facilmente le informazioni di servizio sul pod all'interno del contenitore. Ad esempio, possiamo ottenere informazioni sullo spazio dei nomi in cui è in esecuzione il pod con la seguente costruzione:
Per capire come separare i metodi per lavorare con ConfigMap e Secret, utilizzeremo mappe speciali. Quindi possiamo capire di quali metodi abbiamo bisogno per tracciare e creare l'oggetto:
Successivamente, devi ricevere eventi dal server API. Implementiamolo come segue:
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)
Dopo aver ricevuto l'evento, si passa alla logica principale della sua elaborazione:
# Типы событий, на которые будем реагировать
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)
La logica principale è pronta! Ora dobbiamo racchiudere tutto questo in un unico pacchetto Python. Prepariamo il fascicolo setup.py, scrivi lì le meta informazioni sul progetto:
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: il client Kubernetes per Python ha il proprio controllo delle versioni. Ulteriori informazioni sulla compatibilità tra le versioni client e le versioni Kubernetes sono disponibili in matrici di compatibilità.
Ora il nostro progetto si presenta così:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker e timone
Il Dockerfile sarà incredibilmente semplice: prendi l'immagine base python-alpine e installa il nostro pacchetto. Rimandiamo la sua ottimizzazione a tempi migliori:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
Anche l'implementazione per l'operatore è molto semplice:
È così che, senza paura, rimprovero o apprendimento di Go, siamo stati in grado di creare il nostro operatore per Kubernetes in Python. Certo, ha ancora spazio per crescere: in futuro sarà in grado di elaborare più regole, lavorare su più thread, monitorare in modo indipendente i cambiamenti nei suoi CRD...
Per darti uno sguardo più da vicino al codice, lo abbiamo inserito deposito pubblico. Se desideri esempi di operatori più seri implementati utilizzando Python, puoi rivolgere la tua attenzione a due operatori per la distribuzione di mongodb (prima и secondo).
PS E se sei troppo pigro per gestire gli eventi Kubernetes o semplicemente sei più abituato a usare Bash, i nostri colleghi hanno preparato una soluzione già pronta sotto forma operatore di shell (Noi annunciato ad aprile).