Python 中的 Kubernetes Operator,無需框架和 SDK

Python 中的 Kubernetes Operator,無需框架和 SDK

目前,Go 壟斷了人們選擇為 Kubernetes 編寫語句的程式語言。 造成這種情況是有客觀原因的,例如:

  1. Go 有一個強大的開發算子的框架 - 營運商SDK.
  2. Docker 和 Kubernetes 等改變遊戲規則的應用程式是用 Go 編寫的。 用 Go 編寫運算子意味著與生態系統使用相同的語言。
  3. 高效能的 Go 應用程式和開箱即用的並發處理簡單工具。

NB:順便說一下,如何在Go中寫出自己的語句,我們 已經描述過 在我們的一本由外國作者翻譯的作品中。

但是,如果你因為缺乏時間或簡單地說缺乏動力而無法學習 Go,該怎麼辦? 本文提供了一個範例,說明如何使用幾乎每個 DevOps 工程師都知道的最受歡迎的語言之一編寫良好的語句 - 蟒蛇.

認識:影印機 - 影印操作員!

例如,考慮開發一個簡單的語句,旨在在出現新命名空間或兩個實體之一發生變更時複製 ConfigMap:ConfigMap 和 Secret。 從實際角度來看,該運算元對於批次更新應用程式配置(透過更新 ConfigMap)或更新秘密資料非常有用 - 例如,用於使用 Docker 註冊表的金鑰(將 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 Server 寫入請求。 為此,我們將使用現成的 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 中的環境變量,您可以輕鬆地傳輸容器內 pod 的服務資訊。 例如,我們可以透過以下構造來獲取有關 pod 運行所在的命名空間的資訊:

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:Python 的 kubernetes 用戶端有自己的版本控制。 有關客戶端版本和 Kubernetes 版本之間兼容性的更多信息,請參見 相容性矩陣.

現在我們的專案看起來像這樣:

copyrator
├── copyrator
│   ├── cli.py # Логика работы с командной строкой
│   ├── constant.py # Константы, которые мы приводили выше
│   ├── load_crd.py # Логика загрузки CRD
│   └── operator.py # Основная логика работы оператора
└── setup.py # Оформление пакета

Docker 和 Helm

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 }}

就這樣,我們能夠在沒有恐懼、責備或學習 Go 的情況下,用 Python 為 Kubernetes 建立我們自己的操作符。 當然,它仍然有成長的空間:將來它將能夠處理多個規則,在多個線程中工作,並獨立監視 CRD 的變化...

為了讓您更仔細地查看程式碼,我們將其放入 公共儲存庫。 如果您想要使用 Python 實現的更嚴肅的運算子的範例,您可以將注意力轉向用於部署 mongodb 的兩個運算子(第一 и 第二).

PS 如果您懶得處理 Kubernetes 事件,或者您只是更習慣使用 Bash,我們的同事已經準備了以下形式的現成解決方案 外殼操作符 (我們 宣布 四月的事)。

聚苯硫醚

另請閱讀我們的博客:

來源: www.habr.com

添加評論