ESPN DeportesEN VIVO: México pierde ante USA con error de Tala RangelESPNFollow live: Julian Hall scores to give USMNT lead over MexicoInquirerNDF seeks Red Cross intervention for wounded, pregnant rebel leaderThe Jerusalem PostDefense Ministry: 1,318 Israeli security personnel killed across all fronts since October 7UOLEleição tem 158 milhões aptos a votar com disputa acirrada entre Lula e Flávio BolsonaroZDF heuteAktuelle Pressemitteilungen des ZDFNew Straits TimesMurdered Yong Peng student dreamed of becoming doctor like late motherSRF NewsHunderte Millionen Verlust – Swiss Steel nimmt Stellenabbau in Deutschland vorEl ComercioLa Kábala, sábado 3 de octubre: Conoce los resultados del sorteo con un pozo de S/ 1′123,767n-tv"Situation nicht unterschätzt": Reiche wehrt sich gegen Vorwürfe wegen zusätzlicher GaseinkäufeBBC NewsBurnham scraps controversial plans to curb jury trials경향신문외국인의 ‘이머전시 레디’ 앱 사용 극히 저조···“재난정보 전달 실효성 낮아”
The Daily Newsstand · Free, Always
Sunday, October 4, 2026

От bare metal до managed Kubernetes: разбираем архитектуру Cozystack. Расшифровка митапа в Дубае

Translate

Как устроен Cozystack и managed Kubernetes на собственном железе

Во время поездки в ОАЭ я случайно попал в дубайское IT-комьюнити в Telegram. Там мы внезапно скоординировались и собрали незапланированный митап. 3 октября встретились в небольшой переговорке в Дубае — получилось душевно, и мы успели обсудить много интересных тем: от Talos и хранилища до сетей и устройства managed Kubernetes.

За помощь с организацией большое спасибо члену нашего комьюнити Artem Goncharenko — @roysbike. Артём нашёл локацию и скоординировал встречу.

Участники экспромт-митапа Cozystack в Дубае

Участники экспромт-митапа Cozystack в Дубае

Экспромт-митап 3 октября 2026 года в Дубае.

На митапе я рассказывал о Cozystack — открытой платформе для предоставления managed-сервисов на собственном железе. Начали с обзорной презентации, но довольно быстро перешли к вопросам: почему выбрали именно такой storage, зачем нам одновременно Kube-OVN и Cilium, как устроен Kubernetes внутри Kubernetes и где проходит граница между облачной платформой и обычной виртуализацией.

Ниже — отредактированная техническая расшифровка доклада и обсуждения. Я разберу, как связаны Talos, Flux, LINSTOR, Kube-OVN, Cilium, KubeVirt, Kamaji и Cluster API; как пользовательский ресурс превращается в работающий сервис; и почему для managed Kubernetes важно отдельно обслуживать control plane, вычислительные узлы и хранилище.

Пользователю нужен работающий сервис

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

Мы хотим поднять эту абстракцию на уровень выше. Человек заказывает PostgreSQL, Kubernetes-кластер или другой готовый сервис, а платформа берёт на себя его развёртывание и обслуживание.

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

Каталог приложений Cozystack

Каталог приложений Cozystack

Пользователь выбирает сервис в каталоге; низкоуровневые ресурсы создаёт платформа.

Сложность здесь в слове managed. Установить приложение — только начало. Нужно обеспечить резервное копирование, мониторинг, восстановление после отказов и понятный жизненный цикл. И всё это должно воспроизводимо работать в разных окружениях.

Поэтому Cozystack мы рассматриваем как платформу для managed-сервисов. Kubernetes даёт нам основу, но пользовательский сервис появляется только после того, как мы связываем все необходимые механизмы между собой.

Самая дорогая часть интеграции

Когда приходит заказчик, его задача обычно звучит вполне конкретно: нужен сервис, нужна инфраструктура, нужно решить определённую проблему. А самая трудоёмкая часть решения часто прячется между компонентами.

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

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

Интеграции с сетью избежать невозможно: платформу в любом случае нужно подключить к сети заказчика. А вот зависимость от внешнего хранилища можно убрать. Если готового storage нет, мы приносим свой LINSTOR и строим хранилище на доступных дисках. Если хранилище уже есть, подключаем его через стандартный механизм CSI. Это позволяет использовать существующую инфраструктуру и при этом не делать её обязательным условием установки.

Это не означает, что мы пишем всё самостоятельно. Наоборот, мы стараемся переиспользовать стандартные Kubernetes-операторы и существующие технологии. Собственная работа сосредоточена на том, как сделать из них единую систему.

На обзорной схеме архитектура разделена на четыре уровня:

Уровень

Что на нём находится

Какую задачу решает

1

Операционная система и железо, Talos Linux

Даёт воспроизводимую основу для работы узлов

2

LINSTOR, сеть, KubeVirt

Предоставляет хранение, связность и виртуальные машины

3

Операторы, Cluster API, мониторинг

Обслуживает жизненный цикл приложений и кластеров

4

Managed Kubernetes, базы данных и пользовательский Kubernetes API

Предоставляет готовые сервисы клиенту

Четыре уровня архитектуры Cozystack

Четыре уровня архитектуры Cozystack

Архитектура Cozystack: от операционной системы и железа до пользовательских managed-сервисов.

Flux CD отвечает за доставку и согласование конфигурации компонентов. Эта схема помогает объяснить, почему интерфейс заказа базы данных — только верхушка системы: ниже должны согласованно работать ещё несколько уровней.

Talos Linux и конфигурация узлов

Одна из задач платформы — одинаково работать на разных серверах. Если каждый узел настроен вручную, со временем они начинают различаться: версии пакетов, модули ядра, сетевые настройки. Приходится поддерживать не только платформу, но и историю каждого сервера.

Поэтому мы используем Talos Linux. Для нас здесь важна воспроизводимость. Система состоит из подготовленного образа и декларативной конфигурации. Вместо последовательности ручных действий мы описываем состояние, к которому нужно привести машину.

На вопрос «это прежде всего про security?» я отвечал, что для нас ключевой эффект — управляемость и воспроизводимость. Внутри системы работают контроллеры, которые приводят её состояние к заданному. Это та же идея reconciliation, на которой построен Kubernetes.

Две части конфигурации

В показанном на слайде YAML есть две основные секции: machine и cluster.

machine описывает конкретную машину: её роль, параметры доступа к Talos API, настройки сети, интерфейсы и диск для установки. На слайде это узел типа control-plane, интерфейс eth0 и установка на /dev/sda.

Секция machine в конфигурации Talos

Секция machine в конфигурации Talos

На слайде выделены параметры конкретного узла. Это иллюстрация структуры конфигурации, а не готовый конфиг для копирования.

cluster описывает Kubernetes-кластер: endpoint control plane, параметры удостоверяющего центра и настройки компонентов Kubernetes. На слайде endpoint — https://192.168.100.10:6443; тот же адрес фигурирует в настройке виртуального IP. Другим узлам нужно знать, к какому кластеру подключаться.

Секция cluster в конфигурации Talos

Секция cluster в конфигурации Talos

Иллюстрация структуры конфигурации, а не реальный конфиг: сертификаты и ключи заменены пустыми строками, часть секций сокращена. Общие параметры cluster дополняются настройками конкретной machine.

Вопрос из зала: как узлы находят друг друга и нужно ли делать bootstrap на каждом?

Bootstrap выполняется один раз, на одном control-plane узле. Остальные машины получают конфигурацию того же кластера и присоединяются к нему. Независимо инициализировать кластер на каждой машине не нужно.

Здесь важно разделять общий адрес API и discovery узлов. В показанной конфигурации VIP 192.168.100.10 даёт устойчивый адрес Kubernetes API: его обслуживает один из control-plane узлов, а при переключении адрес переходит к другому. Discovery через Kubernetes API позволяет Talos получать сведения об участниках уже работающего кластера. Это не заменяет первоначальный bootstrap: сначала должна появиться управляющая часть, к которой можно обращаться.

Зачем мы сделали Talm

В виртуальных машинах аппаратная среда относительно однообразна. На bare metal у серверов могут отличаться диски, названия и количество сетевых интерфейсов, схема подключения. Применить одну конфигурацию ко всем узлам без учёта этих различий не всегда получится.

Поэтому мы сделали Talm, исходники которого находятся в cozystack/talm. В описанном на митапе процессе он сначала опрашивает узлы и собирает сведения о машинах, а затем помогает подготовить их конфигурации. Результат можно посмотреть, поправить и применить обратно к узлам.

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

Образ системы и настройки меняются по-разному

Вопрос из зала: при изменении конфигурации Talos нужно перезагружать узел? А как добавлять модули ядра?

Изменение конфигурации не всегда требует перезагрузки: многие настройки Talos применяет в работающей системе. Обновление образа ОС и состава расширений — другая операция, которая может требовать перезапуска.

Talos использует immutable-образ: системная основа заранее собрана, а нужные для нашей платформы модули подготавливаются вместе с образом. В обычном Linux storage-компонент может запускать DKMS и собирать модуль на каждом узле в рантайме под установленное ядро. Здесь мы переносим эту работу на этап подготовки образа. В результате узлы получают один и тот же проверенный набор компонентов, вместо того чтобы каждый раз собирать его независимо при установке.

Отдельная сложность возникла с проприетарными драйверами NVIDIA. В нашей конфигурации Talos нельзя просто загрузить нужные модули в рантайме: чтобы добавить эти драйверы, приходится перекомпилировать сам Talos. Поэтому сейчас для таких задач мы используем Ubuntu.

Почему мы выбрали LINSTOR и DRBD

При выборе хранилища нам была важна не только автоматизация, но и понятная модель того, где находятся данные и как их можно восстановить в случае чего.

Мы используем LINSTOR и DRBD; развёртывание LINSTOR в Kubernetes обслуживает Piraeus Operator. Здесь полезно разделять их роли. LINSTOR управляет размещением и жизненным циклом томов, а DRBD обеспечивает репликацию блочных устройств. Под ними находятся привычные механизмы хранения, такие как LVM или ZFS.

Слои хранения LINSTOR

Слои хранения LINSTOR

LINSTOR управляет стеком хранения, а репликация DRBD работает на уровне ядра.

Поэтому LINSTOR я бы не описывал как прямой аналог Ceph. Это другой подход: управление томами и их репликами поверх стандартных технологий хранения.

Для меня у такой схемы есть существенное преимущество. Даже если сломалась управляющая часть, данные остаются на понятных блочных устройствах. Их восстановление не обязательно начинается с восстановления всей платформы управления. В разговоре я приводил пример с dd: для извлечения данных доступны обычные инструменты работы с блочными устройствами. Это не универсальная инструкция аварийного восстановления, а объяснение того, почему мне важна прозрачная структура хранения. Мне нравится опираться на технологии, поведение которых можно изучить и проверить обычными системными инструментами.

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

Зато мы можем выбирать разные варианты хранения для разных нагрузок. Где-то нужна репликация, где-то достаточно локального тома. Пользователю это предоставляется через классы хранения, а платформа решает, как разместить конкретные ресурсы.

Данные и вычисления нужно размещать вместе

На митапе спросили: что происходит, если реплики диска находятся на одних серверах, а виртуальная машина запускается на другом?

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

У этого два уровня. Сначала планировщик учитывает, где уже находятся реплики тома, и старается разместить Pod или VM на подходящем узле. Для этой задачи используется LINSTOR scheduler extender.

В обратную сторону работает auto-diskful в LINSTOR. Если ресурс достаточно долго находится в роли Primary на узле без локальной реплики, LINSTOR может создать там дисковую реплику и синхронизировать данные. Порог задаётся параметром DrbdOptions/auto-diskful. При включённом auto-diskful-allow-cleanup после синхронизации можно удалить лишнюю Secondary-реплику, соблюдая заданное количество копий.

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

Кроме того, требования клиентов бывают разными. Одному нужно разместить всё в пределах одной площадки. Другому — распределить ресурсы между дата-центрами. Это уже часть политики размещения сервиса. Для таких требований у нас есть cozystack-scheduler и абстракция SchedulingClass. Требование к размещению задаётся на уровне сервиса; дальше его должен реализовать механизм планирования.

Такие компоненты мы стараемся делать пригодными для отдельного использования. Если решённая задача полезна и за пределами Cozystack, хочется, чтобы результат можно было переиспользовать.

Виртуальные машины заставили пересмотреть сеть

Изначально мы пробовали строить сеть на Cilium. Он хорошо закрывает многие задачи Kubernetes, но при работе с KubeVirt мы столкнулись с требованиями, которые отличаются от обычного контейнерного сценария.

Виртуальная машина — это полноценная гостевая операционная система. Она может сохранять настройки интерфейсов и ожидать стабильную сетевую идентичность. Например, гостевая Ubuntu после первого запуска может привязать конфигурацию к MAC-адресу. Если после перезапуска окружение интерфейса изменилось, внутри VM появляются проблемы, которых пользователь облака совершенно не ожидает.

Другая задача — live migration. Виртуальная машина должна переехать на другой физический сервер, сохранив сетевую связность. Сеть должна позволять это сделать без привязки адреса VM к конкретному узлу.

Для этой части нам подошёл Kube-OVN. Он даёт необходимые механизмы работы с адресами и MAC-адресами и позволяет организовать сеть для перемещения виртуальных машин между узлами.

В отличие от модели с отдельными диапазонами Pod IP, закреплёнными за узлами, нам нужна адресация, допускающая перемещение нагрузки между серверами. Kube-OVN мы используем как сетевой транспорт для Pod'ов и виртуальных машин. Он позволяет сохранить нужную VM сетевую идентичность при её перемещении.

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

Почему нам важно сохранить модель Kubernetes

Можно было бы объявить стандартную сеть Kubernetes служебной, а всю пользовательскую сеть построить отдельно: через дополнительные интерфейсы и независимые механизмы управления.

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

Для нас важно, чтобы управление сетью оставалось частью стандартного Kubernetes API. Мы не хотим изобретать параллельный API, который работает в обход привычных инструментов. Пользователь должен иметь возможность управлять сетевыми ресурсами теми же средствами, которыми он уже управляет остальной инфраструктурой Kubernetes.

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

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

Это развивающаяся часть архитектуры; не все обсуждавшиеся возможности уже доступны пользователям.

Исходники — lllamnyp/cozyplane, пример работы — демонстрация CozyPlane.

Как я разделяю сеть Kubernetes

Вопрос из зала: если пользователь создаёт Service типа LoadBalancer во вложенном Kubernetes, каким образом туда придёт трафик? Чтобы ответить, сначала полезно разделить четыре разных сетевых механизма.

Сеть физических узлов

Первый уровень — Node network. Узлы должны обмениваться трафиком через физическую сеть. Это может быть сеть провайдера или собственная схема коммутации и маршрутизации.

Если серверы не могут связаться друг с другом, CNI не исправит саму инфраструктурную проблему. И наоборот: наличие связи между узлами ещё не доказывает, что работает сеть Pod'ов.

Сеть Pod и роль CNI

При запуске Pod создаётся отдельное сетевое пространство имён. В типичном показанном на слайде сценарии оно связано с хостом через пару veth. CNI настраивает подключение нагрузки и необходимую связность.

Сеть узлов и сеть Pod

Сеть узлов и сеть Pod

Физическая связность узлов и связность Pod — два разных уровня. CNI связывает сетевые пространства нагрузок с кластерной сетью.

В объяснении я отдельно обращал внимание на направления: Pod должен достигать другого Pod, в том числе на другом узле; узлы и Pod'ы также должны взаимодействовать в рамках сетевой модели кластера. Этим Kubernetes отличается от привычной схемы, где гипервизор и гостевые машины живут в полностью отдельных сетях.

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

Важны и системные нагрузки с hostNetwork. Не всё в Kubernetes запускается в обычной сети Pod'ов: отдельные компоненты используют сеть узла. Поэтому связность между этими уровнями нужна для штатной работы платформы.

Services не являются отдельной физической сетью

Теперь представим, что приложение работало в pod3. Pod удалили, на его месте появился pod5, и адрес нового экземпляра изменился. Использовать IP конкретного Pod как постоянный адрес приложения нельзя.

Для этого существует Service: он даёт стабильную точку обращения, а сервисный механизм направляет трафик к актуальным экземплярам приложения. В обычной реализации эту роль выполняет kube-proxy; в другой конфигурации — соответствующий механизм Cilium.

Обработка сервиса на узле

Обработка сервиса на узле

Сервисный механизм работает на узлах и направляет трафик к выбранным Pod. Он не требует единственного центрального сервера для всех Services.

На слайде эта часть называется Service network. Но это не ещё один физический интерфейс или отдельный VLAN. Это слой адресации и обработки пакетов. Он работает поверх уже существующей связности и позволяет отделить адрес сервиса от жизненного цикла конкретного Pod.

Как трафик приходит снаружи

Четвёртая задача — внешняя балансировка. Работающий ClusterIP внутри кластера сам по себе не обеспечивает доступ внешнему клиенту.

В облаке пользователь заказывает Service типа LoadBalancer, а cloud-интеграция создаёт или настраивает внешний балансировщик. Затем трафик направляется в кластер и доставляется к нагрузке.

Внешний балансировщик и сервисный слой

Внешний балансировщик и сервисный слой

Внешняя балансировка доставляет трафик к кластеру; сервисный слой направляет его к нужной нагрузке.

На собственном железе такую функцию тоже нужно реализовать. Мы используем MetalLB и интегрируем его с сетью площадки. Внешние адреса могут приходить через L2 или маршрутизируемую схему; это зависит от окружения.

Для вложенного Kubernetes эта задача связывается с нижележащей платформой через cloud controller manager. Пользователь создаёт LoadBalancer в своём API, а управляющий компонент обеспечивает соответствующий ресурс в инфраструктуре. Поэтому «запустить API-сервер Kubernetes» и «предоставить managed Kubernetes с работающим LoadBalancer» — разные объёмы работы.

Проверять эти четыре уровня нужно отдельно: связь узлов, связь Pod'ов, обработку Services и доставку внешнего трафика. Успешная проверка одного уровня ещё не доказывает работоспособность следующего.

Входящий и исходящий трафик разные истории

Особенно интересные проблемы появляются, когда у сервера несколько интерфейсов: один для внутренних коммуникаций, другой для хранения, третий для внешней сети.

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

Для таких сценариев мы используем policy routing. У нас есть контроллер, который помогает настраивать соответствующие правила на узлах: определённый трафик должен выходить через определённый маршрут и интерфейс.

На узле это означает работу с правилами выбора таблицы маршрутизации и самими маршрутами. Входящий путь и выход по default route не обязаны совпадать, поэтому несколько интерфейсов нельзя считать решённой задачей только после проверки входящего соединения.

Сам контроллер — проприетарный. Механизмы source-based и policy-based routing я раньше разбирал в статье «Тонкая настройка маршрутизации для MetalLB в режиме L2».

Пользовательский API поверх Flux

Следующий вопрос — как дать пользователю возможность заказывать сервисы.

Можно открыть ему ресурсы всех установленных операторов. Но тогда у каждого сервиса будет собственный интерфейс, собственный набор настроек и собственные особенности. Кроме того, некоторые настройки нельзя безопасно отдавать клиенту.

Например, возможность изменить образ контейнера у управляемой базы данных фактически может превратиться в возможность запустить произвольный код в management-кластере. Для внутреннего администратора это обычная настройка, для пользователя облака — уже нарушение границы доступа.

Поэтому мы предоставляем отдельный верхнеуровневый API. В нём пользователь описывает сервис и выбирает те параметры, которые платформа разрешает менять. А низкоуровневые ресурсы операторов остаются частью реализации.

В Kubernetes есть разные способы расширить API. Один из самых известных — CRD и контроллеры. Мы также используем механизм агрегации API: регистрируем собственный API-сервер, который обслуживает определённую группу ресурсов.

Здесь полезная аналогия — metrics-server. Ресурсы его API доступны через Kubernetes API, но обслуживает запросы отдельный компонент. Аналогично мы регистрируем свою API-группу через APIService, и kube-apiserver перенаправляет запросы соответствующему серверу.

Cozystack API Server в Kubernetes Aggregation Layer

Cozystack API Server в Kubernetes Aggregation Layer

Группа apps.cozystack.io обслуживается отдельным API-сервером, который работает с HelmRelease. Операторы и Helm controller продолжают выполнять свои задачи через Kubernetes API.

Поэтому отсутствие CRD для конкретного ресурса в нашей группе не означает, что этот ресурс не существует. В демонстрации я как раз показывал различие: нужно смотреть не только CustomResourceDefinition, но и зарегистрированные APIService.

Пользователь видит обычные Kubernetes-объекты. С ними можно работать привычными инструментами и применять стандартную модель разграничения доступа. Но за ними стоит наша логика представления приложений поверх Flux и HelmRelease.

Путь запроса выглядит так: пользователь создаёт объект сервиса в API Cozystack; наш API связывает его с параметрами приложения и ресурсом Flux; Helm chart разворачивает низкоуровневые объекты; их жизненный цикл обслуживают специализированные операторы.

Это разделение ответственности: API Cozystack определяет, что пользователь вправе заказать, Flux доставляет описание, а оператор приложения отвечает за специфическую логику самого сервиса. Исходный код платформы доступен в cozystack/cozystack.

Подробное объяснение этой модели — в статье о Cozystack API и Kubernetes Aggregation Layer.

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

Каталог приложений должен расширяться независимо от платформы

Из этого следует ещё одна идея: каталог сервисов не должен навсегда оставаться жёстко зашитым в платформу.

Мы движемся к модели подключаемых репозиториев приложений. Хочется, чтобы можно было собрать собственный набор сервисов, подключить его к Cozystack и предоставлять пользователям через тот же API.

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

Здесь важно сохранить целостность пользовательского интерфейса. Подключение нового оператора должно сопровождаться описанием того, что именно пользователь заказывает и какие гарантии платформа готова дать.

Мониторинг и доступ часть готового сервиса

На обзорных слайдах мониторинг вынесен в отдельный блок, потому что для managed-сервиса он должен появляться вместе с приложением.

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

Для базы данных в эту же модель входят управление пользователями и правами, настройка автоматических резервных копий и оповещений. Именно такие возможности превращают установленный оператор в сервис, которым можно пользоваться.

Отдельная часть — управление идентификацией и доступом. В презентации показана интеграция с Keycloak, LDAP, Active Directory и OIDC-провайдерами. Пользователь может иметь доступ к нескольким тенантам, поэтому учётная запись и границы инфраструктурных ресурсов не должны быть одной и той же сущностью.

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

Тенанты рекурсивная модель и разные границы изоляции

В Cozystack мы используем рекурсивную модель тенантов. Есть корневой тенант, внутри него можно создавать другие, разграничивать доступ и размещать приложения.

На уровне реализации тенант связан с Kubernetes namespace. Но namespace сам по себе не отвечает на все вопросы изоляции.

Рекурсивная структура тенантов и их компонентов

Рекурсивная структура тенантов и их компонентов

Компоненты могут выделяться на разных уровнях дерева тенантов.

На слайдах это показано деревом: tenant-root, вложенный tenant-foo и ещё один уровень — tenant-bar. Рядом с тенантами размещены инфраструктурные компоненты и приложения. Так можно показать одновременно организационную структуру и то, какие сервисы выделены на конкретном уровне.

Здесь важно различать soft multitenancy и hard multitenancy — разные требования к границе между тенантами.

В сценарии soft multitenancy пользователи разделяют общую инфраструктуру, а доступ и ресурсы разграничиваются на уровне Kubernetes: namespace, RBAC, квоты и сетевые политики. Например, пользователь заказывает управляемую базу данных. Он управляет разрешёнными параметрами, а платформа запускает известную нам нагрузку с контролируемой конфигурацией.

В сценарии hard multitenancy мы исходим из того, что независимые пользователи могут запускать собственный код: произвольные контейнеры, GitLab или любое другое приложение. Здесь нужна более строгая граница изоляции. Для этого пользователь получает отдельный Kubernetes-кластер с worker-узлами в виртуальных машинах либо отдельную виртуальную машину.

Мы не считаем контейнерную изоляцию достаточной универсальной границей между независимыми пользователями, которым разрешено запускать что угодно. Поэтому административные права внутри пользовательского Kubernetes не означают административных прав в management-кластере.

Общие сервисы и выделенные компоненты

Некоторые инфраструктурные функции можно использовать совместно, а некоторые — выделять конкретному тенанту. Например, возникает вопрос: нужен ли ему собственный ingress или отдельная система мониторинга?

Пользователь при этом должен видеть услугу на соответствующем уровне абстракции. Заказывая S3 bucket, он не обязан разбираться, какой именно компонент реализует объектное хранилище. Это решение платформы.

Сетевая изоляция и разрешённые взаимодействия между тенантами обеспечиваются политиками. Здесь снова становится полезной единая сетевая модель: её можно использовать для управления доступом между сервисами.

Proxmox и модель managed Kubernetes в облаке

Вопрос из зала: как ты относишься к Talos в виртуальных машинах на Proxmox?

Я много раз видел такой сетап, и мне он нравится. Это вполне рабочая связка, с которой можно идти в production.

Что мне не нравится — когда внутри этих VM появляется инфраструктурное состояние, например SDS-хранилище. В моём понимании Kubernetes-машины должны быть эфемерными: их можно удалить и пересоздать, сохранив данные отдельно. Proxmox ближе к pet-модели, в которой конкретную VM обслуживают как отдельный сервер. Поэтому вопрос не столько в возможности запустить Talos, сколько в том, какие жизненные циклы и зависимости мы создаём.

В традиционной виртуализации администратор часто работает с конкретными физическими серверами и VM. Он видит топологию, выбирает размещение, устанавливает гостевую ОС и дальше обслуживает её как отдельный сервер.

В облачной модели пользователь заказывает ресурсы и сервисы. Ему нужно определённое количество CPU, памяти и диска, а решение о конкретном физическом размещении принимает платформа. При необходимости пользователь задаёт ограничения — например, площадку или класс хранения, — но не обязан вручную связывать каждую VM с дисками и серверами.

Из Proxmox можно строить интеграции для Kubernetes. В разговоре я упоминал CSI-драйвер, который позволяет Kubernetes заказывать тома в такой инфраструктуре. Сам по себе этот драйвер, однако, закрывает только storage-интеграцию: для облачной модели нужны и остальные механизмы.

Упомянутый драйвер — sergelogvinov/proxmox-csi-plugin.

Control plane compute и storage обслуживаются отдельно

На слайде с типичной схемой Kubernetes в облаке control plane нарисован отдельным блоком, рабочие узлы — отдельными машинами, PersistentVolume — отдельно от них. Внешний балансировщик тоже имеет собственный жизненный цикл.

Типичная схема managed Kubernetes в облаке

Типичная схема managed Kubernetes в облаке

Control plane, рабочие узлы, постоянные тома и внешний балансировщик — разные обслуживаемые сущности.

«Отдельно» здесь означает прежде всего разные жизненные циклы и границы ответственности. Пользовательские данные не должны исчезать при замене worker-ноды, а control plane не должен требовать существования конкретной рабочей машины. Это не требование купить физически отдельное железо для каждого блока схемы.

На слайдах я показывал, что рабочие узлы могут исчезнуть: сначала одна нода, затем несколько, затем все. В облачной модели это штатная ситуация. Control plane и постоянные тома при этом продолжают существовать независимо от worker-нод.

После появления новых worker-нод кластер может снова исполнять нагрузки, используя сохранившиеся данные. Пользовательские приложения, конечно, не работают, пока нет вычислительных узлов, но потеря workers сама по себе не должна означать потерю кластера и его дисков.

Именно поэтому я говорю, что worker-ноды managed Kubernetes должны быть заменяемыми. На них не должно находиться единственное состояние, без которого кластер нельзя восстановить.

Почему на bare metal сложнее

В самостоятельном bare-metal кластере легко совместить на одних узлах control plane, пользовательские приложения и локальное хранилище. Такой дизайн может быть оправдан, но при отказе сервера затрагивает сразу несколько уровней системы.

Для managed Kubernetes хотелось бы дать пользователю другую модель: управление кластером обслуживается отдельно, вычислительные узлы можно заменить, а постоянные диски предоставляет нижележащая инфраструктура. Это позволяет обновлять, добавлять и удалять ноды, не превращая каждую операцию в ручное спасение приложения.

У самой платформы всё равно есть физические узлы и связанные с ними отказы. Но мы контролируем набор нагрузок management-кластера и проектируем их под эти условия. А внутри пользовательского кластера предоставляем обычные Kubernetes-интерфейсы: Node, PersistentVolumeClaim, Service, группы рабочих узлов.

Cluster API и Kubernetes внутри Kubernetes

Теперь посмотрим, как из одного приложения в каталоге получается пользовательский Kubernetes-кластер.

В Cozystack есть management-кластер, внутри которого работают управляющие компоненты платформы. Пользовательский кластер имеет собственный Kubernetes API и собственные worker-ноды. Административные права пользователя распространяются на его кластер.

Control plane пользовательского кластера мы запускаем с помощью Kamaji. Это оператор hosted control planes: компоненты control plane работают как Pod'ы внутри management-кластера. На митапе я показывал такие Pod'ы с контейнерами управляющей части.

Worker-ноды создаются как виртуальные машины через KubeVirt. Для пользователя это обычные Kubernetes-ноды, к которым подключается kubelet. Снаружи мы можем видеть соответствующую VM, подключаться к её консоли и управлять её жизненным циклом.

Приложение создаёт набор декларативных ресурсов

Пользовательский ресурс Kubernetes в каталоге — верхнеуровневое описание. За ним стоит Helm chart, создающий набор ресурсов, в том числе объекты Cluster API.

Cluster API — проект управления жизненным циклом кластеров через Kubernetes API. Его контроллеры работают в management-кластере и следят за декларативными объектами кластеров и машин.

Объекты Cluster API и три группы провайдеров

Объекты Cluster API и три группы провайдеров

На слайде показаны KamajiControlPlane, KubevirtCluster, KubevirtMachineTemplate и KubeadmConfigTemplate. Каждый провайдер обслуживает свою часть модели.

Три роли провайдеров

У Cluster API есть три важных для этого объяснения типа провайдеров.

Control plane provider отвечает за управляющую часть кластера. В показанной схеме это KamajiControlPlane. Соответствующий провайдер Kamaji связывает модель Cluster API с hosted control plane.

Infrastructure provider отвечает за инфраструктурные объекты — например, виртуальные машины. В нашем случае на схеме показаны KubevirtCluster и KubevirtMachineTemplate, а реализацию предоставляет Cluster API Provider KubeVirt.

Bootstrap provider подготавливает конфигурацию, с которой созданная машина сможет стать узлом нужного Kubernetes-кластера. На этих слайдах показан KubeadmConfigTemplate.

Именно эту последнюю часть легко пропустить. Создать VM недостаточно: её гостевой системе нужно знать endpoint API-сервера, параметры подключения и способ инициализации kubelet. Bootstrap provider формирует необходимые данные и сохраняет их в Secret, который затем используется при создании машины.

В зависимости от ОС bootstrap-механизм может отличаться. На слайдах я объяснял вариант с kubeadm; у Talos есть отдельный bootstrap provider. Эти варианты нельзя смешивать в одну конфигурацию только потому, что сверху у них одинаковый объект Cluster.

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

MachineDeployment MachineSet и Machine

Дальше на слайдах я раскрывал цепочку управления рабочими узлами.

MachineDeployment описывает группу машин и желаемое состояние этой группы. Под ним появляется MachineSet, а тот управляет отдельными объектами Machine.

Связь похожа на знакомую модель Deployment → ReplicaSet → Pod, только предмет управления здесь — машины Kubernetes-кластера.

Machine связан с инфраструктурным объектом провайдера. В случае KubeVirt дальше появляются объекты виртуализации: VirtualMachine, работающий экземпляр VirtualMachineInstance и Pod, в котором исполняется виртуальная машина.

От MachineDeployment к Pod виртуальной машины

От MachineDeployment к Pod виртуальной машины

Верхняя цепочка управляет группой Kubernetes-машин; нижняя показывает, как конкретная машина исполняется через KubeVirt.

Это не означает, что гостевой Kubernetes считает свой worker обычным Pod management-кластера. У каждого уровня собственное представление. В management-кластере мы видим объекты Cluster API и виртуализации, а внутри пользовательского — объект Node.

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

Один management-кластер обслуживает несколько пользовательских

На следующей схеме один management-кластер через Cluster API управляет несколькими tenant Kubernetes-кластерами.

Management-кластер и пользовательские кластеры

Management-кластер и пользовательские кластеры

Cluster API управляет жизненным циклом кластеров, но cloud-интеграции и дополнительные компоненты нужно настроить отдельно.

После создания нод работа ещё не закончена. Требуются как минимум cloud controller manager, CSI и Cluster Autoscaler. На слайде они перечислены рядом с вопросом о настройке пользовательских кластеров.

Cluster API создаёт и обслуживает кластерные машины. Но он сам по себе не гарантирует, что пользовательский Service типа LoadBalancer получит внешний адрес или что PVC будет обеспечен диском нижележащей платформы. Для этого нужны соответствующие интеграции.

Что делают CCM CSI и Cluster Autoscaler

Cloud controller manager, или CCM, связывает Kubernetes с инфраструктурой. В обсуждении мы говорили о двух конкретных задачах: обслуживании LoadBalancer и согласовании состояния нод с существованием виртуальных машин. Если VM больше не существует, соответствующая нода не должна бесконечно оставаться в кластере как потерянный объект. Для KubeVirt есть cloud-provider-kubevirt.

CSI обеспечивает работу с постоянными дисками. Пользователь создаёт PVC внутри своего кластера, а интеграция должна выделить и подключить том, предоставляемый нижележащей инфраструктурой. Здесь используется KubeVirt CSI driver. На митапе я также упоминал файловые тома ReadWriteMany: для них используется схема с NFS-сервером, которую обслуживает платформа.

Cluster Autoscaler меняет количество worker-нод в соответствии с потребностями кластера и возможностями групп машин. Чтобы пользователь получил реальное увеличение вычислительных ресурсов, он должен быть связан с механизмом создания нод, а не просто видеть неразмещённые Pod'ы.

Это разные контроллеры и разные задачи. Они вместе превращают запущенный Kubernetes в облачный сервис.

Где исполняются управляющие компоненты

Размещение компонентов managed Kubernetes

Размещение компонентов managed Kubernetes

Управляющие компоненты располагаются со стороны платформы; CNI и node-часть CSI работают на пользовательском worker.

Такое размещение важно для границы доступа. Пользователь является администратором своего Kubernetes. Если принести туда секреты для управления management-кластером, он потенциально сможет ими воспользоваться.

Поэтому компонент, которому нужны права на нижележащую инфраструктуру, можно оставить снаружи пользовательского кластера. Он обращается к нужным API, но его инфраструктурные credentials не становятся обычным Secret в среде, полностью подконтрольной клиенту.

Node-часть CSI при этом должна работать рядом с workload и подключать диски на узле. CNI тоже настраивает сеть на worker. То есть «вынести контроллеры наружу» не означает, что внутри пользовательского кластера вообще нет служебных компонентов.

Доставку конфигурации и компонентов мы связываем через Flux. На слайдах отдельно показано, что часть ресурсов применяется в management-кластере, а часть — в tenant-кластере. Место выполнения определяется тем, какие API и какие права нужны компоненту.

Пользователь в итоге получает kubeconfig, обычный Kubernetes API, постоянные тома и работающие балансировщики. А платформа обслуживает цепочку от его декларативного заказа до Pod'ов control plane, рабочих VM и инфраструктурных интеграций.

Установка в закрытом контуре

Ещё один практический вопрос — можно ли установить платформу без доступа к внешним сервисам.

Для деплоя платформы в air-gap единственная внешняя зависимость — хранилище образов и артефактов, например Nexus. «Внешняя» здесь означает внешняя по отношению к Cozystack: сам registry может находиться внутри закрытого контура и не иметь доступа в интернет.

Мы заранее доставляем туда необходимые образы и артефакты, а конфигурацию направляем на этот источник. Talos поддерживает зеркала registry, а артефакты развёртывания можно распространять через OCI registry. Так установка не зависит от доступности набора внешних репозиториев и сервисов.

Учёт ресурсов отдельный слой

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

В Enterprise-версии у нас есть компонент учёта потребления, который предоставляет отчёты через Kubernetes API. На слайдах видно, как это устроено: клиент отправляет UsageReport в группе billing.aenix.io/v1alpha1, а billing-apiserver обращается к VictoriaMetrics.

Запрос отчёта о потреблении ресурсов

Запрос отчёта о потреблении ресурсов

Запрос задаёт tenant, workload и временной интервал через startTimestamp и endTimestamp.

В ответе появляется report.consumers: записи связаны с тенантом, конкретной нагрузкой, её типом и интервалом. Внутри consumptions перечислены количества ресурсов. В примере это vCPUHours, MemoryGiBHours и EphemeralStorageGiBHours — ресурсы с учётом времени, а не просто мгновенный снимок CPU и памяти.

Ответ API с потреблением отдельных нагрузок

Ответ API с потреблением отдельных нагрузок

Отчёт содержит потребление ClickHouse и worker-машины managed Kubernetes.

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

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

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

Bare-metal provisioning

Для предоставления физических серверов рассматриваем Tinkerbell. Это отдельная задача: управлять подготовкой машины, установкой системы и передачей её пользователю. На момент разговора этот сценарий не был готовой реализованной функцией платформы.

GPU проброс, контейнеры и совместное использование

В задачах с GPU важно различать проброс устройства в виртуальную машину, использование GPU контейнерными нагрузками и разделение устройства на части. В разговоре я упоминал HAMi: он предоставляет механизмы совместного использования GPU контейнерными нагрузками через Kubernetes. Это другой уровень, чем передача GPU виртуальной машине. Наличие одного механизма не означает автоматической поддержки всех остальных. Здесь остаются вопросы к драйверам, аппаратным возможностям и конкретному способу интеграции.

Практические шаги описаны в документации Cozystack: GPU passthrough для виртуальных машин и GPU для контейнерных нагрузок.

Как мы работаем с заказчиками

Вопрос из зала: что именно вы поддерживаете и как устроена работа с заказчиком?

Мы предоставляем поддержку решения целиком: Cozystack, его компонентов и их интеграции в инфраструктуру заказчика. Нас интересует работоспособность всей системы, поэтому поддержка охватывает и то, как платформа взаимодействует с хранилищем, сетью и остальным окружением.

При этом мы заранее разделяем ответственность: договариваемся, какую часть инфраструктуры обслуживаем мы, а какая остаётся у команды заказчика. Для согласованного объёма предоставляем поддержку с гарантиями по SLA — SLA-guaranteed support.

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

AI ускорил разработку. Как теперь проверять результат 

В конце митапа разговор неожиданно ушёл от сетей и хранилища к организации разработки. Но для инфраструктурного проекта это вполне связанная тема.

Мы активно используем AI-инструменты, и это очень сильно повысило производительность команды.

Вместе со скоростью изменилось и узкое место. Написать код становится проще, а определить, что именно нужно сделать, правильно спроектировать изменение и проверить результат — по-прежнему сложно.

Особенно в инфраструктуре. Ошибка здесь может проявиться только при отказе узла, редкой последовательности событий или восстановлении после сбоя. Убедительный код и успешный простой запуск ещё не доказывают, что механизм корректен.

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

Один из упомянутых открытых проектов — lexfrei/ccc.

Ответственность при этом остаётся у людей. Автор изменения и тот, кто его проверяет, должны понимать, почему решение корректно. Если код написан с помощью агента, это не освобождает от необходимости проверить интерфейсы, поведение и последствия изменения.

Внутри команды есть люди, отвечающие за определённые направления. Например, важно, чтобы кто-то последовательно следил за устройством API и соответствием его Kubernetes-модели, а не только за тем, решает ли отдельный патч локальную задачу.

Мы также смотрим в сторону организации работы через Special Interest Groups (SIG) по постоянным направлениям и временные Working Groups под конкретные бизнес-задачи. У направления есть ответственность за свою часть системы; у проекта — задача собрать нужных участников и довести изменение до результата.

Для планирования работы мы используем Aeman — нашу доску поверх GitHub Projects. Она помогает видеть задачи конкретного инженера и команды, определять приоритеты и связывать ежедневную работу с бизнес-задачами.

У Aeman есть MCP-интерфейс, поэтому в планировании участвуют и AI-агенты: через него можно работать с задачами и обновлять план. Подробнее о том, как мы это устроили, рассказали в статье на Хабре; исходный код доступен на GitHub.

Где посмотреть код и продолжить обсуждение

Платформа развивается открыто. Cozystack находится в CNCF Sandbox, и для нас важно участие других компаний и независимых разработчиков. Хочется, чтобы продукт мог развиваться шире одной команды и одного коммерческого предложения.

Исходный код платформы — github.com/cozystack/cozystack, сайт проекта — cozystack.io. В тексте выше оставлены ссылки на отдельные технологии и провайдеры, чтобы можно было перейти от схемы к их реализации.

Присоединиться к встречам и найти календарь можно на странице сообщества Cozystack. Формат встреч и порядок добавления тем описаны в community_meeting.md.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.