
A Go jelenleg a Kubernetes operátorok írására választott programozási nyelvek monopolistája. Ezt számos objektív ok indokolja:
- Van egy erőteljes keretrendszer az operátorok Go nyelven történő fejlesztésére — .
- A Go nyelven írták az olyan "átformáló" alkalmazásokat, mint a Docker és a Kubernetes. Saját operátor írása Go nyelven azt jelenti, hogy egy nyelven kommunikálunk az ökoszisztémával.
- A Go alkalmazások magas teljesítménye és az előre beépített egyszerű eszközök a párhuzamosság kezelésére.
NB: Egyébként, hogyan lehet megírni a saját operátorát Go nyelven, azt az egyik külföldi szerzők fordításában.
De mi van, ha az idő hiánya vagy egyszerűen csak a motiváció hiánya gátolja az Ön Go nyelvű tanulmányait? A cikk példát mutat arra, hogyan lehet jó operátort írni, használva az egyike a legnépszerűbb nyelveknek, amelyet szinte minden DevOps mérnök ismer — Python.
Itt van: a Kópirovat - a másoló operátor!
Példaként nézzük meg egy egyszerű operátor fejlesztését, amely ConfigMap másolására szolgál új namespace megjelenésekor, vagy az egyik két entitás, a ConfigMap és a Secret módosulásakor. Praktikus alkalmazás szempontjából az operátor hasznos lehet az alkalmazások konfigurációinak tömeges frissítésére (a ConfigMap frissítésével) vagy a titkos adatok frissítésére — például a Docker Registry-hez szükséges kulcsokra (amikor Secret-et adunk hozzá a namespace-hez).
Tehát, mi legyen egy jó operátorban:
- Az operátorral való interakció a (továbbiakban — CRD) révén történik.
- Az operátor konfigurálható. Ehhez parancssori kapcsolókat és környezeti változókat fogunk használni.
- A Docker-konténer és a Helm-diagramos összeállításokat úgy dolgozzuk ki, hogy a felhasználók könnyen (szinte egyetlen parancs segítségével) telepíthessék az operátort a Kubernetes klaszterükbe.
CRD
Ahhoz, hogy az operátor tudja, milyen erőforrásokat és hol kell keresnie, szabályt kell megadnunk számára. Minden szabályt egy CRD objektum formájában fogunk megjeleníteni. Milyeneknek kell lennie ennek a CRD-nek a mezői?
- Erőforrás típusa, amelyet keresni fogunk (ConfigMap vagy Secret).
- Namespace-ek listája, ahol az erőforrásoknak lennie kell.
- Kiválasztó, amellyel az erőforrásokat a namespace-ben keresni fogjuk.
Írjuk le a CRD-t:
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 És már azonnal létrehozunk egyszerű szabályt — a névterület keresésére a következő névvel alapértelmezett minden ConfigMap-ot az alábbi címkékkel copyrator: "true":
apiVersion: flant.com/v1
kind: CopyratorRule
metadata:
name: main-rule
labels:
module: copyrator
ruleType: configmap
selector:
copyrator: "true"
namespace: defaultKész! Most valahogy információt kell szereznünk a szabályunkról. Azonnal szeretném megjegyezni, hogy nem fogunk saját API Server lekérdezéseket írni a klaszterhez. Ehhez kész Python könyvtárat fogunk használni :
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')}A kód futása után a következő eredményt kapjuk:
{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}Remek: sikerült megszereznünk a szabályt az operátortól. És ami a legfontosabb — ezt úgy tettük, ahogy a Kubernetes-ben kell.
Környezeti változók vagy zászlók? Mindent elviszünk!
Átérünk az operátor fő konfigurációjára. Két alapvető megközelítés van az alkalmazások konfigurálásához:
- parancssori argumentumok használata;
- környezeti változók használata.
A parancssori argumentumok rugalmasabbá teszik a beállítások olvasását, támogatva a típusok ellenőrzését is. A Python standard könyvtárában van egy modul, argparser, amelyet használni fogunk. Részletek és példák a lehetőségeiről elérhetőek .
Íme, hogyan fog kinézni az argumentumok parancssori olvasására szolgáló beállítás példa a mi esetünkben:
parser = ArgumentParser(
description='Copyrator - másoló operátor.',
prog='copyrator'
)
parser.add_argument(
'--namespace',
type=str,
default=getenv('NAMESPACE', 'default'),
help='Operátor névtere'
)
parser.add_argument(
'--rule-name',
type=str,
default=getenv('RULE_NAME', 'main-rule'),
help='CRD név'
)
args = parser.parse_args()Másrészt a Kubernetes környezeti változóin keresztül könnyen átvitelhetjük a pod-hoz kapcsolódó információt a konténerbe. Például, a namespace információt, ahol a pod fut, a következő konstrukcióval kaphatjuk meg:
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace Az operátor működésének logikája
Ahhoz, hogy megértsük, hogyan különböztessük meg a ConfigMap és a Secret kezelési módszereit, használjunk speciális térképeket. Így megérthetjük, hogy milyen módszerekre van szükségünk az objektumok figyeléséhez és létrehozásához:
LIST_TYPES_MAP = {
'configmap': 'list_namespaced_config_map',
'secret': 'list_namespaced_secret',
}
CREATE_TYPES_MAP = {
'configmap': 'create_namespaced_config_map',
'secret': 'create_namespaced_secret',
}Ezután szükséges eseményeket kapni az API szerverről. Ezt a következőképpen valósítjuk meg:
def handle(specs):
kubernetes.config.load_incluster_config()
v1 = kubernetes.client.CoreV1Api()
# Megkapjuk az objektumok figyeléséhez szükséges módszert
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)Az esemény fogadása után áttérünk a feldolgozás fő logikájára:
# Типы событий, на которые будем реагировать
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) A fő logika készen áll! Most mindezt egy Python csomagba kell csomagolnunk. Elkészítjük a fájlt setup.py, és beleírjuk a projekt metaadatait:
from sys import version_info
from setuptools import find_packages, setup
if version_info[:2] < (3, 5):
raise RuntimeError(
'Nem támogatott python verzió %s.' % '.'.join(version_info)
)
_NAME = 'copyrator'
setup(
name=_NAME,
version='0.0.1',
packages=find_packages(),
classifiers=[
'Fejlesztési állapot :: 3 - Alpha',
'Programozási nyelv :: Python',
'Programozási nyelv :: Python :: 3',
'Programozási nyelv :: Python :: 3.5',
'Programozási nyelv :: Python :: 3.6',
'Programozási nyelv :: 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: A Python-hoz készült kubernetes kliens verziója szigorúan követi a verziózást. A kliens verziója és a Kubernetes verzióival kapcsolatos kompatibilitásról az alábbiakban tájékozódhatunk .
Jelenleg a projektünk így néz ki:
copyrator
├── copyrator
│ ├── cli.py # A parancssoros logika
│ ├── constant.py # Az előbb említett konstansok
│ ├── load_crd.py # A CRD betöltési logikája
│ └── operator.py # Az operátor fő működési logikája
└── setup.py # A csomag kialakításaDocker és Helm
A Dockerfile elég egyszerű lesz: alapként vesszük a python-alpine képet, és telepítjük a csomagunkat. Az optimalizációt későbbre halasztjuk:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]A teleoperator számára a telepítés szintén nagyon egyszerű:
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 }}-accVégül létre kell hozni a megfelelő szerepkört az operátor számára a szükséges jogokkal:
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 }}Result
Így, félelem, szemrehányás és Go tanulmányozása nélkül, sikerült összeraknunk a saját műveletünket a Kubernetes számára Pythonban. Persze, még van hova fejlődni: a jövőben több szabályt fog tudni kezelni, több szálon dolgozni, és önállóan figyelni a CRD-i változásait...
Annak érdekében, hogy közelebbi kapcsolatba kerüljünk a kóddal, összegyűjtöttük egy . Ha komolyabb operátor példákat szeretnél Python segítségével, akkor figyelj két mongodb telepítő operátor irányába ( és ).
P.S. Ha pedig nem akarod magad belemélyedni a Kubernetes eseményeibe, vagy egyszerűen jobban szeretsz Bash-t használni – kollégáink kész megoldásként szolgáltattak egy (ezt áprilisban készítettük).
P.P.S.
Olvasson további információkat a blogunkban:
- «»;
- «»;
- «»;
- «»;
- «».
Forrás: habr.com
