ปัจจุบัน Go มีการผูกขาดในภาษาการเขียนโปรแกรมที่ผู้คนเลือกที่จะเขียนคำสั่งสำหรับ Kubernetes มีเหตุผลวัตถุประสงค์สำหรับสิ่งนี้ เช่น:
- มีกรอบการทำงานที่ทรงพลังสำหรับการพัฒนาตัวดำเนินการใน Go -
ตัวดำเนินการ SDK . - แอปพลิเคชันที่พลิกเกมอย่าง Docker และ Kubernetes เขียนด้วยภาษา Go การเขียนโอเปอเรเตอร์ของคุณใน Go หมายถึงการพูดภาษาเดียวกันกับระบบนิเวศ
- แอปพลิเคชัน Go ประสิทธิภาพสูงและเครื่องมือง่ายๆ สำหรับการทำงานพร้อมกันนอกกรอบ
NB: โดยวิธีการเขียนคำสั่งของคุณเองใน Go เรา
แต่จะเกิดอะไรขึ้นหากคุณถูกขัดขวางไม่ให้เรียนรู้ Go เนื่องจากไม่มีเวลาหรือพูดง่ายๆ ก็คือ แรงจูงใจ? บทความนี้เป็นตัวอย่างว่าคุณสามารถเขียนข้อความที่ดีโดยใช้ภาษายอดนิยมภาษาใดภาษาหนึ่งซึ่งวิศวกร DevOps เกือบทุกคนรู้ได้อย่างไร หลาม.
พบกับ: เครื่องถ่ายเอกสาร - พนักงานถ่ายเอกสาร!
ตามตัวอย่าง ให้พิจารณาพัฒนาคำสั่งง่ายๆ ที่ออกแบบมาเพื่อคัดลอก ConfigMap เมื่อเนมสเปซใหม่ปรากฏขึ้นหรือเมื่อเอนทิตีหนึ่งในสองเอนทิตีเปลี่ยนแปลง: ConfigMap และ Secret จากมุมมองในทางปฏิบัติ ตัวดำเนินการจะมีประโยชน์สำหรับการอัปเดตการกำหนดค่าแอปพลิเคชันจำนวนมาก (โดยการอัปเดต ConfigMap) หรือสำหรับการอัปเดตข้อมูลลับ - ตัวอย่างเช่น คีย์สำหรับการทำงานกับ Docker Registry (เมื่อเพิ่ม Secret ลงในเนมสเปซ)
ดังนั้น สิ่งที่ผู้ดำเนินการที่ดีควรมี:
- การโต้ตอบกับผู้ปฏิบัติงานดำเนินการโดยใช้
คำจำกัดความของทรัพยากรที่กำหนดเอง (ต่อไปนี้จะเรียกว่า CRD) - สามารถกำหนดค่าตัวดำเนินการได้ เมื่อต้องการทำเช่นนี้ เราจะใช้แฟล็กบรรทัดคำสั่งและตัวแปรสภาพแวดล้อม
- โครงสร้างคอนเทนเนอร์ Docker และแผนภูมิ Helm ได้รับการออกแบบเพื่อให้ผู้ใช้สามารถติดตั้งตัวดำเนินการลงในคลัสเตอร์ Kubernetes ได้อย่างง่ายดาย (ด้วยคำสั่งเดียว)
CRD
เพื่อให้ผู้ปฏิบัติงานรู้ว่าต้องค้นหาทรัพยากรใดและควรดูที่ไหน เราต้องตั้งกฎให้เขา แต่ละกฎจะแสดงเป็นออบเจ็กต์ CRD เดียว CRD นี้ควรมีฟิลด์ใด
- ประเภททรัพยากรซึ่งเราจะมองหา (ConfigMap หรือ Secret)
- รายการเนมสเปซซึ่งทรัพยากรควรจะตั้งอยู่
- เลือกโดยที่เราจะค้นหาทรัพยากรในเนมสเปซ
มาอธิบาย 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
และเราจะสร้างมันขึ้นมาทันที กฎง่ายๆ — เพื่อค้นหาในเนมสเปซด้วยชื่อ default
ConfigMap ทั้งหมดที่มีป้ายกำกับเช่น copyrator: "true"
:
apiVersion: flant.com/v1
kind: CopyratorRule
metadata:
name: main-rule
labels:
module: copyrator
ruleType: configmap
selector:
copyrator: "true"
namespace: default
พร้อม! ตอนนี้เราจำเป็นต้องได้รับข้อมูลเกี่ยวกับกฎของเรา ให้ฉันจองทันทีว่าเราจะไม่เขียนคำขอไปยังเซิร์ฟเวอร์ API ของคลัสเตอร์ด้วยตนเอง ในการดำเนินการนี้ เราจะใช้ไลบรารี 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')}
จากการรันโค้ดนี้เราได้รับสิ่งต่อไปนี้:
{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}
เยี่ยมมาก: เราจัดการเพื่อให้ได้กฎสำหรับผู้ปฏิบัติงาน และที่สำคัญที่สุด เราทำสิ่งที่เรียกว่าวิธี Kubernetes
ตัวแปรสภาพแวดล้อมหรือแฟล็ก? เรารับทุกอย่าง!
มาดูการกำหนดค่าตัวดำเนินการหลักกันดีกว่า มีสองวิธีพื้นฐานในการกำหนดค่าแอปพลิเคชัน:
- ใช้ตัวเลือกบรรทัดคำสั่ง
- ใช้ตัวแปรสภาพแวดล้อม
ตัวเลือกบรรทัดคำสั่งช่วยให้คุณอ่านการตั้งค่าได้อย่างยืดหยุ่นมากขึ้น พร้อมรองรับประเภทข้อมูลและการตรวจสอบความถูกต้อง ไลบรารีมาตรฐานของ Python มีโมดูล argparser
ซึ่งเราจะใช้ รายละเอียดและตัวอย่างความสามารถมีอยู่ใน
ในกรณีของเรา นี่คือตัวอย่างการตั้งค่าการอ่านแฟล็กบรรทัดคำสั่ง:
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()
ในทางกลับกัน เมื่อใช้ตัวแปรสภาพแวดล้อมใน Kubernetes คุณสามารถถ่ายโอนข้อมูลบริการเกี่ยวกับพ็อดภายในคอนเทนเนอร์ได้อย่างง่ายดาย ตัวอย่างเช่น เราสามารถรับข้อมูลเกี่ยวกับเนมสเปซที่พ็อดทำงานโดยมีโครงสร้างต่อไปนี้:
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ตรรกะของตัวดำเนินการ
เพื่อให้เข้าใจถึงวิธีแยกวิธีการทำงานกับ ConfigMap และ Secret เราจะใช้แผนที่พิเศษ จากนั้นเราจะเข้าใจได้ว่าเราต้องติดตามและสร้างออบเจ็กต์ด้วยวิธีใด:
LIST_TYPES_MAP = {
'configmap': 'list_namespaced_config_map',
'secret': 'list_namespaced_secret',
}
CREATE_TYPES_MAP = {
'configmap': 'create_namespaced_config_map',
'secret': 'create_namespaced_secret',
}
ถัดไป คุณต้องรับเหตุการณ์จากเซิร์ฟเวอร์ API เรามาปฏิบัติดังนี้:
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)
หลังจากได้รับเหตุการณ์แล้ว เราจะไปยังตรรกะหลักในการประมวลผล:
# Типы событий, на которые будем реагировать
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)
ตรรกะหลักพร้อมแล้ว! ตอนนี้เราจำเป็นต้องรวมทั้งหมดนี้ไว้ในแพ็คเกจ Python เดียว เราเตรียมไฟล์ setup.py
เขียนข้อมูลเมตาเกี่ยวกับโครงการที่นั่น:
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 สำหรับ Python มีการกำหนดเวอร์ชันของตัวเอง ข้อมูลเพิ่มเติมเกี่ยวกับความเข้ากันได้ระหว่างเวอร์ชันไคลเอ็นต์และเวอร์ชัน Kubernetes มีอยู่ใน
ตอนนี้โครงการของเรามีลักษณะดังนี้:
copyrator
├── copyrator
│ ├── cli.py # Логика работы с командной строкой
│ ├── constant.py # Константы, которые мы приводили выше
│ ├── load_crd.py # Логика загрузки CRD
│ └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета
นักเทียบท่าและหางเสือ
Dockerfile จะเรียบง่ายอย่างไม่น่าเชื่อ: ใช้อิมเมจ python-alpine พื้นฐานและติดตั้งแพ็คเกจของเรา ขอเลื่อนการเพิ่มประสิทธิภาพออกไปจนกว่าจะถึงเวลาที่ดีขึ้น:
FROM python:3.7.3-alpine3.9
ADD . /app
RUN pip3 install /app
ENTRYPOINT ["copyrator"]
การปรับใช้สำหรับผู้ปฏิบัติงานนั้นง่ายมากเช่นกัน:
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 }}-acc
สุดท้ายนี้ คุณต้องสร้างบทบาทที่เหมาะสมสำหรับผู้ปฏิบัติงานที่มีสิทธิ์ที่จำเป็น:
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 }}
ทั้งหมด
ด้วยเหตุนี้ เราจึงสามารถสร้างโอเปอเรเตอร์ของเราเองสำหรับ Kubernetes ใน Python ได้โดยไม่ต้องกลัว ตำหนิ หรือเรียนรู้ Go แน่นอนว่ายังมีพื้นที่ให้เติบโต: ในอนาคตจะสามารถประมวลผลกฎหลายข้อ ทำงานในหลายเธรด ติดตามการเปลี่ยนแปลงใน CRD ได้อย่างอิสระ...
เราได้ใส่โค้ดไว้เพื่อให้คุณดูโค้ดได้ละเอียดยิ่งขึ้น
ป.ล. และหากคุณขี้เกียจเกินไปที่จะจัดการกับกิจกรรม Kubernetes หรือคุณคุ้นเคยกับการใช้ Bash มากขึ้นเพื่อนร่วมงานของเราได้เตรียมโซลูชันสำเร็จรูปในรูปแบบ
PPS
อ่านเพิ่มเติมในบล็อกของเรา:
- «
การเตรียมคลัสเตอร์ Kubernetes ง่ายและสะดวกหรือไม่ ประกาศตัวดำเนินการ addon "; - «
ขอแนะนำตัวดำเนินการเชลล์: การสร้างตัวดำเนินการสำหรับ Kubernetes กลายเป็นเรื่องง่ายยิ่งขึ้น "; - «
การขยายและการเสริม Kubernetes (ตรวจสอบและรายงานวิดีโอ) "; - «
การเขียนโอเปอเรเตอร์สำหรับ Kubernetes ใน Golang "; - «
ตัวดำเนินการสำหรับ Kubernetes: วิธีเรียกใช้แอปพลิเคชันแบบเก็บสถานะ '
ที่มา: will.com