Куб всему голова: строим внутреннюю Kubernetes-платформу на BareMetal в MWS

от автора

Всем привет! Меня зовут Иван Гулаков, последние несколько лет я занимаюсь построением и поддержкой платформы по предоставлению Kubernetes-кластеров для внутренних команд разработки в MWS. Эта статья — пересказ моего доклада с DevOps Conf 2024 о том, как мы наливаем железки и превращаем их в k8s-кластеры по всем канонам IaC.

За прошлый год наши подходы к наливке изменились. Тем не менее, это был важный шаг для становления нашей внутренней платформы Piñata.

Почему именно BareMetal и при чём здесь облако

Вы наверняка пользовались различными облаками. Главный строительный блок любого облака — слой IaaS, она же инфраструктура как сервис. IaaS традиционно стоит на трёх китах:

— Compute;

— Network;

— Storage.

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

Следующий аргумент в пользу BareMetal — требования от наших DPL*-сервисов, например compute и network. Агент виртуализации должен работать с железкой напрямую.

Сервисы IaaS да и всего остального облака делятся на две большие группы:

— CPL (control plane);

— DPL (data plane).

CPL — «мозг» системы. Он отвечает за принятие решений, управление и координацию. А DPL — «мышцы» системы, которые выполняют задачи, worker nodes. С точки зрения процесса деплоя и эксплуатации CPL — классические микросервисы. DPL — уже не такие классические и требуют отдельных подходов (privileged, host network).

Если схлопнуть оба типа сервисов под единый оркестратор, у команд эксплуатации и разработки значительно упрощается жизнь. Из минусов — у BareMetal значительно сложнее capacity management в отличие от виртуалок. Железки надо правильно тюнить и грамотно расходовать ресурсы. Запустить оптимальное число сервисов на каждом сервере — довольно нетривиальная задача. И вот как мы подошли к её решению.

Платформа Piñata

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

Каждый стенд может состоять из нескольких Kubernetes-кластеров. Соответственно, k8s-кластеры мы развёртываем в большом количестве и часто их «перекатываем». Поэтому мы решили разработать свою внутреннюю платформу — Piñata. Это решение для деплоя Kubernetes-кластеров, а не доставку кода разработчиков (приложений) в них.

В основу платформы легли принципы: 

  • Подход Kubernetes Native во главе угла. Все инфраструктурные компоненты платформы мы реализовывали в формате операторов.

  • GitOps- и IaC-подходы для поставки конфигурации в операторы.

  • Management-кластер Kubernetes как центральная точка имплементации изменений (привет, Cluster API).

Платформу условно можно разделить на три части:

Из чего состоит Piñata

Из чего состоит Piñata

GitOps-контроллер отвечает за поставку конфигурации непосредственно в управляющий кластер. Он используется и в дочерних кубах — доставка CNI, наших агентов мониторинга и т. д.

Управляющий кластер существует в единственном экземпляре на окружение, он управляет всеми «дочками».

Дочерний кластер — k8s-кластер, где разработчики запускают приложения.

Слой BareMetal набор приложений, которые подготавливают сервер, наливают на него операционную систему и осуществляют day 0 provisioning для того, чтобы железка в дальнейшем стала частью k8s-кластера.

Terraform Operator непосредственно запускает деплой Kubernetes.

Пройдёмся по этому списку поподробнее.

GitOps

Мы используем контроллер Fleet от Rancher и раскатываем кластеры в виде инфраструктурных релизов. Любой релиз состоит из двух основных частей: CR Terraform и конфигурационного секрета, в котором лежат токены и прочие чувствительные данные, необходимые для работы оператора и раскатки дочернего кластера.

Флоу GitOps

Флоу GitOps

Мы используем GitOps- и IaC-подходы в любом конфигурационном репозитории кластера. Содержимое репозитория, естественно, в формате yaml. Релиз упакован с помощью helm и доставляется через Fleet.

Прячем секреты

Прячем секреты

Логичный вопрос — а как спрятать в конфигурационном репозитории секреты, упомянутые выше? Тут всё просто — релиз поставляется в виде двух СR: Terraform и VaultStaticSecret. Vault secrets operator с помощью этой СR вытягивает из Vault данные и создаёт итоговый k8s Secret. В самой CR Terraform мы делаем референс на итоговый секрет, так как знаем имя, под которым он будет синхронизирован из хранилища.

BareMetal

Заливка операционки на хост

Заливка операционки на хост

Операционка заливается на хост в четыре этапа:

  1. Включение железки и настройка её Boot Order. 

  2. Стандартный процесс PXE-загрузки. В нашем случае iPXE, потому что PXE устарел.

  3. После появления NBP-загрузчика из п. 2 мы получаем конфиги для пост-провижининга операционной системы. Это может быть Ignition и CloudInit.

  4. Profit!

Разберём подробнее.

За первый пункт отвечает Piñata BareMetal Operator, наша внутренняя разработка. Это приложение умеет включать и выключать железный хост, собирать статус с его IPMI и выставлять корректный порядок загрузки хоста.

Это связующая часть между хостом и matchbox. Оператор ходит в него по gRPC и переключает там активные конфиги для хоста. Это часть конфигурации Piñatametal.

PXE и iPXE

PXE и iPXE

А теперь вспомним, как работает PXE и iPXE. Всё начинается со стандартной загрузки хоста, у которого в boot order первый пункт — PXE Boot. Сетевая карточка посылает специальный запрос DHCP Discover и получает ответ.

В ответе присутствует специальный параметр BootFileName. Он указывает на адрес TFTP-сервера, на котором можно найти загрузчик. Далее firmware сетевой карточки выдёргивает специальный загрузчик NBP — Network Boot Protocol. 

После его скачивания загружается скрипт Boot iPXE. В нём есть меню, похожее на bash-скрипт, в котором указано, какое ядро linux и initrd откуда загрузить, чтобы начать наливать операционную систему.

За конфигурацию наливки отвечает уже matchbox — open-source-продукт, которым может воспользоваться любой желающий. Изначально он был заточен под установку или загрузку системы семейства CoreOS — Flatcar. Это простой веб-сервер, который умеет отдавать файлики по REST API и gRPC API.

Matchbox

Matchbox

Как мы дорабатывали matchbox

У matchbox есть три основных сущности:

  • Machine group — нечто похожее на идентификатор хоста, который позволяет при загрузке отличить машину от других. Это неочевидно, но группа для однозначной идентификации чаще всего состоит из одной машины. В качестве селектора выступает mac-адрес.

  • Profile описывает, как правильно налить ОС на конкретную машину. Чаще всего это ссылки на нужное ядро, initrd и аргументы запуска для ядра (/proc/cmdline), которые позволят операционной системе загрузиться в инсталлятор.

  • Ignition (butane) или cloud-init. В том же облаке есть метадата-сервер, который предоставляет система виртуализации. А вот у железки что-то должно быть посредником для подготовки ignition- и cloud-init-конфигураций. Matchbox позволяет наливать BareMetal как виртуальную машину.

А ещё мы доработали matchbox, добавив четвёртую сущность — iPXE-menu для работы с iPXE Boot скриптами, упомянутыми выше. В базовом matсhbox захардкожено только одно меню. Мы расширили возможности работы с конфигурацией в формате cloud-init для того, чтобы начать наливать не только Flatcar, но и Ubuntu.

Сам по себе matchbox — это обычное веб-приложение, не заточенное под запуск в Kubernetes. Изначально мы просто обернули его в helm chart, а вся его конфигурация доставлялась в ходе рендеринга этого чарта в виде стандартных кубовых configmap’ов.

На дистанции такой подход стал плохо масштабироваться. Чарт очень сильно распухал и при выкатке тащил за собой очень много configmap’ов, которые монтировались в pod в виде отдельных mount’ов. Мы решили это переработать и написали вспомогательный сервис config loader — sidecar с k8s-клиентом внутри, который ходит в API Куба, вычитывает определённые configmap’ы (фильтрация по служебным labels + annotations) и складывает их содержимое на ФС пода в виде файлов. Проблема с десятками mount’ов была решена.

Ещё одна доработка кодовой части matchbox — клиент для Netbox. Изначально мы жили строго на статических адресах, но это плохо масштабируется. Когда два инженера одновременно наливают серверы и по ошибке выставляют в наливочных конфигах два одинаковых адреса — это боль. Поэтому мы реализовали «псевдостатику» — инженеры больше не вписывают IP-адреса в конфиги самостоятельно, matchbox делает это сам с помощью шаблонизации, обогащённой данными из Netbox. C точки зрения Linux итоговая адресация остаётся статической, не DHCP или SLAAC. 

Как выглядят наливочные конфиги

Рассмотрим сокращённый конфиг, на котором видно, как мы описываем машины. Всё содержимое дальнейших скриншотов — это фактически values для helm-чарта matchbox, лежащие в конфигурационном репозитории.

Обратите внимание, что содержимое секции groups не соответствует тому, что попадает в итоговые configmap’ы с содержимым сущностей groups от самого matchbox. Это наша абстракция. Внутри содержится куча всего разного:

— mac-адреса для идентификации хоста;

— hostname;

— настройки для генерации CR для Piñata BareMetal Operator (для подключения к IPMI хоста) и другое.

Чувствительная информация не протекает в итоговую конфигурацию, есть только референсы для Vault Secret Operator.

Вторая часть — аналогичная обёртка с небольшим количеством метаинформации, только для сущности Profile. На её основе генерится итоговая ссылка до kernel + initrd от Flatcar, лежащих на нашем Nexus.

Последний кусочек — butane-шаблоны (Ignition). Внутри чартов есть несколько базовых butane-шаблонов, а секция override позволяет перезаписывать из values-чарта отдельные их части, чтобы не приходилось каждый раз добавлять новые файлики и пересобирать сам helm chart.

Как всё это работает

Теперь давайте разберёмся, как работают в связке matchbox и Piñatametal Operator.

Matchbox ubuntu installation

Matchbox ubuntu installation

Диаграмму можно разбить на два блока:

— Первичная установка Ubuntu на хост (до пункта now we get ubuntu installed on HDD).

— Перезагрузка хоста и донастройка ОС через cloud-init.

Теперь посмотрим на iPXE-menu поближе. Тут есть две важных строчки, отрендеренных matchbox, — kernel и initrD. В строке kernel есть не только ссылка до файла ядра, но и аргументы для последующей загрузки ОС (/proc/cmdline).

После выполнения скрипта меню (можно выбрать нужный пункт самостоятельно, в нашем случае он называется ipxe) загружается специальный инсталлятор ОС, инструкции для которого описаны в файле autoinstall.yaml.

Это специальный конфигурационный формат для установщика Ubuntu casper, он похож по синтаксису на стандартный cloud-init.

После прогона всех этапов инсталлятора мы получаем установленную на диск или диски ОС. Теперь нам надо не попасть в бесконечный цикл переустановки ОС. Для этого Piñatametal Operator идёт в API matchbox и «перещёлкивает» профиль у машинки. Он мутирует строчку profile в сущности group нужной машины.

И тут есть интересный момент. Главный постулат GitOps — что в репозитории, то и на стенде. Из-за обилия  dummy-коммитов GitOps плохо подходит для промежуточных состояний, например для переключения сущности profile. Поэтому мы вынесли эту логику в matchbox. В GitOps-репозитории у нас описано только желаемое состояние хоста, а не промежуточное.

Ещё нюанс — в обоих случаях, до и после замены итогового iPXE-меню, первым пунктом в bootOrder хоста остаётся PXE. Тем не менее, даже если хост вдруг потеряет сетевую связность, вторым пунктом всегда остаётся загрузка с диска, так что сервер не превратится в тыкву.

Финальный штрих — загрузившийся хост опять идёт в matchbox, получает сloud-init и донастраивается. К установке k8s готовы.

Что мы используем: Flatcar и Ubuntu

Наверняка по тексту выше вы заметили, что где-то идут референсы на конфиги Flatcar, а где-то — на Ubuntu. Мы действительно используем две ОС с разной методологией.

Flatcar — это легковесная ОС, оптимизированная для запуска контейнеров. Поставляется в виде готовых образов (squashFS/qcow/vmdk), включающих все необходимые компоненты container runtime, что позволяет быстро развернуть Kubernetes. Бонусом — immutable, хоть и с некоторыми нюансами.

Мы полностью загружаем Flatcar в ramdisk. На физических дисках присутствуют только два раздела: /opt/rke и /var/lib/docker. Это нужно, чтобы после перезагрузки нода не забыла, что является частью Kubernetes-кластера, и для кеширования docker-образов в качестве бонуса.

Ubuntu устанавливается как обычно, никаких ramdisk, все данные строго персистентные. Ubuntu используется для стендов сервисов Compute/Storage и прочих дистрибутиво-зависимых сервисов, Flatcar — для быстрого поднятия разработческих песочниц в dev.

TF-Operator

Для работы с самим Terraform мы используем open source проект Terraform Operator. Он позволяет запускать стандартный пайплайн Terraform Init/Plan/Apply в виде отдельных Job’ов в k8s. Логи прогона наливок всех кластеров находятся в одном mgmt-кластере, их удобно смотреть.

Давайте посмотрим на пример конфигурации и пройдёмся по важным моментам.

В качестве бэкенда для Terraform state используем k8s по нескольким причинам:

— остальные стейты тоже в k8s в виде CR;

— для каждого кластера у нас организован бэкап etcd, поэтому всё легко восстановить, если что-то пойдёт не так.

Сам оператор предоставляет ряд полезных фичей.

Фишки TF-Operator

Фишки TF-Operator

Require approval позволяет затормозить пайплайн деплоя Terraform на этапе plan. Для инженера это выглядит так: pod с Terraform plan внутри пишет лог в stdout, а специальный trap-скрипт внутри ожидает создания файла по определённому пути. Если инженера устраивает список производимых изменений, он проваливается в pod, создаёт этот файл, далее запускается pod с Terraform apply.

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

Ещё одно отличие от классического Terraform — возможность кастомизации его пайплайна деплоя. Специальные pre- и post-хуки позволяют выполнять различные скрипты между этапами init/plan/apply. Кроме того, можно сделать выполнение хука опциональным. В случае если он не отработает, пайплайн работы Terraform не упадёт в ошибку.

Все этапы пайплайна от стандартного Terraform до хуков Terraform operator можно обогащать данными с помощью переменных окружения и монтирования configmap’ов внутрь самих pod’ов.

Итоговый процесс наливки кластера

Полный флоу деплоя

Полный флоу деплоя

Соберём всё вместе и посмотрим, как выглядит процесс появления нового k8s-кластера на BareMetal.

  1. Появляется таска в Jira.

  2. Подготавливается конфигурация наливки ОС для нового кластера (matchbox). В дальнейшем она отправится в GitOps-репозиторий mgmt-кластера.

  3. Подготавливается конфигурация наливки самого k8s и сервисов, которые будут установлены в него. Она отправится в отдельный репозиторий для дочернего кластера. У каждого кластера есть такой репозиторий.

  4. Конфиги пушатся в git, в mgmt-кластере отрабатывает Fleet и устанавливает helm-релиз нового кластера.

  5. Начинается процесс наливки ОС на серверы.

  6. После окончания установки ОС запускается Terraform c RKE внутри.

  7. Финальный штрих — внутрь дочернего кластера попадает собственный экземпляр Fleet и дотягивает внутрь различные системные компоненты, например CSI-драйвер.

Такой процесс наливки кластера на железки занимает обычно меньше часа без учёта времени подготовки конфигураций.

Что в итоге: плюсы и минусы подхода

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

Что было хорошо тогда и сохранилось сейчас:

  • Относительно легковесная и простая наливка железок.

  • Подход Kubernetes Native — всё, что можно, упаковываем в CR, обрабатываем их операторами.

  • Конфигурация в репозитории в YAML — наглядно и удобно читать.

  • Единая точка управления всем зоопарком кластеров — mgmt k8s, а не с ноутбука инженера.

А что не очень:

  • Много разнородных open source компонентов в системе. Если что-то вдруг ломалось посреди пути, то дебажить было то ещё удовольствие.

  • Terraform. Он довольно-таки хорошо решал свои задачи, как промежуточное решение, но хотелось большего (метрики, более гранулярное логирование, ретраи).

  • Наследственные болячки самого RKE1.


Читайте и смотрите другие материалы про строительство нового облака MWS

Зачем мы строим собственное публичное облако? Рассказывает CTO MWS Данила Дюгуров

Реалити-проект для инженеров про разработку облака — Building the Cloud.
Рассказываем про архитектуру сервисов платформы ещё до релиза.

Подкаст «Расскажите про MWS»

Карьера в MWS


ссылка на оригинал статьи https://habr.com/ru/articles/898922/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *