Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Тази година основната европейска конференция на Kubernetes - KubeCon + CloudNativeCon Europe 2020 - беше виртуална. Подобна промяна във формата обаче не ни попречи да представим нашия дълго планиран доклад „Go? Баш! Запознайте се с оператора на Shell”, посветен на нашия проект с отворен код shell-оператор.

Тази статия, вдъхновена от разговора, представя подход за опростяване на процеса на създаване на оператори за Kubernetes и показва как можете да направите свой собствен с минимални усилия, като използвате shell-operator.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Представяне видео на репортажа (~23 минути на английски, значително по-информативен от статията) и основният откъс от нея в текстов вид. Отивам!

Ние във Flant постоянно оптимизираме и автоматизираме всичко. Днес ще говорим за друга вълнуваща концепция. Среща: облачно родно скриптово обвивка!

Нека обаче започнем с контекста, в който се случва всичко това: Kubernetes.

Kubernetes API и контролери

API в Kubernetes може да бъде представен като един вид файлов сървър с директории за всеки тип обект. Обектите (ресурсите) на този сървър са представени от YAML файлове. Освен това сървърът има основен API, който ви позволява да правите три неща:

  • получавам ресурс по неговия вид и име;
  • промяна ресурс (в този случай сървърът съхранява само „правилни“ обекти - всички неправилно формирани или предназначени за други директории се изхвърлят);
  • писта за ресурса (в този случай потребителят веднага получава неговата текуща/актуализирана версия).

Така Kubernetes действа като един вид файлов сървър (за YAML манифести) с три основни метода (да, всъщност има и други, но засега ще ги пропуснем).

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Проблемът е, че сървърът може да съхранява само информация. За да работи, трябва регулатор - втората най-важна и фундаментална концепция в света на Kubernetes.

Има два основни типа контролери. Първият взема информация от Kubernetes, обработва я според вложената логика и я връща на K8s. Вторият взема информация от Kubernetes, но за разлика от първия вид променя състоянието на някои външни ресурси.

Нека разгледаме по-подробно процеса на създаване на разполагане в Kubernetes:

  • Контролер за разполагане (включен в kube-controller-manager) получава информация за разполагането и създава ReplicaSet.
  • ReplicaSet създава две реплики (два пакета) въз основа на тази информация, но тези пакети все още не са планирани.
  • Планировчикът планира модули и добавя информация за възли към техните YAML.
  • Kubelets прави промени във външен ресурс (да речем Docker).

След това цялата тази последователност се повтаря в обратен ред: kubelet проверява контейнерите, изчислява статуса на pod и го изпраща обратно. Контролерът ReplicaSet получава статуса и актуализира състоянието на набора реплики. Същото се случва с Deployment Controller и потребителят най-накрая получава актуализирания (текущ) статус.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Shell-оператор

Оказва се, че Kubernetes се основава на съвместната работа на различни контролери (операторите на Kubernetes също са контролери). Възниква въпросът как да създадете свой собствен оператор с минимални усилия? И тук на помощ идва разработеният от нас shell-оператор. Той позволява на системните администратори да създават свои собствени отчети, като използват познати методи.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Прост пример: копиране на тайни

Нека да разгледаме един прост пример.

Да кажем, че имаме клъстер Kubernetes. Има пространство от имена default с някаква тайна mysecret. Освен това в клъстера има други пространства от имена. Някои от тях имат специфичен етикет. Нашата цел е да копираме Secret в пространства от имена с етикет.

Задачата се усложнява от факта, че в клъстера могат да се появят нови пространства от имена и някои от тях могат да имат този етикет. От друга страна, когато етикетът бъде изтрит, Secret също трябва да бъде изтрит. В допълнение към това, самият Secret може също да се промени: в този случай новият Secret трябва да бъде копиран във всички пространства от имена с етикети. Ако Secret случайно бъде изтрит в някое пространство от имена, нашият оператор трябва да го възстанови незабавно.

Сега, когато задачата е формулирана, е време да започнем да я изпълняваме с помощта на shell-оператора. Но първо си струва да кажем няколко думи за самия оператор на черупката.

Как работи shell-operator

Подобно на други работни натоварвания в Kubernetes, shell-operator работи в своя собствена група. В тази капсула в директорията /hooks се съхраняват изпълними файлове. Това могат да бъдат скриптове в Bash, Python, Ruby и др. Ние наричаме такива изпълними файлове кукички (куки).

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Операторът на Shell се абонира за събития на Kubernetes и изпълнява тези кукички в отговор на онези събития, от които се нуждаем.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Как операторът на обвивката знае коя кука да стартира и кога? Въпросът е, че всяка кука има два етапа. По време на стартиране операторът на обвивката изпълнява всички кукички с аргумент --config Това е етапът на конфигуриране. И след него се стартират кукички по нормалния начин - в отговор на събитията, към които са прикачени. В последния случай куката получава обвързващия контекст (обвързващ контекст) - данни във формат JSON, за които ще говорим по-подробно по-долу.

Създаване на оператор в Bash

Сега сме готови за изпълнение. За да направим това, трябва да напишем две функции (между другото, препоръчваме библиотеката shell_lib, което значително опростява писането на куки в Bash):

  • първият е необходим за етапа на конфигуриране - той показва контекста на обвързване;
  • втората съдържа основната логика на куката.

#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Следващата стъпка е да решим какви обекти са ни необходими. В нашия случай трябва да проследим:

  • секретен източник за промени;
  • всички пространства от имена в клъстера, така че да знаете кои имат етикет, прикрепен към тях;
  • целеви тайни, за да се гарантира, че всички те са в синхрон с изходната тайна.

Абонирайте се за тайния източник

Конфигурацията на обвързване за него е доста проста. Посочваме, че се интересуваме от Secret с името mysecret в пространството на имената default:

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF

В резултат на това куката ще се задейства, когато тайната на източника се промени (src_secret) и получават следния обвързващ контекст:

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Както можете да видите, той съдържа името и целия обект.

Проследяване на пространства от имена

Сега трябва да се абонирате за пространства от имена. За да направим това, ние задаваме следната конфигурация на обвързване:

- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false

Както можете да видите, в конфигурацията се появи ново поле с името jqFilter. Както подсказва името му, jqFilter филтрира цялата ненужна информация и създава нов JSON обект с полетата, които ни интересуват. Кука с подобна конфигурация ще получи следния контекст на обвързване:

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Съдържа масив filterResults за всяко пространство от имена в клъстера. Булева променлива hasLabel показва дали етикет е прикачен към дадено пространство от имена. Селектор keepFullObjectsInMemory: false показва, че няма нужда да се съхраняват цели обекти в паметта.

Проследяване на тайни цели

Ние се абонираме за всички Тайни, които имат посочена анотация managed-secret: "yes" (това са нашата цел dst_secrets):

- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false

В случая jqFilter филтрира цялата информация с изключение на пространството от имена и параметъра resourceVersion. Последният параметър беше предаден на анотацията при създаването на тайната: тя ви позволява да сравнявате версии на тайни и да ги поддържате актуални.

Кука, конфигурирана по този начин, когато бъде изпълнена, ще получи трите обвързващи контекста, описани по-горе. Те могат да се разглеждат като вид моментна снимка (моментална снимка) клъстер.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Въз основа на цялата тази информация може да се разработи основен алгоритъм. Той итерира всички пространства от имена и:

  • ако hasLabel въпроси true за текущото пространство от имена:
    • сравнява глобалната тайна с локалната:
      • ако са еднакви, не прави нищо;
      • ако се различават - изпълнява kubectl replace или create;
  • ако hasLabel въпроси false за текущото пространство от имена:
    • гарантира, че Secret не е в даденото пространство от имена:
      • ако локалната тайна е налице, изтрийте я с помощта на kubectl delete;
      • ако локалната тайна не бъде открита, тя не прави нищо.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Реализация на алгоритъма в Bash можете да изтеглите в нашия хранилища с примери.

Ето как успяхме да създадем прост Kubernetes контролер, използвайки 35 реда YAML конфигурация и приблизително същото количество Bash код! Работата на shell-оператора е да ги свърже заедно.

Копирането на тайни обаче не е единствената област на приложение на помощната програма. Ето още няколко примера, за да покаже на какво е способен.

Пример 1: Правене на промени в ConfigMap

Нека да разгледаме Разгръщане, състоящо се от три капсули. Pods използват ConfigMap за съхраняване на някои конфигурации. Когато подовете бяха стартирани, ConfigMap беше в определено състояние (нека го наречем v.1). Съответно всички подове използват тази конкретна версия на ConfigMap.

Сега нека приемем, че ConfigMap се е променила (v.2). Подовете обаче ще използват предишната версия на ConfigMap (v.1):

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Как мога да ги накарам да преминат към новата ConfigMap (v.2)? Отговорът е прост: използвайте шаблон. Нека добавим анотация за контролна сума към секцията template Конфигурации за внедряване:

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

В резултат на това тази контролна сума ще бъде регистрирана във всички модули и ще бъде същата като тази на Разгръщане. Сега просто трябва да актуализирате анотацията, когато ConfigMap се промени. И shell-операторът е полезен в този случай. Всичко, което трябва да направите, е да програмирате кука, която ще се абонира за ConfigMap и ще актуализира контролната сума.

Ако потребителят направи промени в ConfigMap, операторът на обвивката ще ги забележи и ще преизчисли контролната сума. След което магията на Kubernetes ще влезе в действие: оркестраторът ще убие pod, ще създаде нов, ще изчака да стане Ready, и преминава към следващия. В резултат на това Разгръщането ще се синхронизира и ще премине към новата версия на ConfigMap.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Пример 2: Работа с персонализирани дефиниции на ресурси

Както знаете, Kubernetes ви позволява да създавате потребителски типове обекти. Например, можете да създадете вид MysqlDatabase. Да приемем, че този тип има два параметъра на метаданни: name и namespace.

apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar

Имаме клъстер Kubernetes с различни пространства от имена, в които можем да създаваме MySQL бази данни. В този случай shell-operator може да се използва за проследяване на ресурси MysqlDatabase, като ги свързва към MySQL сървъра и синхронизира желаните и наблюдаваните състояния на клъстера.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Пример 3: Мониторинг на клъстерна мрежа

Както знаете, използването на ping е най-лесният начин за наблюдение на мрежа. В този пример ще покажем как да реализираме такъв мониторинг с помощта на shell-operator.

На първо място, ще трябва да се абонирате за възли. Операторът на обвивката се нуждае от името и IP адреса на всеки възел. С тяхна помощ той ще пингва тези възли.

configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"

Параметър executeHookOnEvent: [] предотвратява изпълнението на куката в отговор на всяко събитие (т.е. в отговор на промяна, добавяне, изтриване на възли). Въпреки това той ще тече (и актуализиране на списъка с възли) Планиран - всяка минута, както е предписано от полето schedule.

Сега възниква въпросът как точно да знаем за проблеми като загуба на пакети? Нека да разгледаме кода:

function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}

Преглеждаме списъка с възли, получаваме имената и IP адресите им, пингваме ги и изпращаме резултатите на Prometheus. Shell-операторът може да експортира показатели към Prometheus, запазвайки ги във файл, разположен според пътя, посочен в променливата на средата $METRICS_PATH.

Ето го можете да направите оператор за просто наблюдение на мрежата в клъстер.

Механизъм за опашка

Тази статия би била непълна, без да опише друг важен механизъм, вграден в shell-оператора. Представете си, че той изпълнява някакъв вид кука в отговор на събитие в клъстера.

  • Какво се случва, ако в същото време нещо се случи в клъстера? още едно събитие?
  • Шел-операторът ще изпълни ли друго копие на куката?
  • Ами ако, да речем, пет събития се случат в клъстера наведнъж?
  • Шел-операторът ще ги обработва ли паралелно?
  • Какво ще кажете за консумираните ресурси като памет и процесор?

За щастие shell-operator има вграден механизъм за опашка. Всички събития се поставят в опашка и се обработват последователно.

Нека илюстрираме това с примери. Да кажем, че имаме две куки. Първото събитие отива към първата кука. След като обработката му приключи, опашката се придвижва напред. Следващите три събития се пренасочват към втората кука - те се премахват от опашката и се въвеждат в нея в „пакет“. Това е hook получава масив от събития - или, по-точно, набор от обвързващи контексти.

Също и тези събития могат да бъдат комбинирани в едно голямо. Параметърът е отговорен за това group в конфигурацията на обвързване.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Можете да създадете произволен брой опашки/куки и техните различни комбинации. Например една опашка може да работи с две куки или обратното.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Всичко, което трябва да направите, е да конфигурирате съответното поле queue в конфигурацията на обвързване. Ако име на опашка не е указано, куката се изпълнява на опашката по подразбиране (default). Този механизъм за опашка ви позволява напълно да разрешите всички проблеми с управлението на ресурсите, когато работите с куки.

Заключение

Обяснихме какво е shell-operator, показахме как може да се използва за бързо и безпроблемно създаване на Kubernetes оператори и дадохме няколко примера за използването му.

Подробна информация за shell-оператора, както и бърз урок как да го използвате, можете да намерите в съответния хранилища в GitHub. Не се колебайте да се свържете с нас с въпроси: можете да ги обсъдите в специален Група Telegram (на руски) или в този форум (на английски).

И ако ви е харесало, винаги се радваме да видим нови издания/PR/звезди в GitHub, където между другото можете да намерите други интересни проекти. Сред тях си струва да се подчертае addon-оператор, който е големият брат на shell-operator. Тази помощна програма използва Helm диаграми за инсталиране на добавки, може да доставя актуализации и да наблюдава различни параметри/стойности на диаграми, контролира процеса на инсталиране на диаграми и може също да ги променя в отговор на събития в клъстера.

Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)

Видеоклипове и слайдове

Видео от представлението (~23 минути):


Представяне на доклада:

PS

Прочетете също в нашия блог:

Източник: www.habr.com

Добавяне на нов коментар