Go har i øjeblikket monopol på de programmeringssprog, folk vælger at skrive udsagn til Kubernetes. Det er der objektive grunde til, såsom:
Der er en kraftfuld ramme for udvikling af operatører i Go - Operatør SDK.
Spilskiftende applikationer som Docker og Kubernetes er skrevet i Go. At skrive din operatør i Go betyder, at du taler det samme sprog med økosystemet.
Høj ydeevne af Go-applikationer og enkle værktøjer til at arbejde med samtidighed ud af boksen.
NB: Forresten, hvordan du skriver din egen erklæring i Go, vi allerede beskrevet i en af vores oversættelser af udenlandske forfattere.
Men hvad nu, hvis du bliver forhindret i at lære Go af mangel på tid eller, kort sagt, motivation? Artiklen giver et eksempel på, hvordan du kan skrive en god erklæring ved hjælp af et af de mest populære sprog, som næsten alle DevOps-ingeniører kender - Python.
Mød: Kopimaskine - kopioperatør!
Som et eksempel kan du overveje at udvikle en simpel sætning designet til at kopiere et ConfigMap, enten når et nyt navneområde vises, eller når en af to entiteter ændres: ConfigMap og Secret. Fra et praktisk synspunkt kan operatøren være nyttig til masseopdatering af applikationskonfigurationer (ved at opdatere ConfigMap) eller til opdatering af hemmelige data - for eksempel nøgler til at arbejde med Docker Registry (når du tilføjer Secret til navneområdet).
Operatøren kan konfigureres. For at gøre dette bruger vi kommandolinjeflag og miljøvariabler.
Opbygningen af Docker-containeren og Helm-diagrammet er designet, så brugerne nemt (bogstaveligt talt med én kommando) kan installere operatøren i deres Kubernetes-klynge.
CRD
For at operatøren skal vide, hvilke ressourcer han skal kigge efter, og hvor han skal lede, er vi nødt til at sætte en regel for ham. Hver regel vil blive repræsenteret som et enkelt CRD-objekt. Hvilke felter skal denne CRD have?
Ressourcetype, som vi vil lede efter (ConfigMap eller Secret).
Liste over navnerum, hvori ressourcerne skal placeres.
Selector, hvorved vi vil søge efter ressourcer i navnerummet.
Parat! Nu skal vi på en eller anden måde få information om vores regel. Lad mig tage forbehold med det samme, at vi ikke selv vil skrive anmodninger til cluster API Server. For at gøre dette vil vi bruge et færdiglavet 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 et resultat af at køre denne kode får vi følgende:
Fantastisk: det lykkedes os at få en regel for operatøren. Og vigtigst af alt, vi gjorde det, der kaldes Kubernetes-måden.
Miljøvariabler eller flag? Vi tager alt!
Lad os gå videre til hovedoperatørkonfigurationen. Der er to grundlæggende tilgange til konfiguration af applikationer:
brug kommandolinjeindstillinger;
bruge miljøvariabler.
Kommandolinjeindstillinger giver dig mulighed for at læse indstillinger mere fleksibelt med datatypeunderstøttelse og validering. Pythons standardbibliotek har et modul argparser, som vi vil bruge. Detaljer og eksempler på dets muligheder er tilgængelige i officiel dokumentation.
I vores tilfælde er dette, hvordan et eksempel på opsætning af læsekommandolinjeflag ville se ud:
På den anden side kan du ved hjælp af miljøvariabler i Kubernetes nemt overføre serviceoplysninger om poden inde i containeren. For eksempel kan vi få information om det navneområde, som poden kører i, med følgende konstruktion:
For at forstå, hvordan man adskiller metoder til at arbejde med ConfigMap og Secret, vil vi bruge specielle kort. Så kan vi forstå, hvilke metoder vi har brug for til at spore og skabe objektet:
Dernæst skal du modtage hændelser fra API-serveren. Lad os implementere det som følger:
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 at have modtaget begivenheden, går vi videre til hovedlogikken i at behandle 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)
Hovedlogikken er klar! Nu skal vi pakke alt dette ind i én Python-pakke. Vi forbereder filen setup.py, skriv metainformation om projektet der:
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 til Python har sin egen versionering. Flere oplysninger om kompatibilitet mellem klientversioner og Kubernetes-versioner kan findes i kompatibilitetsmatricer.
Nu ser vores projekt således ud:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
Docker og Helm
Dockerfilen vil være utrolig enkel: Tag det grundlæggende python-alpine billede og installer vores pakke. Lad os udsætte dens optimering til bedre tider:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
Det var sådan, vi uden frygt, bebrejdelser eller lære Go, var i stand til at bygge vores egen operatør til Kubernetes i Python. Selvfølgelig har det stadig plads til at vokse: i fremtiden vil det være i stand til at behandle flere regler, arbejde i flere tråde, uafhængigt overvåge ændringer i dets CRD'er...
For at give dig et nærmere kig på koden, har vi lagt den ind offentligt depot. Hvis du vil have eksempler på mere seriøse operatører implementeret ved hjælp af Python, kan du rette opmærksomheden mod to operatører til implementering af mongodb (первый и sekund).
PS Og hvis du er for doven til at beskæftige dig med Kubernetes-begivenheder, eller du simpelthen er mere vant til at bruge Bash, har vores kollegaer udarbejdet en færdig løsning i formularen shell-operatør (Vi annonceret det i april).