Как написать оператор Kubernetes?

Андрей Шагин
NOP::Nuances of Programming
5 min readAug 28, 2024
Оператор Kubernetes

Будучи бэкенд-разработчиком, ежедневно имеющим дело с Kubernetes, я всегда хотел написать оператор, пополнив таким образом багаж знаний. Однако достижение этой цели осложнялось возникавшими препятствиями.

Это история о том, как я написал gobackup-operator во время службы в армии.

Можете перейти сразу к проекту.

Разгорелась подготовка

Желание написать оператор Kubernetes усиливалось. Я читал статьи, изучал репозитории GitHub, консультировался с коллегами. Не сказать чтоб суперуспешно.

Однако результатом всех этих усилий стала коллекция проектов-руководств, хранящихся в моем GitHub.

К практической реализации своего намерения я приступил год назад, с момента первого знакомства с Kubernetes: смотрел руководство гуру для CKAD, затем руководство Nana на YouTube.

Но обратилась в пепел

Меня призвали в армию.

Там не было ни интернета, ни даже какого-либо электронного устройства. Только книги в твердом переплете, волейбол и завораживающие виды восхода и заката.

В этой ситуации мысли о создании оператора угасли. Все, что оставалось: есть, читать и иногда наслаждаться свободой, то есть ходить в увалы. Впрочем, свобода эта непродолжительная. Как однажды заметил командир:

«Радость от увала заканчивается в тот момент, когда покидаешь казарму».

И вот сборы закончились, я стал работать в офисе, но и там ощущалась нехватка интернета! Только по вечерам, уходя из офиса, я занимался любимым делом. Иногда результат получается выше, когда время ограничено. Итак, с 16:00 до 19:00 нужно было создать что-то особенное. Для меня это и было особенным.

Он сказал: «Поехали!»…

В итоге с помощью серии статей удалось написать другой оператор Kubernetes, по руководствам и на этот раз по-иному.

Мои коллеги уже разработали систему резервного копирования, но она оказалась не слишком рабочей. Тогда они стали искать другое решение и наткнулись на проект gobackup, посвященный регулярному созданию резервных копий баз данных и отправлению их в хранилище.

Проблема заключалась в том, что отсутствовала поддержка базы данных etcd. Коллеги приняли участие в проекте, добавив поддержку etcd под имевшиеся требования. В итоге появился новый релиз, на основе которого в мое отсутствие они решили разработать оператор Kubernetes.

Для меня это был важный шаг. Когда они рассказали мне о проекте, я с нетерпением изучил его и подумал: «Ну наконец-то, оператор почти готов».

Пока читал проект, заметил проблему в его README: одна из ссылок вела на страницу 404. Исправив проблему, отправил запрос на включение изменений в репозиторий проекта. Владельцы приняли его с распростертыми объятиями.

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

Открыв вопрос, я предложил создать в gobackup репозиторий, меня снова приняли как родного.

Днем я служил в армии, а вечерами коммитил проект оператора на gobackup.

Собственно проект

Я начал с настройки среды.

На моем компьютере уже были установлены Golang, Docker и kubectl. С запускаемыми на локальной машине кластерами Kubernetes, такими как Kind, и инструментами для создания операторов вроде kubebuilder я познакомился ранее.

Поэтому, запустив код оператора:

$ kubebuilder init --domain gobackup.io --repo github.com/gobackup/gobackup-operator

Я приступил к созданию API-интерфейсов сперва для оператора:

$ kubebuilder create api --group gobackup --version v1 --kind Backup
Create Resource [y/n]
y
Create Controller [y/n]
y

Потом для баз данных и хранилищ:

$ kubebuilder create api --group database.gobackup --version v1 --kind PostgreSQL
Create Resource [y/n]
y
Create Controller [y/n]
y
$ kubebuilder create api --group storage.gobackup --version v1 --kind S3
Create Resource [y/n]
y
Create Controller [y/n]
y

Исходя из конкретных требований проекта, внес изменения в API-интерфейсы:

// Backup — это схема для API резервных копий
type Backup struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec BackupSpec `json:"spec,omitempty"`
Status BackupStatus `json:"status,omitempty"`

BackupModelRef BackupModelRef `json:"backupModelRef,omitempty"`
StorageRefs []StorageRef `json:"storageRefs,omitempty"`
DatabaseRefs []DatabaseRef `json:"databaseRefs,omitempty"`
}

Затем в метод Reconcile:

//+kubebuilder:rbac:groups=gobackup.io,resources=backups,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=gobackup.io,resources=backups/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=gobackup.io,resources=backups/finalizers,verbs=update
func (r *BackupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// реализация «reconcile»
}

Протестируем

Но прежде подготовим тестовую базу данных для создания из нее резервных копий, создаем развертывание PostgreSQL из этого доступного yaml-файла gobackup-operator-postgres-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-deployment
spec:
selector:
matchLabels:
app: postgres
replicas: 1
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:14.11
env:
- name: POSTGRES_USER
value: ""
- name: POSTGRES_PASSWORD
value: ""
- name: PGDATA
value: "/var/lib/postgresql/data/pgdata"
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgredb
volumes:
- name: postgredb
persistentVolumeClaim:
claimName: postgres-pvc

Не забываем поменять в манифесте POSTGRES_USER и POSTGRES_PASSWORD и применить:

kubectl apply -f example/gobackup-opetator-postgres-deployment.yaml, 
example/gobackup-opetator-postgres-service.yaml

Чтобы протестировать оператор в кластере Kubernetes, я добавил в каталог gobackup-operator/example/ ресурсы: развертывания, роли, clusterroles, serviceaccounts и т. д.

Для добавления базовых ресурсов применяем этот манифест:

kubectl apply -f example/gobackup-opetator-serviceaccount.yaml,
gobackup-opetator-pvc.yaml,
gobackup-opetator-namespace.yaml,
gobackup-opetator-clusterrolebinding.yaml,
gobackup-opetator-clusterrole.yaml

А затем манифесты хранилища и базы данных:

kubectl apply -f example/gobackup-opetator-storage/*
kubectl apply -f example/gobackup-opetator-database/*

Запускаем оператор на локальном компьютере, используя такой манифест:

kubectl apply -f example/gobackup-opetator-deployment.yaml

Когда создается или изменяется объект Backup или CronBackup, оператором выполняются необходимые задачи.

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

kubectl apply -f example/gobackup-opetator/gobackup-opetator-backupmodel.yaml

При применении в каталоге gobackup-operator/example/gobackup-operator одного из манифестов — backup или cronbackup — активируется оператор, которым запустится резервное копирование:

kubectl apply -f example/gobackup-opetator/gobackup-opetator-cronbackup.yaml

Заключение

Сначала я чувствовал неловкость из-за внесения столь малого изменения в файл README. Это было похоже на один из тех запросов на включение изменений в репозиторий, которые делаются только для того, чтобы поучаствовать в коммитах на Hacktoberfest.

Но потом я задумался: даже те однострочные коммиты были эффективными. Кто знает, не внеси я то изменение в документ README, создал бы вообще оператор или нет?

Важен и малый шаг.

Загляните и вы сюда поучаствовать. Меняйте даже файл README, если нужно.

Читайте также:

Читайте нас в Telegram, VK и Дзен

--

--