- PVSM.RU - https://www.pvsm.ru -

8 апреля на конференции Saint HighLoad++ 2019 [1], в рамках секции «DevOps и эксплуатация», прозвучал доклад «Расширяем и дополняем Kubernetes», в создании которого участвовали три сотрудника компании «Флант». В нём мы рассказываем о многочисленных ситуациях, в которых нам хотелось расширить и дополнить возможности Kubernetes, но для чего мы не находили готового и простого решения. Необходимые решения у нас появились в виде Open Source-проектов, и им тоже посвящено это выступление.
По традиции рады представить видео с докладом [2] (50 минут, гораздо информативнее статьи) и основную выжимку в текстовом виде. Поехали!
Kubernetes меняет отрасль и подходы к администрированию, которые давно устоялись:
Однако, разумеется, всё не так гладко: с Kubernetes пришли и свои — новые — вызовы.
Kubernetes не является комбайном, который решает все проблемы всех пользователей. Ядро Kubernetes отвечает только за набор минимально необходимых функций, что присутствуют в каждом кластере:

В ядре Kubernetes определяется базовый набор примитивов — для группировки контейнеров, управления трафиком и так далее. Подробнее о них мы рассказывали в докладе 2-летней давности [3].
С другой стороны, K8s предлагает замечательные возможности по расширению доступных функций, что помогают закрыть и другие — специфичные — потребности пользователей. За дополнения в Kubernetes отвечают администраторы кластеров, которые должны установить и настроить всё необходимое для того, чтобы их кластер «обрёл нужную форму» [для решения их специфичных задач]. Что же это за дополнения такие? Рассмотрим некоторые примеры.
Установив Kubernetes, мы можем удивиться, что сеть, столь необходимая для взаимодействия pod'ов как в рамках узла, так и между узлами, сама по себе не работает. Ядро Kubernetes не гарантирует нужные связи — вместо этого, оно определяет сетевой интерфейс (CNI [5]) для сторонних дополнений. Мы должны установить одно из таких дополнений, которое и будет отвечать за конфигурацию сети.

Близкий пример — решения для хранения данных (локальный диск, сетевое блочное устройство, Ceph…). Изначально они были в ядре, но с появлением CSI [6] ситуация меняется на аналогичную уже описанной: в Kubernetes интерфейс, а его реализация — в сторонних модулях.
Среди прочих примеров:


И это далеко не полный список дополнений… Например, мы в компании «Флант» на каждый Kubernetes-кластер на сегодняшний день устанавливаем 29 дополнений (все они в общей сложности создают 249 объектов Kubernetes). Проще говоря, мы не видим жизни кластера без дополнений.
Операторы созданы для автоматизации рутинных операций, с которыми мы повседневно сталкиваемся. Вот примеры из жизни, отличным решением для которых будет написание оператора:
В любом кластере надо решать рутинные задачи, а правильно это делать — с помощью операторов.
Подытоживая все описанные истории, мы для себя пришли к выводу, что для комфортной работы в Kubernetes требуется: а) устанавливать дополнения, б) разрабатывать операторы (для решения повседневных админских задач).
В целом схема проста:

… но тут выясняется, что:
Итог: для написания контроллера (оператора) приходится потратить существенные ресурсы для изучения матчасти. Это было бы оправдано для «больших» операторов — скажем, для СУБД MySQL. Но если мы вспомним описанные выши примеры (раскладывание секретов, доступ pod'ов в интернет…), которые хочется тоже делать правильно, то мы поймём, что затрачиваемые усилия перевесят нужный сейчас результат:

В общем, возникает дилемма: потратить много ресурсов и обрести правильный инструмент для написания операторов или действовать «по старинке» (но быстро). Для её решения — нахождения компромисса между этими крайностями — мы создали свой проект: shell-operator [13] (см. также его недавний анонс [14] на хабре).
Как он работает? В кластере есть pod, в котором лежит Go-бинарник с shell-operator. Рядом с ним хранится набор хуков (подробнее о них — см. ниже). Сам shell-operator подписывается на определённые события в Kubernetes API, по факту наступления которых он запускает соответствующие хуки.
Как shell-operator понимает, какие хуки при каких событиях вызывать? Эту информацию передают shell-operator'у сами хуки и делают они это очень просто.
Хук — это скрипт на Bash или любой другой исполняемый файл, который поддерживает единственный аргумент --config и в ответ на него выдаёт JSON. Последний определяет, какие объекты его интересуют и на какие события (для этих объектов) следует реагировать:

Проиллюстрирую реализацию на shell-operator одного из наших примеров — раскладывание секретов для доступа к приватному registry с образами приложения. Она состоит из двух этапов.
Первым делом в хуке обработаем --config, указав, что нас интересуют namespace'ы, а конкретно — момент их создания:
[[ $1 == "--config" ]] ; then
cat << EOF
{
"onKubernetesEvent": [
{
"kind": "namespace",
"event": ["add"]
}
]
}
EOF
…
Как будет выглядеть логика? Тоже довольно просто:
…
else
createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
kubectl create -n ${createdNamespace} -f - << EOF
Kind: Secret
...
EOF
fi
Первым шагом мы узнаём, какой namespace был создан, а вторым — создаём через kubectl секрет для этого пространства имён.
Осталось передать созданный хук shell-operator'у — как это сделать? Сам shell-operator поставляется в виде Docker-образа, так что наша задача — добавить хук в специальный каталог в этом образе:
FROM flant/shell-operator:v1.0.0-beta.1
ADD my-handler.sh /hooks
Останется собрать его и push'нуть:
$ docker build -t registry.example.com/my-operator:v1 .
$ docker push registry.example.com/my-operator:v1
Финальный штрих — задеплоить образ в кластер. Для этого напишем Deployment:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-operator
spec:
template:
spec:
containers:
- name: my-operator
image: registry.example.com/my-operator:v1 # 1
serviceAccountName: my-operator # 2
В нём нужно обратить внимание на два момента:
Результат — мы решили нашу проблему родным для Kubernetes способом, создав оператор для раскладывания секретов.
Чтобы ограничить объекты выбранного вами типа, с которыми будет работать хук, их можно фильтровать, отбирая по определённым лейблам (или с помощью matchExpressions):
"onKubernetesEvent": [
{
"selector": {
"matchLabels": {
"foo": "bar",
},
"matchExpressions": [
{
"key": "allow",
"operation": "In",
"values": ["wan", "warehouse"],
},
],
}
…
}
]
Предусмотрен механизм дедупликации, который — с помощью jq-фильтра — позволяет преобразовывать большие JSON'ы объектов в маленькие, где остаются только те параметры, за изменением которых мы хотим следить.
При вызове хука shell-operator передаёт ему данные про объект, которые могут использоваться для любых нужд.
События, при наступлении которых вызываются хуки, не ограничены Kubernetes events: в shell-operator предусмотрена поддержка вызова хуков по времени (аналогично crontab в традиционном планировщике), а также специального события onStartup. Все эти события могут комбинироваться и назначаться на один и тот же хук.
И ещё две особенности shell-operator:
Подводя итог этой части доклада:

Для комфортной работы с Kubernetes была также упомянута необходимость установки дополнений. О ней я расскажу на примере пути нашей компании к тому, как мы делаем это сейчас.
Работу с Kubernetes мы начинали с нескольких кластеров, единственным дополнением в которых был Ingress. В каждый кластер его требовалось ставить по-разному, и мы сделали несколько YAML-конфигураций для разных окружений: bare metal, AWS…
Кластеров становилось больше — больше становилось и конфигураций. Кроме того, мы улучшали сами эти конфигурации, в результате чего они стали довольно разнородными:

Чтобы привести всё в порядок, мы начали со скрипта (install-ingress.sh), который принимал аргументом тип кластера, в который будем деплоиться, генерировал нужную YAML-конфигурацию и выкатывал её в Kubernetes.
Если вкратце, то дальнейший наш путь и связанные с ним рассуждения были таковы:
install-prometheus.sh), однако он примечателен тем, что требует гораздо больше вводных данных, а также их хранение (по-хорошему — централизованное и в кластере), причём некоторые данные (пароли) можно было автоматически генерировать:

kubectl apply стало сложно работать, потому что он не является декларативным и умеет только создавать объекты, но не принимать решения по их статусу/удалять их;Весь этот накопленный опыт мы реализовали в рамках другого своего проекта — addon-operator [15].
В его основе — уже упомянутый shell-operator. Вся система же выглядит следующим образом:
К хукам shell-operator'а добавляются:

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

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

Эта схема удовлетворяет всем требованиям к установке дополнений, что были озвучены выше:
Вся эта система реализована в виде единственного бинарника на Go, который и получил название addon-operator. Благодаря этому схема выглядит проще:

Главный компонент на этой схеме — набор модулей (выделены серым цветом внизу). Теперь мы можем небольшими усилиями написать модуль для нужного дополнения и быть уверенными, что оно будет установлено в каждый кластер, будет обновляться и реагировать на нужные ему события в кластере.
«Флант» использует addon-operator [15] на 70+ Kubernetes-кластерах. Текущий статус — альфа-версия. Сейчас мы готовим документацию, чтобы выпустить бету, а пока в репозитории доступны примеры [16], на основе которых можно создать свой addon.
Где взять сами модули для addon-operator? Публикация своей библиотеки — следующий этап для нас, мы планируем это сделать летом.
Видео с выступления (около часа):
Презентация доклада:
Другие доклады в нашем блоге:
Возможно, вас также заинтересуют следующие публикации:
Автор: Андрей Половов
Источник [20]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/open-source/316204
Ссылки в тексте:
[1] Saint HighLoad++ 2019: http://www.highload.ru/spb/2019/
[2] видео с докладом: https://www.youtube.com/watch?v=6VHk1R1TNgk
[3] докладе 2-летней давности: https://habr.com/company/flant/blog/331188/
[4] Image: https://goo.gl/y4fXUF
[5] CNI: https://habr.com/ru/company/flant/blog/329830/
[6] CSI: https://habr.com/ru/company/flant/blog/424211/
[7] нашей недавней статье: https://habr.com/ru/company/flant/blog/447180/
[8] cert-manager: https://github.com/jetstack/cert-manager
[9] Операторы: https://habr.com/ru/company/flant/blog/326414/
[10] Prometheus и Grafana: https://habr.com/ru/company/flant/blog/412901/
[11] taint: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
[12] Operator SDK: https://github.com/operator-framework/operator-sdk
[13] shell-operator: https://github.com/flant/shell-operator
[14] недавний анонс: https://habr.com/ru/company/flant/blog/447442/
[15] addon-operator: https://github.com/flant/addon-operator
[16] доступны примеры: https://github.com/flant/addon-operator/tree/master/examples
[17] Базы данных и Kubernetes: https://habr.com/company/flant/blog/431500/
[18] Мониторинг и Kubernetes: https://habr.com/company/flant/blog/412901/
[19] Лучшие практики CI/CD с Kubernetes и GitLab: https://habr.com/company/flant/blog/345116/
[20] Источник: https://habr.com/ru/post/449096/?utm_source=habrahabr&utm_medium=rss&utm_campaign=449096
Нажмите здесь для печати.