ProHoster > Блог > администрация > Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)
Отивам? Баш! Запознайте се с оператора на черупката (ревю и видео отчет от KubeCon EU'2020)
Тази година основната европейска конференция на Kubernetes - KubeCon + CloudNativeCon Europe 2020 - беше виртуална. Подобна промяна във формата обаче не ни попречи да представим нашия дълго планиран доклад „Go? Баш! Запознайте се с оператора на Shell”, посветен на нашия проект с отворен код shell-оператор.
Тази статия, вдъхновена от разговора, представя подход за опростяване на процеса на създаване на оператори за Kubernetes и показва как можете да направите свой собствен с минимални усилия, като използвате shell-operator.
Представяне видео на репортажа (~23 минути на английски, значително по-информативен от статията) и основният откъс от нея в текстов вид. Отивам!
Ние във Flant постоянно оптимизираме и автоматизираме всичко. Днес ще говорим за друга вълнуваща концепция. Среща: облачно родно скриптово обвивка!
Нека обаче започнем с контекста, в който се случва всичко това: Kubernetes.
Kubernetes API и контролери
API в Kubernetes може да бъде представен като един вид файлов сървър с директории за всеки тип обект. Обектите (ресурсите) на този сървър са представени от YAML файлове. Освен това сървърът има основен API, който ви позволява да правите три неща:
получавам ресурс по неговия вид и име;
промяна ресурс (в този случай сървърът съхранява само „правилни“ обекти - всички неправилно формирани или предназначени за други директории се изхвърлят);
писта за ресурса (в този случай потребителят веднага получава неговата текуща/актуализирана версия).
Така Kubernetes действа като един вид файлов сървър (за YAML манифести) с три основни метода (да, всъщност има и други, но засега ще ги пропуснем).
Проблемът е, че сървърът може да съхранява само информация. За да работи, трябва регулатор - втората най-важна и фундаментална концепция в света на Kubernetes.
Има два основни типа контролери. Първият взема информация от Kubernetes, обработва я според вложената логика и я връща на K8s. Вторият взема информация от Kubernetes, но за разлика от първия вид променя състоянието на някои външни ресурси.
Нека разгледаме по-подробно процеса на създаване на разполагане в Kubernetes:
Контролер за разполагане (включен в kube-controller-manager) получава информация за разполагането и създава ReplicaSet.
ReplicaSet създава две реплики (два пакета) въз основа на тази информация, но тези пакети все още не са планирани.
Планировчикът планира модули и добавя информация за възли към техните YAML.
Kubelets прави промени във външен ресурс (да речем Docker).
След това цялата тази последователност се повтаря в обратен ред: kubelet проверява контейнерите, изчислява статуса на pod и го изпраща обратно. Контролерът ReplicaSet получава статуса и актуализира състоянието на набора реплики. Същото се случва с Deployment Controller и потребителят най-накрая получава актуализирания (текущ) статус.
Shell-оператор
Оказва се, че Kubernetes се основава на съвместната работа на различни контролери (операторите на Kubernetes също са контролери). Възниква въпросът как да създадете свой собствен оператор с минимални усилия? И тук на помощ идва разработеният от нас shell-оператор. Той позволява на системните администратори да създават свои собствени отчети, като използват познати методи.
Прост пример: копиране на тайни
Нека да разгледаме един прост пример.
Да кажем, че имаме клъстер Kubernetes. Има пространство от имена default с някаква тайна mysecret. Освен това в клъстера има други пространства от имена. Някои от тях имат специфичен етикет. Нашата цел е да копираме Secret в пространства от имена с етикет.
Задачата се усложнява от факта, че в клъстера могат да се появят нови пространства от имена и някои от тях могат да имат този етикет. От друга страна, когато етикетът бъде изтрит, Secret също трябва да бъде изтрит. В допълнение към това, самият Secret може също да се промени: в този случай новият Secret трябва да бъде копиран във всички пространства от имена с етикети. Ако Secret случайно бъде изтрит в някое пространство от имена, нашият оператор трябва да го възстанови незабавно.
Сега, когато задачата е формулирана, е време да започнем да я изпълняваме с помощта на shell-оператора. Но първо си струва да кажем няколко думи за самия оператор на черупката.
Как работи shell-operator
Подобно на други работни натоварвания в Kubernetes, shell-operator работи в своя собствена група. В тази капсула в директорията /hooks се съхраняват изпълними файлове. Това могат да бъдат скриптове в Bash, Python, Ruby и др. Ние наричаме такива изпълними файлове кукички (куки).
Операторът на Shell се абонира за събития на Kubernetes и изпълнява тези кукички в отговор на онези събития, от които се нуждаем.
Как операторът на обвивката знае коя кука да стартира и кога? Въпросът е, че всяка кука има два етапа. По време на стартиране операторът на обвивката изпълнява всички кукички с аргумент --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:
Както можете да видите, в конфигурацията се появи ново поле с името jqFilter. Както подсказва името му, jqFilter филтрира цялата ненужна информация и създава нов JSON обект с полетата, които ни интересуват. Кука с подобна конфигурация ще получи следния контекст на обвързване:
Съдържа масив filterResults за всяко пространство от имена в клъстера. Булева променлива hasLabel показва дали етикет е прикачен към дадено пространство от имена. Селектор keepFullObjectsInMemory: false показва, че няма нужда да се съхраняват цели обекти в паметта.
Проследяване на тайни цели
Ние се абонираме за всички Тайни, които имат посочена анотация managed-secret: "yes" (това са нашата цел dst_secrets):
В случая jqFilter филтрира цялата информация с изключение на пространството от имена и параметъра resourceVersion. Последният параметър беше предаден на анотацията при създаването на тайната: тя ви позволява да сравнявате версии на тайни и да ги поддържате актуални.
Кука, конфигурирана по този начин, когато бъде изпълнена, ще получи трите обвързващи контекста, описани по-горе. Те могат да се разглеждат като вид моментна снимка (моментална снимка) клъстер.
Въз основа на цялата тази информация може да се разработи основен алгоритъм. Той итерира всички пространства от имена и:
ако hasLabel въпроси true за текущото пространство от имена:
сравнява глобалната тайна с локалната:
ако са еднакви, не прави нищо;
ако се различават - изпълнява kubectl replace или create;
ако hasLabel въпроси false за текущото пространство от имена:
гарантира, че Secret не е в даденото пространство от имена:
ако локалната тайна е налице, изтрийте я с помощта на kubectl delete;
ако локалната тайна не бъде открита, тя не прави нищо.
Ето как успяхме да създадем прост Kubernetes контролер, използвайки 35 реда YAML конфигурация и приблизително същото количество Bash код! Работата на shell-оператора е да ги свърже заедно.
Копирането на тайни обаче не е единствената област на приложение на помощната програма. Ето още няколко примера, за да покаже на какво е способен.
Пример 1: Правене на промени в ConfigMap
Нека да разгледаме Разгръщане, състоящо се от три капсули. Pods използват ConfigMap за съхраняване на някои конфигурации. Когато подовете бяха стартирани, ConfigMap беше в определено състояние (нека го наречем v.1). Съответно всички подове използват тази конкретна версия на ConfigMap.
Сега нека приемем, че ConfigMap се е променила (v.2). Подовете обаче ще използват предишната версия на ConfigMap (v.1):
Как мога да ги накарам да преминат към новата ConfigMap (v.2)? Отговорът е прост: използвайте шаблон. Нека добавим анотация за контролна сума към секцията template Конфигурации за внедряване:
В резултат на това тази контролна сума ще бъде регистрирана във всички модули и ще бъде същата като тази на Разгръщане. Сега просто трябва да актуализирате анотацията, когато ConfigMap се промени. И shell-операторът е полезен в този случай. Всичко, което трябва да направите, е да програмирате кука, която ще се абонира за ConfigMap и ще актуализира контролната сума.
Ако потребителят направи промени в ConfigMap, операторът на обвивката ще ги забележи и ще преизчисли контролната сума. След което магията на Kubernetes ще влезе в действие: оркестраторът ще убие pod, ще създаде нов, ще изчака да стане Ready, и преминава към следващия. В резултат на това Разгръщането ще се синхронизира и ще премине към новата версия на ConfigMap.
Пример 2: Работа с персонализирани дефиниции на ресурси
Както знаете, Kubernetes ви позволява да създавате потребителски типове обекти. Например, можете да създадете вид MysqlDatabase. Да приемем, че този тип има два параметъра на метаданни: name и namespace.
apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
name: foo
namespace: bar
Имаме клъстер Kubernetes с различни пространства от имена, в които можем да създаваме MySQL бази данни. В този случай shell-operator може да се използва за проследяване на ресурси MysqlDatabase, като ги свързва към MySQL сървъра и синхронизира желаните и наблюдаваните състояния на клъстера.
Пример 3: Мониторинг на клъстерна мрежа
Както знаете, използването на ping е най-лесният начин за наблюдение на мрежа. В този пример ще покажем как да реализираме такъв мониторинг с помощта на shell-operator.
На първо място, ще трябва да се абонирате за възли. Операторът на обвивката се нуждае от името и IP адреса на всеки възел. С тяхна помощ той ще пингва тези възли.
Параметър 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 в конфигурацията на обвързване.
Можете да създадете произволен брой опашки/куки и техните различни комбинации. Например една опашка може да работи с две куки или обратното.
Всичко, което трябва да направите, е да конфигурирате съответното поле queue в конфигурацията на обвързване. Ако име на опашка не е указано, куката се изпълнява на опашката по подразбиране (default). Този механизъм за опашка ви позволява напълно да разрешите всички проблеми с управлението на ресурсите, когато работите с куки.
Заключение
Обяснихме какво е shell-operator, показахме как може да се използва за бързо и безпроблемно създаване на Kubernetes оператори и дадохме няколко примера за използването му.
Подробна информация за shell-оператора, както и бърз урок как да го използвате, можете да намерите в съответния хранилища в GitHub. Не се колебайте да се свържете с нас с въпроси: можете да ги обсъдите в специален Група Telegram (на руски) или в този форум (на английски).
И ако ви е харесало, винаги се радваме да видим нови издания/PR/звезди в GitHub, където между другото можете да намерите други интересни проекти. Сред тях си струва да се подчертае addon-оператор, който е големият брат на shell-operator. Тази помощна програма използва Helm диаграми за инсталиране на добавки, може да доставя актуализации и да наблюдава различни параметри/стойности на диаграми, контролира процеса на инсталиране на диаграми и може също да ги променя в отговор на събития в клъстера.