Фото: Jukan Tateisi | Unsplash
В этой статье мы подробно рассмотрим, как создать собственный оператор Kubernetes с нуля. Операторы — это такие программные расширения, которые используют кастомные ресурсы (kind), чтобы управлять приложениями. Подробности читайте в официальной документации.
Возьмем самый простой пример — приложение HelloApp. Чтобы задеплоить HelloApp, создадим ресурс Kubernetes.
Смотрим на kind: HelloApp — это кастомное определение ресурса (Custom Resource Definition, CRD), которое обрабатывается нашим оператором (или контроллером). Здесь мы с нуля создадим такой оператор (или контроллер).
Миф
Пока я не начал создавать операторы, я думал, что для этого подходит только Go. Это миф.
Реализовать оператор (контроллер) можно на любом языке и в любом рантайме, которые могут быть клиентом для Kubernetes API.
Скриншот из документации по Kubernetes:
По сути Kubernetes — это такая большая система мониторинга. Все функции доступны через API (сервер API). Допустим, мы пишем приложение, которое как клиент обращается к серверу API Kubernetes и выполняет нужные действия. Это и есть оператор. Так что язык здесь не важен. Но раз Go — это нативный язык рантайма Kubernetes, и у него очень много библиотек для реализации операторов, обычно выбирают его.
Софт для этого руководства
Нам понадобятся:
- Go lang (1.16)
- Operator SDK (1.5)
- Kind
- Visual Studio Code с плагином Go
Этапы процесса
Статья разделена на несколько частей:
Часть 1: создание проекта оператора
Часть 2: реализация логики оператора
Часть 3: создание CRD
Часть 4: установка CRD
Часть 5: запуск оператора за пределами кластера
Часть 6: отладка оператора за пределами кластера
Часть 7: запуск оператора в кластере
Часть 1: создание проекта оператора
Чтобы создать структуру проекта, используем Operator SDK. При написании руководства я использовал Operator SDK версии 1.5. Чтобы подробно изучить создание проекта с помощью SDK, читайте официальную документацию. Для краткости я расскажу о создании проекта в двух словах.
$ mkdir demo-operator
$ cd demo-operator
$ operator-sdk init --domain anupam.com --repo
github.com/anupamgogoi/demo-operator
Создание API и контроллера
$ operator-sdk create api --group apps --version v1 --kind HelloApp --resource --controller
С помощью этих команд мы подготовили минимальную структуру проекта для дальнейшей работы.
Примечания о некоторых важных файлах:
- Это Makefile со всеми командами, которые понадобятся, чтобы создать артефакты для оператора. Выполните make help, и увидите все доступные команды.
- Это центральная точка входа для оператора с основной функцией. Кроме того, это центральная точка входа для отладки оператора в локальном кластере.
- Контроллер. Здесь находится основная логика оператора.
- Структура кастомного ресурса.
- Информация о группе и версии, которую мы указали при создании оператора. Подробности о группе и версии читайте в официальной документации.
Часть 2: реализация логики оператора
Логика оператора предельно проста. Когда применяется CRD (ниже), оператор (контроллер) должен создать деплоймент для kind HelloApp с числом подов, которое мы указали в spec.size.
Полный исходный код вы найдете в этом репозитории. В spec у нас два поля: image (образ) и size (размер). Чтобы добавить их в spec, редактируем файл [4] из первой части статьи.
Логику контроллера можно найти здесь. Тут никакой магии. Ничего особенного он не делает — просто проверяет наличие деплоймента для HelloApp и пытается создать его, если он отсутствует. Наконец он проверяет, что число подов соответствует указанному в spec.size, а если нет — создает больше подов. Вот и все. Подробности об API читайте в официальной документации.
Контроллеры реализуют интерфейс Reconciler, который предоставляет метод Reconcile. Функция Reconcile вызывается для таких событий в кластере, как операции CRUD (create read update delete — «Создание чтение обновление удаление», прим. переводчика), и сравнивает фактический статус ресурса (kind) с ожидаемым (spec). Если есть различия, он их исправляет.
main.go
Файл main.go создается Operator-SDK и содержит дополнительный код. Для простоты я удалил все лишнее и оставил только то, что нужно для запуска оператора. Изучите файл main.go.
Вот самая важная часть кода в файле main.go:
ctrl.GetConfigOrDie() попытается считать конфигурацию кластера Kubernetes из файла ~/.kube/config и получит информацию о подключении. Файл ~/.kube/config выглядит примерно так:
Здесь IP сервера, сертификаты и т. д. Это главная часть. Метод GetConfigOrDie() считывает эту информацию, и на ее основе ctrl.NewManager() создает менеджер для контроллера. Остается просто вызвать API с сервера API Kubernetes. Здесь и начинается магия. Просто изучите файл main.go, и все станет ясно.
Даже kubectl CLI использует вызовы API к серверу API Kubernetes. Выполните эту команду и убедитесь сами:
$ kubectl get nodes --v=8
Часть 3: создание CRD
Итак, логика контроллера HelloApp готова. Теперь надо создать для нее CRD. В корне demo-operator выполняем команду:
$ make manifests
Она создает CRD в каталоге
~/demo-operator/config/crd/bases
В каталоге ~/demo-operator/config/samples создается пример.
Часть 4: установка CRD
Чтобы запустить оператор, нужен локальный кластер. Используем kind, чтобы создать кластер на локальном хосте.
$ kind create cluster --name k8s
Есть два способа установить определения CRD в кластере: просто выполняем команду
$ make install
или переходим в ~/demo-operator/config/crd/bases и выполняем команду
$ kubectl apply -f .
Результат будет одинаковым.
Часть 5: запуск оператора за пределами кластера
Это самый простой способ запустить и отладить логику оператора. Чтобы заглянуть внутрь Kubernetes, лучше всего начать с
$ cd demo-operator
$ go run main.go
Вот что мы видим:
Давайте задеплоим наш кастомный ресурс — перейдем в каталог samples и применим ресурс:
$ kubectl create ns test
$ kubectl apply -f apps_v1_helloapp.yaml -n test
Посмотрим, что мы создали в неймспейсе test:
$ kubectl get all -n test
А теперь самое интересное:
$ kubectl get HelloApp -n test
Вот он — наш кастомный ресурс!
Часть 6: отладка оператора за пределами кластера
Здесь начинается самое увлекательное — можно отлаживать оператор построчно, чтобы узнать много интересного.
Для этого, во-первых, CRD должны быть установлены в кластере, как в части 4 (про установку CRD).
Убедитесь, что локальный кластер k8s запущен. При установке плагина Go в Visual Studio Code устанавливается и отладчик. Нажимаем Run → Start Debugging в VS Code, и конфигурация выполняется автоматически.
Ставим точки останова в нужных местах — и пожалуйста:
Открываем терминал, переходим к каталогу ~/demo-operator/config/samples
и деплоим CRD.
$ kubectl apply -f apps_v1_helloapp.yaml -n test
В первый раз запустится цикл Reconcile, и программа будет останавливаться в точках останова в функции Reconcile, как на рисунке выше. Для обновления или удаления тоже будет вызываться функция Reconcile. Поэкспериментируйте, пока не поймете, как это работает.
Часть 7: запуск оператора в кластере
Кастомный оператор — это просто набор файлов конфигурации (YAML) и docker-образ самого оператора. Минимальный набор файлов для кастомного оператора:
- Файл конфигурации для создания неймспейса.
- Файл конфигурации для создания сервисаккаунта.
- Файл конфигурации для определения ролей, которые понадобятся оператору для работы с API Kubernetes.
- Файл конфигурации для привязки ролей к сервисаккаунту из пункта 2.
- Наконец, файл конфигурации для развертывания самого оператора.
Создание ролей для оператора
В папке ~/demo-operator/config/rbac мы видим много файлов конфигурации. Прямо сейчас нам нужны не все. Но какие?
Заглянем в код нашего контроллера.
Вот что мы видим:
- Сейчас мы выполняем операцию Get, чтобы проверить наличие ресурса HelloApp. Значит, нам нужно разрешение на Get для ресурса HelloApp, который входит в группу API apps.anupam.com.
- Точно так же мы делаем Get, чтобы проверить ресурс Deployment. Кстати, ресурс Deployment в Kubernetes входит в группу API apps.
- Мы делаем Update для ресурса Deployment, который входит в группу API apps.
У нас тут есть комментарии, которые начинаются со знака +.
//+kubebuilder:rbac:groups=apps.anupam.com,resources=helloapps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps.anupam.com,resources=helloapps/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=apps.anupam.com,resources=helloapps/finalizers,verbs=update
Это так называемые маркеры. Больше о маркерах Kubernetes можно узнать здесь. С помощью этих маркеров controller-gen CLI создает все артефакты (CRD, RBAC и т. д.).
После добавления маркеров достаточно выполнить команду make manifests. Она создаст или обновит все, что нужно, — CRD, роли, привязки ролей и т. д.
Создание Docker-образа для оператора
Кастомный оператор — это просто docker-образ со всеми своими артефактами для развертывания в кластере Kubernetes.
Если посмотреть структуру проекта, там уже будет Dockerfile, созданный Operator-SDK. Ничего не надо делать вручную.
Перейдем в корень проекта и выполним команду для сборки docker-образа:
$ make docker-build IMG=anupamgogoi/demo-operator:latest
Укажите свой репозиторий docker. После создания образа отправим его в registry следующей командой:
make docker-push IMG=anupamgogoi/demo-operator:latest
Все готово.
Создание готового пакета
Для простоты я вручную создам папку с именем dist в том же проекте и добавлю 5 файлов, о которых говорил в начале раздела.
Создаем 1-Namespace.yaml (укажите любое имя). В этом неймспейсе будет установлен оператор. Файлы 2, 3 и 4 можно скопировать из папки rbac. Копируем файл 5 из папки crd/bases. Наконец, создаем 6-Controller.yaml, чтобы развернуть оператор. Не забудьте изменить неймспейсы в файлах конфигурации. Вот готовые файлы.
Все готово.
Запуск оператора в кластере
У меня есть кластер Kubernetes на три ноды на виртуальной машине CentOS. Подробнее о создании кластера см. здесь. Я просто загружу эти файлы конфигурации на мастер-ноду Kubernetes и разверну оператор. Приступим.
Я скопировал каталог dist на мастер-ноду кластера, а теперь просто сделаю kubectl apply для всех файлов конфигурации.
Неймспейс я назвал demo-operator-system. Проверим, развернут ли в нем оператор.
Отлично. У меня развернут кастомный оператор и CRD. Имя CRD — helloapps.apps.anupam.com, как указано в файле конфигурации 5-apps.anupam.com_helloapps.yaml.
Теперь можно создать кастомный ресурс, или kind, то есть HelloApp. Откроем еще один терминал мастер-ноды для проверки логов кастомного оператора, а в другом терминале задеплоим ресурс HelloApp.
Как видно на скриншоте, после деплоя кастомного ресурса HelloApp (нижний терминал) в верхнем терминале появились логи. Это лучший способ отладки оператора при развертывании в кластере Kubernetes.
Теперь проверим, создан ли деплоймент для кастомного ресурса HelloApp.
$ kubectl get all -n test
Пожалуйста!
Деплоймент на месте, и при этом он создал под в соответствии со спецификациями (size=1).
Давайте получим доступ к приложению из кластера, вызвав его по IP пода.
Вот и все. Мы получили от приложения ответ.
Заключение
В этой статье мы попытались создать очень простой кастомный оператор Kubernetes с нуля с помощью Operator-SDK. Здесь мы использовали Go, но можно выбрать любой другой язык. Экспериментируйте!