
Всем привет! Меня зовут Иван Гулаков, последние несколько лет я занимаюсь построением и поддержкой платформы по предоставлению 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).
Платформу условно можно разделить на три части:

GitOps-контроллер отвечает за поставку конфигурации непосредственно в управляющий кластер. Он используется и в дочерних кубах — доставка CNI, наших агентов мониторинга и т. д.
Управляющий кластер существует в единственном экземпляре на окружение, он управляет всеми «дочками».
Дочерний кластер — k8s-кластер, где разработчики запускают приложения.
Слой BareMetal — набор приложений, которые подготавливают сервер, наливают на него операционную систему и осуществляют day 0 provisioning для того, чтобы железка в дальнейшем стала частью k8s-кластера.
Terraform Operator непосредственно запускает деплой Kubernetes.
Пройдёмся по этому списку поподробнее.
GitOps
Мы используем контроллер Fleet от Rancher и раскатываем кластеры в виде инфраструктурных релизов. Любой релиз состоит из двух основных частей: CR Terraform и конфигурационного секрета, в котором лежат токены и прочие чувствительные данные, необходимые для работы оператора и раскатки дочернего кластера.

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

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

Операционка заливается на хост в четыре этапа:
-
Включение железки и настройка её Boot Order.
-
Стандартный процесс PXE-загрузки. В нашем случае iPXE, потому что PXE устарел.
-
После появления NBP-загрузчика из п. 2 мы получаем конфиги для пост-провижининга операционной системы. Это может быть Ignition и CloudInit.
-
Profit!
Разберём подробнее.
За первый пункт отвечает Piñata BareMetal Operator, наша внутренняя разработка. Это приложение умеет включать и выключать железный хост, собирать статус с его IPMI и выставлять корректный порядок загрузки хоста.
Это связующая часть между хостом и matchbox. Оператор ходит в него по gRPC и переключает там активные конфиги для хоста. Это часть конфигурации Piñatametal.

А теперь вспомним, как работает 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 есть три основных сущности:
-
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.

Диаграмму можно разбить на два блока:
— Первичная установка 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, поэтому всё легко восстановить, если что-то пойдёт не так.
Сам оператор предоставляет ряд полезных фичей.

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.
-
Появляется таска в Jira.
-
Подготавливается конфигурация наливки ОС для нового кластера (matchbox). В дальнейшем она отправится в GitOps-репозиторий mgmt-кластера.
-
Подготавливается конфигурация наливки самого k8s и сервисов, которые будут установлены в него. Она отправится в отдельный репозиторий для дочернего кластера. У каждого кластера есть такой репозиторий.
-
Конфиги пушатся в git, в mgmt-кластере отрабатывает Fleet и устанавливает helm-релиз нового кластера.
-
Начинается процесс наливки ОС на серверы.
-
После окончания установки ОС запускается Terraform c RKE внутри.
-
Финальный штрих — внутрь дочернего кластера попадает собственный экземпляр Fleet и дотягивает внутрь различные системные компоненты, например CSI-драйвер.
Такой процесс наливки кластера на железки занимает обычно меньше часа без учёта времени подготовки конфигураций.
Что в итоге: плюсы и минусы подхода
В шапке статьи я уже упоминал, что наши подходы к наливке кубов сильно изменились. Появился долгожданный ClusterAPI, был написан infra provider для него и ещё целая пачка вспомогательных операторов. Выводы ниже — это некий срез на начало 2024 года, ровно перед началом новой большой стройки.
Что было хорошо тогда и сохранилось сейчас:
-
Относительно легковесная и простая наливка железок.
-
Подход Kubernetes Native — всё, что можно, упаковываем в CR, обрабатываем их операторами.
-
Конфигурация в репозитории в YAML — наглядно и удобно читать.
-
Единая точка управления всем зоопарком кластеров — mgmt k8s, а не с ноутбука инженера.
А что не очень:
-
Много разнородных open source компонентов в системе. Если что-то вдруг ломалось посреди пути, то дебажить было то ещё удовольствие.
-
Terraform. Он довольно-таки хорошо решал свои задачи, как промежуточное решение, но хотелось большего (метрики, более гранулярное логирование, ретраи).
-
Наследственные болячки самого RKE1.
Читайте и смотрите другие материалы про строительство нового облака MWS
Зачем мы строим собственное публичное облако? Рассказывает CTO MWS Данила Дюгуров
Реалити-проект для инженеров про разработку облака — Building the Cloud.
Рассказываем про архитектуру сервисов платформы ещё до релиза.
ссылка на оригинал статьи https://habr.com/ru/articles/898922/
Добавить комментарий