Kubernetes Operator Π½Π° Python Π±Π΅Π· Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠΎΠ² ΠΈ SDK

Kubernetes Operator Π½Π° Python Π±Π΅Π· Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠΎΠ² ΠΈ SDK

Go Π½Π° Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ являСтся монополистом срСди языков программирования, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ люди Π²Ρ‹Π±ΠΈΡ€Π°ΡŽΡ‚ для написания ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠ² для Kubernetes. Π’ΠΎΠΌΡƒ Π΅ΡΡ‚ΡŒ Ρ‚Π°ΠΊΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ ΠΏΡ€ΠΈΡ‡ΠΈΠ½Ρ‹, ΠΊΠ°ΠΊ:

  1. БущСствуСт ΠΌΠΎΡ‰Π½Π΅ΠΉΡˆΠΈΠΉ Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊ для Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠ² Π½Π° Go β€” Operator SDK.
  2. На Go написаны Ρ‚Π°ΠΊΠΈΠ΅ Β«ΠΏΠ΅Ρ€Π΅Π²Π΅Ρ€Π½ΡƒΠ²ΡˆΠΈΠ΅ ΠΈΠ³Ρ€ΡƒΒ» прилоТСния, ΠΊΠ°ΠΊ Docker ΠΈ Kubernetes. ΠŸΠΈΡΠ°Ρ‚ΡŒ свой ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ Π½Π° Go β€” Π³ΠΎΠ²ΠΎΡ€ΠΈΡ‚ΡŒ с экосистСмой Π½Π° ΠΎΠ΄Π½ΠΎΠΌ языкС.
  3. Высокая ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ Π½Π° Go ΠΈ простыС инструмСнты для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с concurrency Β«ΠΈΠ· ΠΊΠΎΡ€ΠΎΠ±ΠΊΠΈΒ».

NB: ΠšΡΡ‚Π°Ρ‚ΠΈ, ΠΊΠ°ΠΊ Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ свой ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ Π½Π° Go, ΠΌΡ‹ ΡƒΠΆΠ΅ описывали Π² ΠΎΠ΄Π½ΠΎΠΌ ΠΈΠ· Π½Π°ΡˆΠΈΡ… ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΎΠ² Π·Π°Ρ€ΡƒΠ±Π΅ΠΆΠ½Ρ‹Ρ… Π°Π²Ρ‚ΠΎΡ€ΠΎΠ².

Но Ρ‡Ρ‚ΠΎ, Ссли ΠΈΠ·ΡƒΡ‡Π°Ρ‚ΡŒ Go Π²Π°ΠΌ ΠΌΠ΅ΡˆΠ°Π΅Ρ‚ отсутствиС Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ ΠΈΠ»ΠΈ, банально, ΠΌΠΎΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ? Π’ ΡΡ‚Π°Ρ‚ΡŒΠ΅ ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Ρ‚ΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ Π΄ΠΎΠ±Ρ€ΠΎΡ‚Π½Ρ‹ΠΉ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ ΠΎΠ΄ΠΈΠ½ ΠΈΠ· самых популярных языков, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π·Π½Π°Π΅Ρ‚ практичСски ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ DevOps-ΠΈΠ½ΠΆΠ΅Π½Π΅Ρ€, β€” Python.

ВстрСчайтС: ΠšΠΎΠΏΠΈΡ€Π°Ρ‚ΠΎΡ€ β€” ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€!

Для ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π° рассмотрим Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ простого ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π°, ΠΏΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π΅Π½Π½ΠΎΠ³ΠΎ для копирования ConfigMap Π»ΠΈΠ±ΠΎ ΠΏΡ€ΠΈ появлСнии Π½ΠΎΠ²ΠΎΠ³ΠΎ namespace, Π»ΠΈΠ±ΠΎ ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ ΠΎΠ΄Π½ΠΎΠΉ ΠΈΠ· Π΄Π²ΡƒΡ… сущностСй: ConfigMap ΠΈ Secret. Π‘ Ρ‚ΠΎΡ‡ΠΊΠΈ зрСния практичСского примСнСния ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ ΠΏΠΎΠ»Π΅Π·Π΅Π½ для массового обновлСния ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΉ прилоТСния (ΠΏΡƒΡ‚Π΅ΠΌ обновлСния ConfigMap) ΠΈΠ»ΠΈ ΠΆΠ΅ для обновлСния сСкрСтных Π΄Π°Π½Π½Ρ‹Ρ… β€” Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ΠΊΠ»ΡŽΡ‡Π΅ΠΉ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Docker Registry (ΠΏΡ€ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΈ Secret’Π° Π² namespace).

Π˜Ρ‚Π°ΠΊ, Ρ‡Ρ‚ΠΎ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ Ρƒ Ρ…ΠΎΡ€ΠΎΡˆΠ΅Π³ΠΎ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π°:

  1. ВзаимодСйствиС с ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠΌ осущСствляСтся ΠΏΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ Custom Resource Definitions (Π΄Π°Π»Π΅Π΅ β€” CRD).
  2. ΠžΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π°ΡΡ‚Ρ€Π°ΠΈΠ²Π°Ρ‚ΡŒΡΡ. Для этого Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Ρ„Π»Π°Π³ΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ строки ΠΈ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния.
  3. Π‘Π±ΠΎΡ€ΠΊΠ° Docker-ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Π° ΠΈ Helm-Ρ‡Π°Ρ€Ρ‚Π° ΠΏΡ€ΠΎΡ€Π°Π±Π°Ρ‚Ρ‹Π²Π°ΡŽΡ‚ΡΡ Ρ‚Π°ΠΊ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ ΠΌΠΎΠ³Π»ΠΈ Π»Π΅Π³ΠΊΠΎ (Π±ΡƒΠΊΠ²Π°Π»ΡŒΠ½ΠΎ ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ) ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ Π² свой Kubernetes-кластСр.

CRD

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ Π·Π½Π°Π», ΠΊΠ°ΠΊΠΈΠ΅ рСсурсы ΠΈ Π³Π΄Π΅ Π΅ΠΌΡƒ ΠΈΡΠΊΠ°Ρ‚ΡŒ, Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ Π·Π°Π΄Π°Ρ‚ΡŒ для Π½Π΅Π³ΠΎ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ. КаТдоС ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ Π±ΡƒΠ΄Π΅Ρ‚ прСдставлСно Π² Π²ΠΈΠ΄Π΅ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° CRD. КакиС поля Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρƒ этого CRD?

  1. Π’ΠΈΠΏ рСсурса, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΊΠ°Ρ‚ΡŒ (ConfigMap ΠΈΠ»ΠΈ Secret).
  2. Бписок namespace’ΠΎΠ², Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒΡΡ рСсурсы.
  3. Selector, ΠΏΠΎ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌΡƒ ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΊΠ°Ρ‚ΡŒ рСсурсы Π² namespace’Π΅.

ОпишСм 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

И сразу ΠΆΠ΅ создадим простоС ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ β€” Π½Π° поиск Π² namespace’Π΅ с ΠΈΠΌΠ΅Π½Π΅ΠΌ default всСх ConfigMap c label’Π°ΠΌΠΈ Π²ΠΈΠ΄Π° 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-client:

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 way.

ΠŸΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния ΠΈΠ»ΠΈ Ρ„Π»Π°Π³ΠΈ? Π‘Π΅Ρ€Π΅ΠΌ всё!

ΠŸΠ΅Ρ€Π΅Ρ…ΠΎΠ΄ΠΈΠΌ ΠΊ основной ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π°. Π•ΡΡ‚ΡŒ Π΄Π²Π° Π±Π°Π·ΠΎΠ²Ρ‹Ρ… ΠΏΠΎΠ΄Ρ…ΠΎΠ΄Π° ΠΊ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΡŽ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ:

  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’Π΅ Π²Π½ΡƒΡ‚Ρ€ΡŒ ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Π°. НапримСр, ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ namespace, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π·Π°ΠΏΡƒΡ‰Π΅Π½ 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 server. Π Π΅Π°Π»ΠΈΠ·ΡƒΠ΅ΠΌ это ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

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 package. ΠžΡ„ΠΎΡ€ΠΌΠ»ΡΠ΅ΠΌ Ρ„Π°ΠΉΠ» 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 # ΠžΡ„ΠΎΡ€ΠΌΠ»Π΅Π½ΠΈΠ΅ ΠΏΠ°ΠΊΠ΅Ρ‚Π°

Docker ΠΈ Helm

Dockerfile Π±ΡƒΠ΄Π΅Ρ‚ Π΄ΠΎ бСзобразия простым: возьмСм Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π· python-alpine ΠΈ установим наш ΠΏΠ°ΠΊΠ΅Ρ‚. Π•Π³ΠΎ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΡŽ ΠΎΡ‚Π»ΠΎΠΆΠΈΠΌ Π΄ΠΎ Π»ΡƒΡ‡ΡˆΠΈΡ… Π²Ρ€Π΅ΠΌΠ΅Π½:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

Deployment для ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π° Ρ‚ΠΎΠΆΠ΅ ΠΎΡ‡Π΅Π½ΡŒ прост:

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, ΠΌΡ‹ смогли ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ своСго собствСнного ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π° для Kubernetes Π½Π° Python. ΠšΠΎΠ½Π΅Ρ‡Π½ΠΎ, Π΅ΠΌΡƒ Π΅Ρ‰Ρ‘ Π΅ΡΡ‚ΡŒ ΠΊΡƒΠ΄Π° расти: Π² Π±ΡƒΠ΄ΡƒΡ‰Π΅ΠΌ ΠΎΠ½ смоТСт ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ нСсколько ΠΏΡ€Π°Π²ΠΈΠ», Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ Π² нСсколько ΠΏΠΎΡ‚ΠΎΠΊΠΎΠ², ΡΠ°ΠΌΠΎΡΡ‚ΠΎΡΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΡ‚ΡŒ измСнСния своих CRD…

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΌΠΎΠΆΠ½ΠΎ Π±Ρ‹Π»ΠΎ ΠΏΠΎΠ±Π»ΠΈΠΆΠ΅ ΠΏΠΎΠ·Π½Π°ΠΊΠΎΠΌΠΈΡ‚ΡŒΡΡ с ΠΊΠΎΠ΄ΠΎΠΌ, ΠΌΡ‹ слоТили Π΅Π³ΠΎ Π² ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΉ Ρ€Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ. Если хочСтся ΠΏΡ€ΠΈΠΌΠ΅Ρ€ΠΎΠ² Π±ΠΎΠ»Π΅Π΅ ΡΠ΅Ρ€ΡŒΠ΅Π·Π½Ρ‹Ρ… ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ΠΎΠ², Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π½Ρ‹Ρ… ΠΏΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ Python, ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΎΠ±Ρ€Π°Ρ‚ΠΈΡ‚ΡŒ своё Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅ Π½Π° Π΄Π²Π° ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π° для развёртывания mongodb (ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈ Π²Ρ‚ΠΎΡ€ΠΎΠΉ).

P.S. А Ссли Π²Π°ΠΌ лСнь Ρ€Π°Π·Π±ΠΈΡ€Π°Ρ‚ΡŒΡΡ с событиями Kubernetes ΠΈΠ»ΠΈ ΠΆΠ΅ Π²Π°ΠΌ попросту ΠΏΡ€ΠΈΠ²Ρ‹Ρ‡Π½Π΅Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Bash β€” наши ΠΊΠΎΠ»Π»Π΅Π³ΠΈ ΠΏΡ€ΠΈΠ³ΠΎΡ‚ΠΎΠ²ΠΈΠ»ΠΈ Π³ΠΎΡ‚ΠΎΠ²ΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ Π² Π²ΠΈΠ΄Π΅ shell-operator (ΠΌΡ‹ анонсировали Π΅Π³ΠΎ Π² Π°ΠΏΡ€Π΅Π»Π΅).

P.P.S.

Π§ΠΈΡ‚Π°ΠΉΡ‚Π΅ Ρ‚Π°ΠΊΠΆΠ΅ Π² нашСм Π±Π»ΠΎΠ³Π΅:

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com

Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ