
Go on hetkel on ainus programmeerimiskeel, mida inimesed valivad Kubernetes'i operaatorite kirjutamiseks. Sellel on mitmeid objektiivseid põhjuseid, nagu:
- Mäletatavasti on olemas võimas raamistik operaatorite arendamiseks Go-s — .
- Go-s on kirjutatud mängu muutvaid rakendusi, nagu Docker ja Kubernetes. Oma operaatori kirjutamine Go-s on nagu rääkimine ökosüsteemiga ühisel keelel.
- Go rakenduste kõrge jõudlus ja lihtsad tööriistad konkureerimise jaoks "välja pakutud".
NB: Muide, kuidas kirjutada oma operaatorit Go-s, oleme ühes meie tõlkes välisautorite teosest.
Kuid mis siis, kui Go õppimist takistab aeg või lihtsalt motivatsiooni puudumine? Artiklis on näide, kuidas kirjutada kvaliteetne operaator, kasutades ühte populaarseimat keelt, mida iga DevOps-insener tavaliselt tunneb — Python.
Tere tulemast: Kopi operaatort!
Vaatame lihtsa operaatori väljatöötamist, mis on mõeldud ConfigMap'i kopeerimiseks kas uue nimespaani lisamise või ühe kahe entiteedi, ConfigMap'i ja Salajase, muutmise korral. Praktilise kasutamise seisukohalt võib operaator olla kasulik rakenduse konfigureerimisprotsesside massiliseks uuendamiseks (ConfigMap'i uuendamise kaudu) või salajaste andmete, näiteks Docker Registry töötamise võtmete uuendamiseks (nt Salajase lisamine nimespaani).
Nii, mida peab omama hea operaator:
- Operaatoriga suhtlemine toimub kaudu (edasi — CRD).
- Operaatorit saab konfigureerida. Selleks kasutame käsurea lippusid ja keskkonnamuutujid.
- Docker konteineri ja Helm-charts'i kogumine on kavandatud nii, et kasutajad saavad lihtsalt (literally ühe käsuga) operaatori oma Kubernetes'i klastrisse installida.
CRD
Kuna operaator peab teadma, milliseid ressursse ja kus teda otsida, peame määrama tema reegli. Iga reegel esitatakse ühe CRD objektina. Millised väljad peaksid sellel CRD-l olema?
- Otsitava ressursitüüp, mida me otsime (ConfigMap või Secret).
- Nimespaanide loetelu, kus ressursid peaksid asuma.
- Valija, mille alusel me ressursse nimespaanis otsime.
Kirjeldame 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 Ja loome kohe lihtsa reegli — määratlemiseks nimel Seejärel käivitame exec käsu ja ootame 5000 sekundit, et jätkata jälgimist: kõik ConfigMapid label'idega kujul copyrator: "true":
apiVersion: flant.com/v1
kind: CopyratorRule
metadata:
name: peamine-reegel
labels:
module: copyrator
ruleType: configmap
selector:
copyrator: "true"
namespace: defaultValmis! Nüüd peame kuidagi saama teavet meie reegli kohta. Täpsustan kohe, et me ei hakka ise API Server'ile päringuid kirjutama. Selleks kasutame valmis Python'i raamatukogu :
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')}Selle koodi tulemuseks on:
{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}Suurepärane: me saime reegli operaatorile. Ja mis kõige tähtsam — me tegime seda nii-öelda Kubernetes'i viisil.
Keskkonnamuutujad või lipud? Võtame kõik!
Liigume edasi operaatori põhikonfiguratsiooni juurde. On kaks põhimeetodit rakenduste konfigureerimiseks:
- kasutada käsurea argumente;
- kasutada keskkonnamuutujad.
Käsurea argumentide abil saab seadeid lugeda paindlikumalt, andmetüüpide toega ja valideerimisega. Python'i standardraamatukogus on moodul argparser, mida me ka kasutame. Üksikasjad ja näited selle võimalustest on saadaval .
Nii näeb meie juhtumi jaoks välja näide käsurea lippude lugemise seadistamisest:
parser = ArgumentParser(
description='Copyrator - copy operator.',
prog='copyrator'
)
parser.add_argument(
'--namespace',
type=str,
default=getenv('NAMESPACE', 'default'),
help='Operaatori nimel'
)
parser.add_argument(
'--rule-name',
type=str,
default=getenv('RULE_NAME', 'peamine-reegel'),
help='CRD nimi'
)
args = parser.parse_args()Teiselt poolt on Kuberneteses keskkonnamuutujate abil lihtne edastada teavet pod'i kohta konteinerisse. Näiteks saame teada namespace'i, milles pod töötab, järgmise konstruktsiooniga:
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace Operaatori töölogika
Kuna mõistame, kuidas eristada meetodeid ConfigMap'i ja Secret'i jaoks, kasutame spetsiaalseid kaarte. Nii saame aru, milliseid meetodeid on meie objektide jälgimiseks ja loomiseks vaja:
LIST_TYPES_MAP = {
'configmap': 'list_namespaced_config_map',
'secret': 'list_namespaced_secret',
}
CREATE_TYPES_MAP = {
'configmap': 'create_namespaced_config_map',
'secret': 'create_namespaced_secret',
}Seejärel on vajalik saada sündmusi API serverilt. Teeme seda järgmiselt:
def handle(specs):
kubernetes.config.load_incluster_config()
v1 = kubernetes.client.CoreV1Api()
# Saame meetodi objektide jälgimiseks
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)Sündmuse saamisel liigume põhiloogika töötlemise juurde:
# Типы событий, на которые будем реагировать
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õhiloogika on valmis! Nüüd tuleb kogu see kokku panna ühte Python'i paketti. Vormistame faili setup.py, kirjutame sinna projekti metaandmed:
from sys import version_info
from setuptools import find_packages, setup
if version_info[:2] < (3, 5):
raise RuntimeError(
'Toe python versioon %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: Kubernetes'i klient Python'i jaoks omab oma versioonimist. Klientide ja Kubernetes'e versioonide ühilduvuse kohta saab lisainfot .
Praegu näeb meie projekt välja järgmine:
copyrator
├── copyrator
│ ├── cli.py # Command line'i tööloogika
│ ├── constant.py # Ülesanne, mille me varem määrasime
│ ├── load_crd.py # CRD laadimise tööloogika
│ └── operator.py # Operaatori põhiloogika
└── setup.py # Paketi vormistamineDocker ja Helm
Dockerfile on ülilihtne: võtame põhijuurikaks python-alpine ja installime meie paketi. Selle optimeerimise lükkame edasi paremate aegade tarvis:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]Operatori juurutamine on samuti väga lihtne:
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 }}-accLõpuks on vaja luua sobiv roll operaatorile vajalike õigustega:
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 }}Kokkuvõte
Nii, ilma hirmu, etteheiteid ja Go õppimata saime ise koduse operaatori kokku panna Kuberneteses Pythonis. Loomulikult on tal veel arenguruumi: tulevikus võiks ta hallata mitmeid reegleid, töötada mitmes lõimes, jälgida oma CRD muutusi iseseisvalt...
Et koodiga lähemalt tutvuda, oleme selle kogunud . Kui otsite tõsisemaid operaatorite näiteid, mille on loonud Python, võite kaaluda kahte operaatorit mongodb juurutamiseks ( ja ).
P.S. Ja kui teil on liiga raske aru saada Kubernetesest või eelistate lihtsalt Bashit kasutada — meie kolleegid on valmistanud valmis lahenduse (meie selle aprillis).
P.P.S.
Lugege ka meie blogist:
- «»;
- «»;
- «»;
- «»;
- «».
Allikas: habr.com
