Игорь Тиунов

SRE в Yandex и автор курса «DevOps для эксплуатации и разработки».

Интро

Когда мы собрались писать статью про Kubernetes, у нас была куча идей для мемов, картинок и интересных историй из жизни. Стоило только заварить эфиопских зёрен, достать пару куросанчиков (пасхалка раскроется на курсе). Всё перевернулось с ног на голову. И знаете почему?

Какими бы интересными ни были истории из жизни, статья про Kubernetes всё равно превращается в «очередную статью про Kubernetes». Вот вам история Kubernetes, вот его архитектура, а вот манифесты и пример приложения. И так каждый день: приложения, манифесты, релизы. Снова релизы, откаты релизов.

Даже работая YAML-программистом и выкатывая по 10 раз на дню в Kubernetes очередной релиз, задумываешься об однообразии процессов. Не хочется растягивать резину и петь дифирамбы kubectl, а хочется, чтобы за твоим приложением в Kubernetes следила автоматика: выкатывала новые версии, восстанавливала после сбоя и перезапускала, если закончилась память. Поэтому рано или поздно на свет должны были появиться операторы Kubernetes!

Operator Pattern

Как известно, лень — это двигатель прогресса. Когда инженеры начали работать с Kubernetes, они быстро нашли себе замену — Kubernetes Operators, чтобы иметь время на Disco Elysium и разговоры о том, что Java уже в прошлом, Go ведёт нас в будущее, а JavaScript считает 2 + 2 = 22.

Слово «Operator» здесь не случайно, цель оператора — автоматизировать рутину и заменить собой человека, то бишь оператора Kubernetes. 

Хотя сам Kubernetes, конечно же, создавался для автоматизации рутины: из коробки в кластере у вас есть полный набор инструментов для развёртывания приложений и контроля их работоспособности. В сердце Kubernetes работают контроллеры, отслеживающие состояние подопечных ресурсов.

Рис 1. Kubernetes-контроллер отслеживает состояние подопечных ресурсов
Рис 1. Kubernetes-контроллер отслеживает состояние подопечных ресурсов

Вот пример детального описания типового оператора:

1. В кластере присутствует Custom Resource (назовём его MyDB), который вы можете использовать для запуска базы данных в Kubernetes.

2. Здесь же в кластере работает Deployment, который запускает поды контроллера оператора.

3. Код контроллера периодически опрашивает Control Plane кластера Kubernetes на предмет настроенных MyDB:

  • если вы создали новый MyDB, оператор запускает процесс запуска базы данных: создаёт StatefulSet, подключает диски с помощью PersistentVolumeClaims, создаёт Service для подключения к БД;

  • если вы удаляете MyDB, оператор может сделать бэкап ваших данных и удалить StatefulSet и PersistentVolumes.

4. Оператор может выполнять вспомогательные операции, например обновление версии БД. При изменении конфигурации ресурса MyDB код контроллера оператора может выполнить аккуратное обновление подов запущенного StatefulSet для базы данных.

Примеры операторов

Работу операторов можно сравнить с парадигмой SaaS (software as a service): используя привычный Kubernetes API, вы можете запускать готовые к использованию кластеры баз данных, брокеров сообщений или CI/CD системы.

Рис 2. Операторы для запуска GitLab в Kubernetes
Рис 2. Операторы для запуска GitLab в Kubernetes

Для всех популярных технологий, таких как Redis, Kafka, PostgreSQL, уже созданы операторы. В репозитории https://operatorhub.io можно найти качественные решения для запуска сервисов, готовых к промышленной эксплуатации.

Простейший оператор выполняет развёртывание приложения при создании ресурса, а в более сложных сценариях берёт на себя функциональность мониторинга, бэкапов и автоматического обновления сервисов. Шестой разряд сварщика — полный автопилот, когда контроллер оператора следит за состоянием сервиса, автоматически поправляет конфигурацию и количество реплик приложения в зависимости от нагрузки, а ещё следит за бесшовными обновлениями.

Рис 3. Уровни возможности операторов
Рис 3. Уровни возможности операторов
Реальная история № 1

В больших компаниях постоянно ведутся разговоры на тему возможности или невозможности использования технологии в такой динамичной среде, как Kubernetes. Типичной жертвой становятся базы данных: большая сложная система со своими порядками и бородатыми админами со своими привычками. Когда вы с горящими глазами приходите к базданщикам и говорите: «А давайте это всё запустим в Kubernetes?!» — у них портится настроение.

Базданщики в целом не любят автоматизацию, она ломает привычную автоматику «пяти пальцев»: раньше админ мог за пару минут поднять инстанс на голом сервере, а за остальные три –– настроить репликацию. А теперь админу предлагают изучать новый птичий язык для работы с модной технологией: это занимает часы и даже дни (если тот плохо знаком с технологией), а процесс дебага усложняется в разы.

Компания Google начала разработку оператора для управления базами данных Oracle. Оператор был выпущен в open-source под названием El Carro. El Carro (с исп. «автомобиль» — догадайтесь, почему оператор для Oracle называется именно так) позволяет запускать инстансы Oracle RDBMS, создавать резервные копии и выполнять импорт дампов, созданных с помощью Oracle Data Pump.

Разработка оператора

Наверняка вам захотелось создать собственный Kubernetes Operator? Да точно захотелось! Для этого вам понадобятся:

  • минимальный опыт программирования на любом языке;

  • установленный Minikube;

  • лютая жажда познания нового и интересного.

Настройка окружения

Для старта нужен работающий Kubernetes-кластер, и Minikube — самый простой способ его получить.

Установите Minikube для вашей операционной системы по инструкции и проверьте работу:

$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

Установка Go

Код контроллера оператора может быть написан на любом языке программирования, но мы рассмотрим примеры на популярном сейчас Go. Установите версию 1.19 для вашей операционной системы.

Не переживайте, вам не придётся писать много кода для этого простого примера. Основной шаблон для вас создаст помощник — Operator SDK, набор инструментов для разработки операторов.  

Установка Operator SDK

В процессе разработки лучше сосредоточиться на описании бизнес-логики контроллера (как запускать, обновлять и масштабировать ваш сервис), а подготовку логики взаимодействия с Kubernetes API доверить Operator SDK. Вместе с набором вспомогательных скриптов SDK предоставляет утилиту operator-sdk, которая создаст файловую структуру проекта оператора, код прослойки между вашим контроллером и Kubernetes API и примеры манифестов для Kubernetes.

Установите последнюю версию для вашей операционной системы по инструкции.

Реальная история № 2

Про OpenShift

Компания RedHat построила свою систему OpenShift на операторах; там операторы –– это стероиды для Kubernetes.

До версии OpenShift 4 в качестве инсталлятора платформы использовали Ansible. Сначала готовили инфраструктуру: запускали серверы, регистрировали их в DNS и дальше по списку. А затем запускали плейбук установки. Во время работы плейбука установки, конечно же, приходилось налаживать отношения с техподдержкой.

В таком процессе было несколько сложностей: дополнительная работа по запуску и корректной настройке инфраструктуры для админов (а админы, как известно, не любят дополнительной работы) и дополнительная головная боль (которую вообще никто не любит) при установке обновлений: апдейты операционных систем на хостах могли конфликтовать с версиями OpenShift. Эти сложности существовали не только по части OpenShift — они были актуальны для всего сообщества Kubernetes.

Процесс установки кластера, обновление, мониторинг, масштабирование и взаимодействие с облаком в OpenShift 4 выведены на новый уровень за счёт операторов: человеку нужно приложить минимум усилий для решения админских задач. Ребята из RedHat даже придумали название для такой платформы — «A NoOps Platform».

За примером далеко ходить не нужно: всё управление рабочими нодами кластера выполняется с помощью Machine Config Operator. MCO воспринимает операционные системы на хостах как «ещё один объект в Kubernetes»: управление состоянием и конфигурацией выполняется с помощью таких же манифестов, что и запуск деплойментов и подов приложений.

Запуск оператора

Для примера запустим очень простой оператор, который... ничего не делает. SDK поможет нам создать образ контейнера с кодом контроллера оператора, создать Custom Resource Definition для расширения Kubernetes API и запустить деплоймент контроллера оператора. После чего попробуем создать Custom Resource и проверим логи контроллера оператора. 

Генерация кода

Утилита operator-sdk позволяет создать шаблон проекта для начала разработки кода контроллера оператора. Команда init инициализирует проект:

mkdir kubernetes-operator && cd kubernetes-operator
operator-sdk init --domain itd27m01.com --repo github.com/itd27m01/kubernetes-operator

Параметр --domain задаёт доменное имя для API-группы в Kubernetes API, в рамках которого будут создаваться ваши Custom Resources. API-группы –– это механизм для логической разбивки монолитного API.

Важно: Если вы используете macOS на M1, добавьте плагин `--plugins=go/v4-alpha` для поддержки этой платформы.

API и CRD

Теперь в кластере Kubernetes нужно создать Custom Resource Definition (CRD). Это расширения для Control Plane, за которыми будет следить контроллер оператора.

Первой командой создадим файлы с Go-кодом для описания CRD и шаблона кода контроллера:

operator-sdk create api --group example --version v1alpha1 --kind MyCRD --resource --controller

В файле controllers/mycrd_controller.go вы найдёте функцию Reconcile, в которой будет код логики вашего контроллера:

func (r *MyCRDReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = log.FromContext(ctx)

	// TODO(user): your logic here

	return ctrl.Result{}, nil
}

На этом этапе для разработки полноценного оператора не мешало бы добавить код логики вашего контроллера, но для простоты добавим код логирования того момента, когда ресурс появляется в кластере:

func (r *MyCRDReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	// Воспользуемся логгером для создания сообщения
	log := log.FromContext(ctx)

	// В этом блоке кода контроллер сделает запрос на получение информации о ресурсах MyCRD 
	example := &examplev1alpha1.MyCRD{}
	err := r.Get(ctx, req.NamespacedName, example)
	if err != nil {
		log.Error(err, "Failed to get example resource")
	}

	// Эта строчка должна появиться в логах контроллера
	log.Info(fmt.Sprintf("A new example resource found: %s", example.Name))

	return ctrl.Result{}, nil
}

Далее нужно описать структуру и поля управляемых контроллером ресурсов. Для этого нужно зайти в файл api/v1alpha1/mycrd_types.go. SDK заполнил основную часть шаблона кода и добавил пример для поля в спецификации ресурса:

// MyCRDSpec defines the desired state of MyCRD
type MyCRDSpec struct {
	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
	// Important: Run "make" to regenerate code after modifying this file

	// Foo is an example field of MyCRD. Edit mycrd_types.go to remove/update
	Foo string `json:"foo,omitempty"`

Оставим эту структуру как есть, нам этого достаточно.

Реальная история № 3

Операторы Kubernetes выглядят хорошей перспективой для развития облачных сервисов «Kubernetes As a Service», таких как «Yandex Cloud Managed Service for Kubernetes» или OpenStack Magnum. В последнем процесс развёртывания кластера выполняется буквально bash-скриптами.

В OpenStack Magnum запуск инфраструктуры реализован с помощью сервиса OpenStack Heat, а развёртывание компонентов Kubernetes-кластера выполняется агентом heat-container-agent. Агент запускается при старте сервера, скачивает bash-скрипты установки, формирует из них один большой скрипт и выполняет развёртывание.

Недостатки такого решения очевидны: код развёртывания очень сложно отлаживать, тестировать и, как следствие, адаптировать к реальной инсталляции OpenStack. Операторы могли бы помочь повторить опыт инженеров RedHat в OpenShift: за процесс развёртывания компонентов кластера могут отвечать соответствующие операторы.

Установка

На этом этапе уже можно запустить работающую версию оператора. Правда, его контроллер ничего не будет делать, если вы не добавили код логики.

Соберите образ контроллера и опубликуйте его в Registry (в примере неявно используется docker.io — укажите свой Registry или замените на своё имя пользователя Docker Hub):

make docker-build docker-push IMG='itd27m01/kubernetes-operator:v0.0.1'

И теперь установите оператор в кластер Kubernetes:

make deploy IMG="itd27m01/kubernetes-operator:v0.0.1"

Команда выше запустит деплоймент для контроллера оператора и установит Custom Resource Definition в кластере Kubernetes. Манифест для MyCRD можно посмотреть в файле config/crd/bases/example.itd27m01.com_mycrds.yaml.

Сейчас уже можно попробовать создать Custom Resource:

$ kubectl apply -f config/samples/example_v1alpha1_mycrd.yaml
mycrd.example.itd27m01.com/mycrd-sample created

И проверить логи контроллера:

CONTROLLER_POD_NAME=$(kubectl get pods -n kubernetes-operator-system --no-headers -o custom-columns=":metadata.name")

kubectl logs $CONTROLLER_POD_NAME -n kubernetes-operator-system

# Вывод:
<...>

1.6675597079600263e+09	INFO	A new example resource found: mycrd-sample

<...>

Это та самая строчка в логах: «A new example resource found: mycrd-sample», которую мы добавили в код контроллера для обработки события создания Custom Resource.

Эпилог

Тот, кто дочитал до этого места, почувствует недосказанность. А где же самое мясо? Где код работы с Kubernetes API?

В одной небольшой статье такие детали не расскажешь. Даже при создании нашего курса по DevOps пришлось хорошенько потрудиться, чтобы собрать из Kubernetes самое лучшее для закладки обучающего фундамента.

А сейчас предлагаем обратиться к документации SDK и пройтись по дополнительным материалам.

  • В гайдах Operator SDK можно найти хороший пример оператора для запуска сервиса Memcache

  • Код Reconcile функции контроллера оператора доступен в примерах проекта на GitHub

  • Логику работы функции можно хорошо проследить по её тексту: контроллер создаёт деплоймент с заданным в Custom Resource количеством реплик для Memcached-сервиса. Манифест деплоймента формируется в соответствующем методе deploymentForMemcached.

На самом деле бизнес-логику можно написать с помощью любого языка программирования или фреймворка автоматизации. Один из вариантов — запуск Ansible-плейбука при реакции на создание Custom Resource. Благодаря тому, что в коллекции community.kubernetes есть весь набор модулей для управления объектами Kubernetes, кода придётся написать меньше. Пример такого оператора также есть в документации SDK.

Ключевая мысль

Оператор Kubernetes –– это способ развёртывания и управления приложением в Kubernetes. Как развёртывание, так и управление происходит с помощью знакомого вам Kubernetes API путём добавления Custom Resources и запуска контроллера оператора –– специфичного для приложения кода, который будет присматривать за подами приложения, вовремя обновлять его и выполнять любые другие операции, которые заложит разработчик оператора в код контроллера.

Процесс разработки кода контроллера значительно упрощается за счёт использования фреймворка Operator SDK. Вместе с набором вспомогательных скриптов для сборки образа контейнера с кодом контроллера SDK сгенерирует необходимый код контроллера, а разработчику останется только реализовать бизнес-логику работы с приложением.

Полезные материалы

Комментарии (2)


  1. micbal
    10.11.2022 17:32
    +1

    Автор где-то видел "крутого" DBAшника "бородатого", который не любит учится? А "крутым" он стал нечайно?


  1. zvlb
    11.11.2022 22:26
    +2

    минимальный опыт программирования на любом языке;

    Что-то мне подсказывает, что если DevOps(/SRE/инфраструктурный инженер) дошёл до кондиции, когда понимает, что ему нужен оператор для разворачивания какого-то ПО в кластере - он первым делом пойдёт искать уже готовый оператор с кучей звёзд.

    Если же он не нашёл подходящий ему оператор, то, скорее всего, перед человеком стоит какая-то нетривиальная задача, и чаще всего минимальный опыт в программирований приведёт к очень плохому итоговому продукту.