
Go má v súčasnosti monopol na programovacie jazyky, ktoré si ľudia vyberajú na písanie príkazov pre Kubernetes. Má to objektívne dôvody, ako napr.
- V Go existuje výkonný rámec pre vývoj operátorov - .
- Aplikácie, ktoré menia hru, ako sú Docker a Kubernetes, sú napísané v Go. Napísať svojho operátora v Go znamená hovoriť rovnakým jazykom s ekosystémom.
- Vysoký výkon Go aplikácií a jednoduché nástroje na prácu so súbežnosťou hneď po vybalení.
NB: Mimochodom, ako napísať svoje vlastné vyhlásenie v Go, my v jednom z našich prekladov zahraničných autorov.
Čo ak vám však v učení Go bráni nedostatok času alebo jednoducho povedané motivácia? Článok poskytuje príklad, ako môžete napísať dobré vyhlásenie pomocou jedného z najpopulárnejších jazykov, ktorý pozná takmer každý inžinier DevOps - Pytón.
Zoznámte sa: Kopírka - operátor kopírovania!
Ako príklad zvážte vytvorenie jednoduchého príkazu určeného na skopírovanie mapy ConfigMap buď vtedy, keď sa objaví nový priestor názvov, alebo keď sa zmení jedna z dvoch entít: ConfigMap a Secret. Z praktického hľadiska môže byť operátor užitočný pri hromadnej aktualizácii konfigurácií aplikácií (aktualizáciou ConfigMap) alebo pri aktualizácii tajných údajov – napríklad kľúčov pre prácu s Docker Registry (pri pridávaní Secret do menného priestoru).
Takže, čo by mal mať dobrý operátor:
- Interakcia s operátorom sa vykonáva pomocou (ďalej len CRD).
- Operátor je možné konfigurovať. Na tento účel použijeme príznaky príkazového riadku a premenné prostredia.
- Zostavenie kontajnera Docker a grafu Helm je navrhnuté tak, aby používatelia mohli jednoducho (doslova jedným príkazom) nainštalovať operátora do svojho klastra Kubernetes.
CRD
Aby operátor vedel, aké zdroje má hľadať a kde hľadať, musíme mu nastaviť pravidlo. Každé pravidlo bude reprezentované ako jeden objekt CRD. Aké polia by malo obsahovať toto CRD?
- Typ zdroja, ktorý budeme hľadať (ConfigMap alebo Secret).
- Zoznam menných priestorov, v ktorej by sa mali zdroje nachádzať.
- selektor, pomocou ktorého budeme hľadať zdroje v mennom priestore.
Opíšme CRD:
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: copyrator.flant.com
spec:
group: flant.com
versions:
- name: v1
served: true
storage: true
scope: Namespaced
names:
plural: copyrators
singular: copyrator
kind: CopyratorRule
shortNames:
- copyr
validation:
openAPIV3Schema:
type: object
properties:
ruleType:
type: string
namespaces:
type: array
items:
type: string
selector:
type: string A hneď ho vytvoríme jednoduché pravidlo — vyhľadávanie v priestore mien s menom default všetky ConfigMap s menovkami ako copyrator: "true":
apiVersion: flant.com/v1
kind: CopyratorRule
metadata:
name: main-rule
labels:
module: copyrator
ruleType: configmap
selector:
copyrator: "true"
namespace: defaultPripravený! Teraz musíme nejakým spôsobom získať informácie o našom pravidle. Dovoľte mi hneď urobiť rezerváciu, že nebudeme sami zapisovať požiadavky na server klastra API. Na to nám poslúži hotová knižnica Python :
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')}V dôsledku spustenia tohto kódu dostaneme nasledovné:
{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}Skvelé: podarilo sa nám získať pravidlo pre operátora. A čo je najdôležitejšie, urobili sme to, čomu sa hovorí Kubernetesova cesta.
Premenné prostredia alebo príznaky? Berieme všetko!
Prejdime ku konfigurácii hlavného operátora. Existujú dva základné prístupy ku konfigurácii aplikácií:
- použite možnosti príkazového riadku;
- používať premenné prostredia.
Možnosti príkazového riadka vám umožňujú flexibilnejšie čítať nastavenia s podporou a overovaním typov údajov. Štandardná knižnica Pythonu má modul argparser, ktorý budeme používať. Podrobnosti a príklady jeho schopností sú k dispozícii v .
V našom prípade by takto vyzeral príklad nastavenia čítania príznakov príkazového riadku:
parser = ArgumentParser(
description='Copyrator - copy operator.',
prog='copyrator'
)
parser.add_argument(
'--namespace',
type=str,
default=getenv('NAMESPACE', 'default'),
help='Operator Namespace'
)
parser.add_argument(
'--rule-name',
type=str,
default=getenv('RULE_NAME', 'main-rule'),
help='CRD Name'
)
args = parser.parse_args()Na druhej strane, pomocou premenných prostredia v Kubernetes môžete ľahko prenášať servisné informácie o podu vnútri kontajnera. Napríklad môžeme získať informácie o mennom priestore, v ktorom modul beží, pomocou nasledujúcej konštrukcie:
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace Logika operátora
Aby sme pochopili, ako oddeliť metódy práce s ConfigMap a Secret, použijeme špeciálne mapy. Potom môžeme pochopiť, aké metódy potrebujeme na sledovanie a vytváranie objektu:
LIST_TYPES_MAP = {
'configmap': 'list_namespaced_config_map',
'secret': 'list_namespaced_secret',
}
CREATE_TYPES_MAP = {
'configmap': 'create_namespaced_config_map',
'secret': 'create_namespaced_secret',
}Ďalej musíte prijímať udalosti zo servera API. Implementujme to nasledovne:
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)Po prijatí udalosti prejdeme k hlavnej logike jej spracovania:
# Типы событий, на которые будем реагировать
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) Hlavná logika je pripravená! Teraz to všetko musíme zabaliť do jedného balíka Python. Pripravíme súbor setup.py, napíšte tam meta informácie o projekte:
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='maksim.nabokikh@flant.com',
include_package_data=True,
install_requires=[
'kubernetes==9.0.0',
],
entry_points={
'console_scripts': [
'{0} = {0}.cli:main'.format(_NAME),
]
}
)NB: Klient kubernetes pre Python má svoje vlastné verzovanie. Viac informácií o kompatibilite medzi klientskymi verziami a verziami Kubernetes nájdete v .
Teraz náš projekt vyzerá takto:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакетаDocker a Helm
Dockerfile bude neuveriteľne jednoduchý: vezmite základný obraz python-alpine a nainštalujte náš balík. Odložme jeho optimalizáciu na lepšie časy:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]Nasadenie pre operátora je tiež veľmi jednoduché:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
spec:
selector:
matchLabels:
name: {{ .Chart.Name }}
template:
metadata:
labels:
name: {{ .Chart.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: privaterepo.yourcompany.com/copyrator:latest
imagePullPolicy: Always
args: ["--rule-type", "main-rule"]
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
serviceAccountName: {{ .Chart.Name }}-accNakoniec musíte vytvoriť vhodnú rolu pre operátora s potrebnými právami:
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Chart.Name }}-acc
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: {{ .Chart.Name }}
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: {{ .Chart.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ .Chart.Name }}
subjects:
- kind: ServiceAccount
name: {{ .Chart.Name }}Celkový
Takto sme si bez strachu, výčitiek alebo učenia sa Go dokázali postaviť vlastného operátora pre Kubernetes v Pythone. Samozrejme, stále má priestor na rast: v budúcnosti bude schopný spracovávať viacero pravidiel, pracovať vo viacerých vláknach, nezávisle sledovať zmeny vo svojich CRD...
Aby sme vám poskytli bližší pohľad na kód, vložili sme ho . Ak chcete príklady serióznejších operátorov implementovaných pomocou Pythonu, môžete obrátiť svoju pozornosť na dva operátory na nasadenie mongodb ( и ).
PS A ak ste príliš leniví na to, aby ste riešili udalosti Kubernetes alebo ste jednoducho viac zvyknutí používať Bash, naši kolegovia pripravili hotové riešenie vo forme (My to v apríli).
PPS
Prečítajte si aj na našom blogu:
- «»;
- «»;
- «»;
- «»;
- «".
Zdroj: hab.com
