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

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

Выдать разработчикам доступ к production-виртуализации или широкие права в Kubernetes тоже нельзя. Пользователю нужен ограниченный набор операций над своими VM, а платформе – контроль шаблонов, сети, ресурсов, владельцев и границ контура.

Так появилась идея внутреннего dev cloud:

KubeVirt            -> виртуализация внутри dev-кластера
kubevirt-manager    -> self-service, policy и пользовательские сценарии
Keycloak            -> идентификация и группы доступа
Prometheus          -> метрики и первичная диагностика

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

Это не попытка заменить Harvester, OpenShift Virtualization или большой корпоративный cloud portal. Получился компактный продукт для конкретного процесса: дать техническим пользователям самостоятельность в dev-контуре, но не превратить их в администраторов Kubernetes.

Все имена ВМ, команд, нод, домены, IP-адреса, registry и пользовательские идентификаторы на скриншотах заменены. Сценарии и устройство интерфейса сохранены.

Что получилось

kubevirt-manager – Go-приложение, которое формирует HTML-страницы на сервере и использует небольшое количество JavaScript для интерактивных операций. Оно собирает несколько Kubernetes-сущностей в понятный пользователю объект «виртуальная машина».

Пользовательская часть закрывает почти весь ежедневный lifecycle:

  • создание VM из подготовленного шаблона;

  • выбор CPU, RAM, режима хранения, SSH key и начальных портов;

  • список VM со статусами, IP, доменами, портами, ресурсами и нодами;

  • поиск и фильтры по состоянию и команде;

  • start, stop, restart и migration;

  • resize CPU/RAM с явным предупреждением о downtime;

  • управление опубликованными портами;

  • установка root-пароля через guest-agent;

  • Events, логи, метрики и serial-консоль в браузере.

Администратор получает отдельный контур:

  • обзор всех VM и их владельцев;

  • управление прикладными группами доступа;

  • привязку одной VM к нескольким командам;

  • allocatable-ёмкость KubeVirt-нод;

  • заявку vCPU/RAM запущенных VM;

  • переподписку по каждой ноде;

  • потребление ресурсов в разрезе команд.

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

Зачем отдельный dev-контур

Production-виртуализация обычно оптимизирована под стабильные и долгоживущие нагрузки. Там оправданы change management, согласования и осторожный lifecycle. Разработка живёт в другом ритме: сегодня нужен тестовый PostgreSQL, завтра стенд для новой версии сервиса, через неделю всё это можно удалить.

Если временные dev-VM размещать рядом с production-нагрузкой, постепенно смешиваются и ресурсы, и ответственность:

  • временные стенды становятся долгоживущими;

  • учёт разработки смешивается с production capacity;

  • эксперименты наследуют production-регламенты и теряют скорость;

  • cleanup зависит от дисциплины отдельных людей;

  • ошибка в экспериментальном стенде получает ненужный blast radius.

Отдельный dev cloud задаёт более подходящий контракт. Платформа предоставляет конечный объём CPU, RAM и storage, поддерживаемые шаблоны и сетевую модель. Команды получают бюджеты и внутри них сами решают, какие стенды важнее прямо сейчас.

Такой подход даёт разработчику пространство для экспериментов, но оставляет эти эксперименты внутри dev-контура. Production-виртуализация продолжает решать production-задачи.

Почему не просто kubectl

Для платформенного инженера команды kubectl get vm, virtctl console и kubectl edit svc выглядят естественно. Для self-service это слишком широкий и слишком низкоуровневый интерфейс.

За одной карточкой VM скрывается сразу несколько слоёв:

  • VirtualMachine хранит желаемую конфигурацию, а VirtualMachineInstance — текущее состояние запущенного экземпляра;

  • системный диск может быть постоянным, через DataVolume и PersistentVolumeClaim, или временным — прямо из контейнерного Docker/OCI-образа;

  • временный вариант удобен для одноразовых стендов: после остановки и нового запуска VM возвращается к исходному образу;

  • cloud-init подготавливает машину под конкретный сценарий: создаёт пользователя, добавляет SSH key и выполняет начальную настройку;

  • Service публикует IP и порты, Secret хранит данные доступа, а lifecycle и console работают через отдельные API.

Выдать человеку права на произвольное редактирование этих объектов сложнее и опаснее, чем разрешить небольшой набор проверенных операций. Веб-панель здесь является не только удобным интерфейсом, но и дополнительной policy boundary: backend валидирует поля, проверяет владельца и вызывает только поддерживаемые сценарии.

Целевая аудитория при этом остаётся технической. Интерфейс не скрывает CPU, RAM, сетевые порты, guest-agent, migration или downtime. Он скрывает лишнюю связность между CRD и необходимость помнить правильную последовательность команд.

Архитектура

Основным source of truth остаётся Kubernetes:

браузер
  |
  | HTML forms / fetch / WebSocket
  v
Go HTTP server
  |
  +--> Keycloak OIDC: пользователь, сессия, группы
  |
  +--> kubevirt client-go: VM, VMI, lifecycle, migration, console
  |
  +--> kubernetes client-go: Service, Secret, PVC, Events, Pod logs
  |
  +--> Prometheus HTTP API: CPU, RAM, network

Отдельной базы данных пока нет. VM, владельцы, сервисы, диски и группы доступа описаны Kubernetes-объектами. Локальная память используется только для короткоживущего состояния выполняющихся операций.

Формирование HTML на сервере здесь оказалось практичным выбором. Доступы проверяются ещё до построения страницы, а небольшой JS-слой отвечает за фильтры, модальные окна, обновление статуса и WebSocket-консоль. В итоге и интерфейс, и вся серверная логика живут в одном Go-приложении: не понадобились отдельное frontend-приложение, дополнительный API для него и дублирование моделей данных.

Создание VM: сценарий вместо YAML

Пользователь не загружает произвольный YAML и не собирает VM из инфраструктурных деталей. Он выбирает готовый сценарий, который поддерживает платформа.

Такой шаблон определяет сразу несколько вещей:

  • базовый образ и операционную систему;

  • постоянный диск для долгоживущего стенда или временный диск из Docker/OCI-образа;

  • cloud-init для нужной начальной настройки;

  • допустимую сетевую и ресурсную конфигурацию.

После выбора шаблона остаются только параметры конкретного экземпляра: имя VM, CPU, RAM, SSH public key, размер диска для постоянного варианта и начальные порты. Команду-владельца пользователь не выбирает — сервис определяет её автоматически по данным авторизованной сессии в cookie. Нельзя случайно отнести VM к чужой команде или исказить учёт ресурсов выбором другого владельца.

После нажатия Create платформа сама подготавливает диск, доступ, сетевой адрес и VM. Пользователь получает готовую машину, а не набор Kubernetes-объектов, которые нужно связывать и проверять вручную.

Шаблоны стали удобной точкой контроля для платформенной команды. Через них можно добавлять новые образы и сценарии cloud-init, менять инфраструктурные настройки и при этом сохранять для разработчика короткий и предсказуемый путь создания VM.

Сеть как часть VM

Созданная VM получает понятную точку входа: IP-адрес, DNS-имя и набор опубликованных портов. Всё это видно в её карточке рядом со статусом и ресурсами.

Открывать и закрывать порты можно там же. Для пользователя сеть является свойством его VM; устройство Kubernetes Service и права на работу с ним остаются внутри платформы.

Доступ к сетевым операциям автоматически следует за доступом к самой VM. В результате разработчику не нужны отдельная заявка на каждый порт и широкие права в Kubernetes, а администратору не приходится поддерживать ещё один независимый набор разрешений.

Команды, доступы и бюджеты

Модель доступа строится вокруг команды, а не вокруг отдельных Kubernetes-объектов. Keycloak отвечает на вопрос «кто этот пользователь и в каких командах он состоит», привязка VM — «какие ресурсы ему доступны», а бюджет — «сколько ресурсов команда может использовать».

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

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

Считать стоимость VM в рублях нам не требовалось: внутренний dev cloud не является биллинговой системой, а ресурсы не продаются подразделениям. Под тарификацией мы понимали квотирование — каждой команде задаётся бюджет в vCPU и RAM, внутри которого она самостоятельно создаёт и изменяет свои стенды.

Бюджеты хранятся на уровне прикладных команд. Перед созданием VM сервис сопоставляет запрошенные CPU и RAM с уже занятым бюджетом команды. Если хотя бы один лимит будет превышен, операция останавливается до создания инфраструктурных объектов. Это не просто отчёт для администратора, а реальная граница self-service.

Такая же проверка срабатывает при увеличении ресурсов, но для resize учитывается только разница между текущей и новой конфигурацией. Если VM уже занимает 4 vCPU / 8 Gi, а пользователь увеличивает её до 6 vCPU / 16 Gi, дополнительный расход составляет 2 vCPU / 8 Gi, а не полный новый размер.

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

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

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

Страница VM как рабочее место

Dashboard нужен для быстрого обзора. Detail-страница собирает всё, что требуется для управления и первичной диагностики конкретной VM:

  • шаблон, ноду, IP, домен и порты;

  • состояние и наличие guest-agent;

  • CPU, RAM и network из Prometheus;

  • Kubernetes Events;

  • логи virt-launcher и гостевой dmesg;

  • serial-консоль через WebSocket и xterm.js;

  • resize, migration и управление группами;

  • установку root-пароля через guest-agent.

Пользователь может отличить «VM запущена» от «приложение внутри действительно работает», посмотреть, на какой ноде живёт инстанс, и увидеть события вокруг Service или IP announcement.

Serial-консоль даёт аварийный доступ без установки virtctl на рабочую станцию. Это особенно полезно, когда SSH ещё не поднялся или сетевые настройки внутри гостя повреждены.

В итоге detail-страница становится единой точкой первого разбора. Если информации недостаточно, пользователь уже приходит к инфраструктурной команде не с сообщением «VM не работает», а с конкретным состоянием, событием или графиком.

Что потребовалось для эксплуатационного качества

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

Быстрый dashboard без застоявшегося кэша

Первая реализация догружала Service, VMI, PVC и группы доступа отдельно для каждой VM. При 30 VM получалось порядка 120 дополнительных обращений к Kubernetes API на один рендер.

Вместо раннего перехода на informers приложение делает batch pre-fetch в начале HTTP-запроса:

VirtualMachines.List()         -> список VM
Services.List()                -> map[domainLabel]ServiceInfo
VirtualMachineInstances.List() -> map[vmName]VMI
PersistentVolumeClaims.List()  -> map[pvcName]diskSize
Access groups                  -> загружаются один раз

Дальше карточки собираются из lookup-карт в памяти. Это snapshot на один рендер, а не долгоживущий кэш: следующий refresh снова получает актуальные коллекции.

Для текущих десятков VM этого достаточно. При росте масштаба следующим шагом будут shared informers и SSE, но основную проблему сначала стоило решить более простым способом.

Видимое состояние долгих операций

Resize запущенной VM включает stop, ожидание состояния Stopped, обновление template spec, start и ожидание Running. Пользователь не должен всё это время смотреть на активную кнопку и гадать, принят ли запрос.

Для таких действий появился небольшой async-слой:

POST /resize/{vm}
  -> auth + validation + busy check
  -> register operation
  -> run in background

GET /op-status/{vm}
  -> phase + done + error + log

UI показывает фазы queued, stopping, applying, starting, done, блокирует несовместимые действия и не позволяет запустить второй resize для той же VM. В результате длительность операции остаётся технической реальностью, но перестаёт быть неопределённостью для пользователя.

Состояние Kubernetes нужно перечитывать

После stop контроллеры изменяют VM, поэтому обновлять объект, полученный до начала операции, нельзя: его resourceVersion уже может устареть. Перед Update backend повторно получает VM, а после Start ждёт фактического PrintableStatus=Running.

Та же логика нужна для конкурентных изменений Service: свежий Get, ограниченный RetryOnConflict и идемпотентная трактовка уже достигнутого состояния.

Именно этот слой отличает демонстрационный UI от инструмента, которому можно доверить повседневный self-service.

Куда развивать дальше

Следующий этап проекта связан уже не с добавлением ещё нескольких кнопок, а с масштабированием правил и эксплуатации:

Направление

Развитие

Kubernetes API

заменить runtime-вызов kubectl на server-side apply через client-go

live-состояние

перейти от snapshot на запрос к informers и SSE

несколько replica

вынести координацию операций из локальной памяти

безопасность

добавить CSRF tokens, расширенный аудит и управление секретами

надёжность

покрыть auth, ports и resize state machine fake-клиентами

Ещё одно ограничение – фиксированный namespace. Для одного dev-контура это упрощает модель, но multi-environment установка потребует явного namespace scope.

Текущая архитектура не мешает этим изменениям: Kubernetes остаётся source of truth, а пользовательские сценарии уже отделены от конкретных API-вызовов.

Что изменилось в процессе

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

выбрать шаблон
  -> создать VM с привязкой к команде
  -> получить IP и открыть порты
  -> управлять lifecycle и ресурсами
  -> смотреть Events, метрики и console
  -> удалить ненужный стенд

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

KubeVirt здесь дал строительные блоки, но ценность появилась на слое выше: когда VM стала цельным пользовательским объектом, а технические возможности превратились в поддерживаемые сценарии.

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

Комментарии (0)