
Go Å”obrÄ«d ir monopols programmÄÅ”anas valodu vidÅ«, ko cilvÄki izvÄlas operatoru rakstīŔanai Kubernetes. Tam ir objektÄ«vi iemesli, piemÄram:
- PastÄv jaudÄ«gs ietvars operatoru izstrÄdei, izmantojot Go ā .
- Operatori uz Go ir izstrÄdÄti tÄdiem Ā«spÄli mainoÅ”iemĀ» lietotnÄm kÄ Docker un Kubernetes. Rakstot savu operatoru uz Go, jÅ«s runÄjat ar ekosistÄmu vienÄ valodÄ.
- Augsta Go lietotÅu veiktspÄja un vienkÄrÅ”i instrumenti darbam ar konkurenci Ā«no kastesĀ».
PiezÄ«me: Starp citu, kÄ uzrakstÄ«t savu operatoru uz Go, mÄs vienÄ no mÅ«su tulkojumiem no Ärzemju autoriem.
Bet ko darÄ«t, ja Go apgūŔana jums traucÄ laika trÅ«kums vai, vienkÄrÅ”i, motivÄcijas trÅ«kums? Å ajÄ rakstÄ ir piemÄrs, kÄ var uzrakstÄ«t labu operatoru, izmantojot vienu no populÄrÄkajÄm valodÄm, ko praktiski pazÄ«st katrs DevOps inženieris ā Python.
IepazÄ«stieties: KopirotÄjs ā kopÄÅ”anas operators!
Par piemÄru aplÅ«kosim vienkÄrÅ”a operatora izstrÄdi, kas paredzÄts ConfigMap kopÄÅ”anai vai nu jauna namespace parÄdīŔanÄs gadÄ«jumÄ, vai arÄ« vienas no divÄm entitÄtÄm: ConfigMap un Secret maiÅas gadÄ«jumÄ. PraktiskÄs pielietoÅ”anas viedokļa, operators var bÅ«t noderÄ«gs masveida lietojumprogrammu konfigurÄciju atjauninÄÅ”anai (atjauninot ConfigMap) vai arÄ« slepeno datu atjauninÄÅ”anai, piemÄram, atslÄgÄm darbam ar Docker Registry (pievienojot Secret ietvarÄ).
TÄtad, kas jÄbÅ«t labam operatoram:
- SaziÅa ar operatoru notiek, izmantojot (turpmÄk ā CRD).
- Operators var bÅ«t konfigurÄjams. Tam mÄs izmantosim komandlÄ«nijas karogus un vides mainÄ«gos.
- Docker konteineru un Helm shÄmu izstrÄde tiek veikta tÄ, lai lietotÄji varÄtu viegli (burtiski ar vienu komandu) instalÄt operatoru savÄ Kubernetes klasterÄ«.
CRD
Lai operators zinÄtu, kÄdus resursus un kur meklÄt, mums ir jÄnosaka noteikums. Katrs noteikums tiks attÄlots kÄ viens CRD objekts. KÄdi lauki jÄbÅ«t Å”im CRD?
- Resursa tips, kuru mÄs meklÄsim (ConfigMap vai Secret).
- Namespace saraksts, kuros jÄatrodas resursiem.
- Selector, pÄc kura mÄs meklÄsim resursus namespace.
Aprakstīsim 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 UnÄ brÄ«dÄ« izveidosim vienkÄrÅ”u noteikumu ā meklÄÅ”anai namespace ar nosaukumu default visiem ConfigMap ar tÄdÄm etiÄ·etÄm kÄ copyrator: "true":
apiVersion: flant.com/v1
kind: CopyratorRule
metadata:
name: main-rule
labels:
module: copyrator
ruleType: configmap
selector:
copyrator: "true"
namespace: defaultGatavs! Tagad ir jÄnopelna informÄcija par mÅ«su noteikumu. Uzreiz jÄnorÄda, ka mÄs neizveidosim API Server pieprasÄ«jumus paÅ”i. MÄs izmantosim gatavu Python bibliotÄku :
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')}Å Ä« koda izpildes rezultÄtÄ saÅemsim sekojoÅ”o:
{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}Lieliski: mÄs esam ieguvuÅ”i noteikumu operatoram. Un, kas ir vissvarÄ«gÄkais ā mÄs to izdarÄ«jÄm, kÄ mÄdz teikt, Kubernetes veidÄ.
Vides mainīgie vai karogi? Šemam visu!
PÄrejam pie operatora pamatkonfigurÄcijas. Ir divi pamatpieejas veidi lietojumprogrammu konfigurÄÅ”anai:
- izmantot komandrindas parametrus;
- izmantot vides mainīgos.
Komandrindas parametri ļauj elastÄ«gÄk nolasÄ«t iestatÄ«jumus, atbalstot un validÄjot datu tipus. Standarta Python bibliotÄkÄ ir modulis argparser, ko mÄs izmantosim. SÄ«kÄka informÄcija un piemÄri ir pieejami .
TÄ kÄ mÅ«su gadÄ«jumÄ izskatÄ«sies piemÄrs komandrindas karogu nolasīŔanai:
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()No otras puses, izmantojot vides mainÄ«gos Kubernetes, var viegli pÄrvietot pakalpojuma informÄciju par podu iekÅ”Ä konteinerÄ. PiemÄram, informÄciju par namespace, kurÄ ir palaists pods, varam iegÅ«t ar sekojoÅ”o konstrukciju:
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace Operatora darbības loģika
Lai saprastu, kÄ sadalÄ«t metodes darbÄ«bai ar ConfigMap un Secret, izmantosim Ä«paÅ”as kartes. TÄdÄjÄdi mÄs spÄsim saprast, kÄdas metodes mums nepiecieÅ”amas, lai uzraudzÄ«tu un radÄ«tu 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',
}Tad ir nepiecieÅ”ams saÅemt notikumus no API servera. RealizÄsim to Å”Ädi:
def handle(specs):
kubernetes.config.load_incluster_config()
v1 = kubernetes.client.CoreV1Api()
# Iegūstam metodi, lai uzraudzītu objektus
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)PÄc notikuma saÅemÅ”anas pÄrejam pie galvenÄs tÄ apstrÄdes loÄ£ikas:
# Š¢ŠøŠæŃ ŃŠ¾Š±ŃŃŠøŠ¹, на ŠŗŠ¾ŃоŃŃŠµ Š±ŃŠ“ем ŃŠµŠ°Š³ŠøŃоваŃŃ
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) GalvenÄ loÄ£ika ir gatava! Tagad ir jÄiekļauj viss tas vienÄ Python paketÄ. NoformÄjam failu setup.py, rakstÄm tur projektu metainformÄciju:
from sys import version_info
from setuptools import find_packages, setup
if version_info[:2] < (3, 5):
raise RuntimeError(
'AtbalstÄ«tÄ python versija %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),
]
}
)PiezÄ«me: Kubernetes klients Pythonam piedÄvÄ savu versiju pÄrvaldÄ«bu. PlaÅ”Äku informÄciju par klienta versiju un Kubernetes versiju saderÄ«bu var uzzinÄt no .
Tagad mÅ«su projekts izskatÄs Å”Ädi:
copyrator
āāā copyrator
ā āāā cli.py # Darba loÄ£ika ar komandu rindu
ā āāā constant.py # Konstantas, kuras mÄs iepriekÅ” minÄjÄm
ā āāā load_crd.py # CRD ielÄdes loÄ£ika
ā āāā operator.py # Operatora pamatdarbÄ«bas loÄ£ika
āāā setup.py # Paketes noformÄjumsDocker un Helm
Dockerfile bÅ«s vienkÄrÅ”s: Åemsim pamatattÄlu python-alpine un instalÄsim mÅ«su paketi. TÄs optimizÄciju atliksim uz vÄlÄku laiku:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]IzvietoÅ”ana operatoram ir arÄ« ļoti vienkÄrÅ”a:
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 }}-accVisbeidzot, ir jÄizveido atbilstoÅ”a loma operatoram ar nepiecieÅ”amajÄm tiesÄ«bÄm:
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 }}RezultÄts
TÄdÄjÄdi, bez bailÄm, pÄrmetumiem un Go apguves, mÄs spÄjam izveidot savu paÅ”u operatoru Kubernetes platformai, izmantojot Python. Protams, tam vÄl ir kur pilnveidoties: nÄkotnÄ tam bÅ«s iespÄja apstrÄdÄt vairÄkus noteikumus, strÄdÄt paralÄli, patstÄvÄ«gi uzraudzÄ«t savas CRD izmaiÅasā¦
Lai varÄtu tuvÄk iepazÄ«ties ar kodu, mÄs to ievietojÄm . Ja vÄlaties redzÄt nopietnÄku operatoru piemÄrus, kas izstrÄdÄti ar Python palÄ«dzÄ«bu, varat pievÄrst uzmanÄ«bu diviem operatoriem MongoDB izvietoÅ”anai ( un ).
P.S. Un, ja jums ir slinkums izprast Kubernetes notikumus vai vienkÄrÅ”i ir ÄrtÄk izmantot Bash ā mÅ«su kolÄÄ£i ir sagatavojuÅ”i gatavu risinÄjumu, (mÄs to izdarÄ«jÄm aprÄ«lÄ«).
P.P.S.
Lasiet arÄ« mÅ«su emuÄra rakstos:
- «»;
- «»;
- «»;
- «»;
- «».
Avots: habr.com
