ตัวดำเนินการ Kubernetes ใน Python ที่ไม่มีเฟรมเวิร์กและ SDK

ตัวดำเนินการ Kubernetes ใน Python ที่ไม่มีเฟรมเวิร์กและ SDK

ปัจจุบัน Go มีการผูกขาดในภาษาการเขียนโปรแกรมที่ผู้คนเลือกที่จะเขียนคำสั่งสำหรับ Kubernetes มีเหตุผลวัตถุประสงค์สำหรับสิ่งนี้ เช่น:

  1. มีกรอบการทำงานที่ทรงพลังสำหรับการพัฒนาตัวดำเนินการใน Go - ตัวดำเนินการ SDK.
  2. แอปพลิเคชันที่พลิกเกมอย่าง Docker และ Kubernetes เขียนด้วยภาษา Go การเขียนโอเปอเรเตอร์ของคุณใน Go หมายถึงการพูดภาษาเดียวกันกับระบบนิเวศ
  3. แอปพลิเคชัน Go ประสิทธิภาพสูงและเครื่องมือง่ายๆ สำหรับการทำงานพร้อมกันนอกกรอบ

NB: โดยวิธีการเขียนคำสั่งของคุณเองใน Go เรา อธิบายไว้แล้ว ในการแปลของเราโดยนักเขียนชาวต่างประเทศ

แต่จะเกิดอะไรขึ้นหากคุณถูกขัดขวางไม่ให้เรียนรู้ Go เนื่องจากไม่มีเวลาหรือพูดง่ายๆ ก็คือ แรงจูงใจ? บทความนี้เป็นตัวอย่างว่าคุณสามารถเขียนข้อความที่ดีโดยใช้ภาษายอดนิยมภาษาใดภาษาหนึ่งซึ่งวิศวกร DevOps เกือบทุกคนรู้ได้อย่างไร หลาม.

พบกับ: เครื่องถ่ายเอกสาร - พนักงานถ่ายเอกสาร!

ตามตัวอย่าง ให้พิจารณาพัฒนาคำสั่งง่ายๆ ที่ออกแบบมาเพื่อคัดลอก ConfigMap เมื่อเนมสเปซใหม่ปรากฏขึ้นหรือเมื่อเอนทิตีหนึ่งในสองเอนทิตีเปลี่ยนแปลง: ConfigMap และ Secret จากมุมมองในทางปฏิบัติ ตัวดำเนินการจะมีประโยชน์สำหรับการอัปเดตการกำหนดค่าแอปพลิเคชันจำนวนมาก (โดยการอัปเดต ConfigMap) หรือสำหรับการอัปเดตข้อมูลลับ - ตัวอย่างเช่น คีย์สำหรับการทำงานกับ Docker Registry (เมื่อเพิ่ม Secret ลงในเนมสเปซ)

ดังนั้น สิ่งที่ผู้ดำเนินการที่ดีควรมี:

  1. การโต้ตอบกับผู้ปฏิบัติงานดำเนินการโดยใช้ คำจำกัดความของทรัพยากรที่กำหนดเอง (ต่อไปนี้จะเรียกว่า CRD)
  2. สามารถกำหนดค่าตัวดำเนินการได้ เมื่อต้องการทำเช่นนี้ เราจะใช้แฟล็กบรรทัดคำสั่งและตัวแปรสภาพแวดล้อม
  3. โครงสร้างคอนเทนเนอร์ Docker และแผนภูมิ Helm ได้รับการออกแบบเพื่อให้ผู้ใช้สามารถติดตั้งตัวดำเนินการลงในคลัสเตอร์ Kubernetes ได้อย่างง่ายดาย (ด้วยคำสั่งเดียว)

CRD

เพื่อให้ผู้ปฏิบัติงานรู้ว่าต้องค้นหาทรัพยากรใดและควรดูที่ไหน เราต้องตั้งกฎให้เขา แต่ละกฎจะแสดงเป็นออบเจ็กต์ CRD เดียว CRD นี้ควรมีฟิลด์ใด

  1. ประเภททรัพยากรซึ่งเราจะมองหา (ConfigMap หรือ Secret)
  2. รายการเนมสเปซซึ่งทรัพยากรควรจะตั้งอยู่
  3. เลือกโดยที่เราจะค้นหาทรัพยากรในเนมสเปซ

มาอธิบาย 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 สำเร็จรูป kubernetes-ไคลเอนต์:

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

ตัวแปรสภาพแวดล้อมหรือแฟล็ก? เรารับทุกอย่าง!

มาดูการกำหนดค่าตัวดำเนินการหลักกันดีกว่า มีสองวิธีพื้นฐานในการกำหนดค่าแอปพลิเคชัน:

  1. ใช้ตัวเลือกบรรทัดคำสั่ง
  2. ใช้ตัวแปรสภาพแวดล้อม

ตัวเลือกบรรทัดคำสั่งช่วยให้คุณอ่านการตั้งค่าได้อย่างยืดหยุ่นมากขึ้น พร้อมรองรับประเภทข้อมูลและการตรวจสอบความถูกต้อง ไลบรารีมาตรฐานของ 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 ได้อย่างอิสระ...

เราได้ใส่โค้ดไว้เพื่อให้คุณดูโค้ดได้ละเอียดยิ่งขึ้น พื้นที่เก็บข้อมูลสาธารณะ. หากคุณต้องการตัวอย่างโอเปอเรเตอร์ที่จริงจังมากขึ้นที่นำไปใช้งานโดยใช้ Python คุณสามารถหันความสนใจไปที่โอเปอเรเตอร์สองตัวสำหรับการปรับใช้ mongodb (แรก и ที่สอง).

ป.ล. และหากคุณขี้เกียจเกินไปที่จะจัดการกับกิจกรรม Kubernetes หรือคุณคุ้นเคยกับการใช้ Bash มากขึ้นเพื่อนร่วมงานของเราได้เตรียมโซลูชันสำเร็จรูปในรูปแบบ ตัวดำเนินการเชลล์ (เรา ประกาศ ในเดือนเมษายน)

PPS

อ่านเพิ่มเติมในบล็อกของเรา:

ที่มา: will.com

เพิ่มความคิดเห็น