Kubernetes называют инструментом автоматизации. Ирония в том, что первые годы его эксплуатации в облаке — это история ручного труда: сертификаты, которые никто не обновлял, кластеры, которые деградировали молча, клиенты, которые ждали поддержки там, где мы предполагали самообслуживание. Мы начали делать managed Kubernetes в 2018 году, когда на российском рынке этого сервиса почти не существовало. Эта статья о том, что значит слово «managed» на самом деле — и какую архитектуру оно в итоге требует.

Привет, Хабр! Меня зовут Алексей Ефимов, я ведущий инженер-разработчик K2 Cloud. На K2Cloud Conf’26 я прочитал доклад «Строили, строили и наконец построили: три итерации KaaS в K2Cloud». Эта статья, созданная по его следам, про то, как мы делали свой managed Kubernetes, на какие грабли наступали, как вообще пришли к этой идее — сделать свой KaaS, как менялись наши ожидания и ожидания клиентов. И в итоге, как и почему мы сделали своё решение KubeInsception.
Начало эта история берёт в 2018 году.
Точка отсчёта: 2018 год и рынок, которого не было
Идея сделать Kubernetes-сервис в K2Cloud появилась не из продуктовой стратегии — её предложил наш нынешний CTO Кирилл Бойко в коридорном разговоре. Инициативу приняли быстро: рынок был почти пустой, конкурентов практически не было, а значит, и готовых ответов на вопрос «как это делать правильно» — тоже.
Поэтому мы пошли за ответами к коллегам по рынку. Выяснилось, что managed Kubernetes в российских облаках находился в зачаточном состоянии: где-то Preview, где-то закрытая бета, где-то не было вообще ничего. Единого понимания, каким должен быть этот сервис, не существовало ни у кого.
Тогда мы пошли к клиентам. Их ответ был предсказуемо осторожным: «Поставьте что-нибудь базовое, мы потрогаем и скажем, чего хотим». Это был честный ответ — клиенты сами не знали своих требований, потому что инструмент был для них новым. Мы приняли этот ответ как руководство к действию: двигаться итерациями, не пытаясь предсказать финальную архитектуру заранее.
Первое решение, которое определило всё последующее, — деплоить Kubernetes прямо в VPC клиента. То есть в ту же инфраструктуру, где клиент запускает виртуальные машины, создаёт подсети и размещает свои сервисы. Это был осознанный выбор в пользу простоты: клиент видит кластер в своей инфраструктуре, интеграция понятна, барьер входа минимален.

Архитектура первой версии строилась на четырёх компонентах, каждый из которых решал конкретную задачу.
Мастер-узлы с Control Plane мы распределили по трём зонам доступности — именно столько зон было в московском регионе на тот момент. Это давало отказоустойчивость: потеря одной зоны не роняла управляющий слой кластера. Рабочие узлы, где размещается нагрузка клиента, распределялись между зонами по необходимости.
Для хранения метаданных кластера использовали etcd — стандартное для Kubernetes решение. Лёгкая и быстрая база данных, в которой хранятся состояния контейнеров, манифесты и всё, что нужно кластеру для работы.
Мониторинг состояния кластеров потребовал собственной разработки: готового инструмента, который решал нашу задачу, не нашлось. Мы написали Cluster Manager — агентский компонент, который устанавливается на все мастер-узлы. Он проверяет состояние кластера, запрашивает у облака необходимые действия и выполняет их. Через него же реализовали масштабирование рабочих узлов вверх и вниз.
Четвёртый компонент — CSI-плагин (Container Storage Interface). Он нужен для того, чтобы Kubernetes мог обращаться к облаку напрямую: запрашивать создание диска, подключать его к виртуальной машине и прокидывать внутрь пода как Persistent Volume. Без этого интеграция с облачным хранилищем потребовала бы ручных операций при каждом запросе на диск.

Отдельного решения потребовал вопрос деплоя внутрь виртуальных машин. Мы с самого начала отказались от SSH-доступа к машинам клиента — это было принципиальным решением с точки зрения безопасности и доверия. Ansible, который был основным инструментом деплоя в 2018 году, без такого доступа не работал.
Выход нашли в Cloud-init. Это модульный инструмент инициализации виртуальных машин, который получает конфигурацию через metadata-сервис — специальный эндпоинт, доступный с любой виртуальной машины в облаке. Через него машина узнаёт о себе всё необходимое: тип инстанса, подключённые диски, сетевые интерфейсы, зону доступности. Мы написали собственный модуль Cloud-init, который через тот же механизм передавал кластеру его роль, версию Kubernetes и эндпоинты облачных сервисов. Внутри виртуальной машины при запуске скачивались сертификаты и конфигурации, разворачивался etcd-кластер, через kubeadm деплоился Control Plane, устанавливались сетевые компоненты, хранилище и Cluster Manager.

Сертификаты заслуживают отдельного упоминания, потому что позже они станут источником первых серьёзных проблем. Kubernetes без сертификатов не существует — они нужны для того, чтобы мастер-узлы узнавали друг друга и рабочие узлы, чтобы контейнеры могли обращаться к API-серверу. Поэтому выпуск сертификатов был первым шагом при создании любого кластера. Тогда мы не придали этому особого значения. Но через год поняли, что напрасно.
Первые грабли: сертификаты и конфликт ожиданий
Первый год прошёл спокойно. Кластеры работали, клиенты осваивались, команда накапливала опыт. Мы задеплоили Kubernetes, обложили его базовым мониторингом и передали клиентам. Казалось, модель работает.
Проблема была не в том, что мы сделали что-то неправильно технически. Проблема была в том, чего мы не сделали вовсе — и не заметили этого, потому что первые клиенты не заметили тоже.

Те, кто пришёл к нам с самого начала, были технически подготовлены и готовы к самостоятельной работе. Они понимали, что Kubernetes требует обслуживания, следили за сертификатами и обновляли их сами. Для нас это создало ложное ощущение, что модель самообслуживания работает.
Новые клиенты думали иначе. Они видели «Kubernetes как сервис» и ожидали, что слово «сервис» означает полное обслуживание со стороны провайдера. Сертификаты, по их логике, — это часть инфраструктуры, а инфраструктура — это наша ответственность. Они никогда не формулировали это требование явно, потому что считали его очевидным.
Мы тоже не формулировали обратного — по той же причине.
Через год это расхождение материализовалось. В части кластеров сертификаты истекли, и кластеры начали деградировать. Не падать — именно деградировать, что хуже: нагрузка продолжала работать, поды оставались запущенными, но добавить новый рабочий узел было уже невозможно. Узел не мог присоединиться к кластеру, потому что не мог пройти проверку сертификата. Кластер продолжал жить, но терял способность к изменению — а значит, к масштабированию, к восстановлению после отказа узла, к любым операциям, требующим изменения состава кластера.
Смотря на это из 2026 года, легко сказать, что ошибка очевидна. В 2019-м она не была очевидной — не потому что команда была невнимательной, а потому что граница ответственности не была проведена явно. Ни внутри, ни с клиентами.
Решение оказалось технически несложным. Мы написали компонент Cert-manager, который отслеживает срок действия сертификатов и обновляет их автоматически до истечения. Автовосстановление мастер-узлов при этом сделали отключаемым — для тех клиентов, которые предпочитают контролировать такие операции самостоятельно.

Но важнее технического решения был организационный вывод, который мы сделали и который с тех пор определяет, как мы думаем о продукте. Слово «managed» означает разные вещи в зависимости от того, кто его произносит. Для инженера, который строит сервис, «managed» — это автоматизированный деплой и базовый мониторинг. Для клиента, который его покупает, «managed» — это «я больше не думаю об этом». Пока эти два определения не совпадут, между ними будет накапливаться долг — технический и репутационный одновременно.
Именно с этого момента мы начали отсчитывать путь к архитектуре, в которой провайдер может обслуживать кластер полностью — незаметно для клиента и без запроса его доступов. Правда до неё оставалось ещё несколько лет и несколько итераций.
Клиентов стало больше: внедрение Node Groups и EKS API
Пока мы разбирались с сертификатами, рынок менялся. Kubernetes переставал быть инструментом энтузиастов и становился производственной необходимостью. Клиентов становилось больше, их инфраструктуры — разнообразнее, а запросы — конкретнее.
Два из них повторялись достаточно часто, чтобы стать архитектурными требованиями.
Первый: возможность конфигурировать рабочие узлы по-разному в рамках одного кластера. До этого момента все узлы были однородными. Но реальные инфраструктуры устроены иначе: базы данных требуют машин с быстрыми дисками, вычислительно интенсивные задачи — CPU-оптимизированных конфигураций, задачи с большими объёмами данных — машин с увеличенной памятью. Запускать всё на одинаковых узлах означало либо переплачивать за ресурсы, которые не нужны, либо мириться с деградацией производительности там, где нужны специализированные машины.
Второй запрос был неожиданным: совместимость с Amazon EKS API. Часть клиентов уже работала с инструментами экосистемы AWS и хотела использовать их с нашим кластером без переписывания пайплайнов. Это был сигнал о том, что Kubernetes-сервис перестал быть изолированным продуктом — он должен был встраиваться в существующие инженерные процессы клиента.
Оба запроса закрыли одним архитектурным решением — Node Groups. Группы узлов позволяют в рамках одного кластера определять несколько пулов рабочих узлов с разными конфигурациями виртуальных машин. Нагрузка распределяется по группам в зависимости от требований приложения. API при этом выровняли под EKS — не для совместимости ради совместимости, а чтобы инструменты, написанные под Amazon, работали у нас без модификаций.

Вместе с Node Groups в архитектуру пришёл Auto Scaling. К тому моменту в облаке уже вышел собственный механизм автомасштабирования, и мы интегрировали его с группами узлов сразу: кластер мог автоматически добавлять и убирать рабочие узлы в зависимости от нагрузки. Позже добавили интеграцию с Cluster Autoscaler — стандартным компонентом Kubernetes для управления масштабированием, — который позволил делать это ещё точнее, опираясь на реальное потребление ресурсов внутри кластера, а не только на внешние метрики.
Здесь проявился принцип, который с тех пор стал для нас рабочим правилом: каждая новая возможность облака интегрируется в Kubernetes-сервис как можно раньше — часто ещё на этапе бета-тестирования. Когда появились GPU, мы добавили GPU-оператор и преднастроили образы виртуальных машин с установленными драйверами — клиенту не нужно было разбираться с этим самостоятельно. Когда вышли сетевые балансировщики, интегрировали их в кластер, чтобы сервисы типа LoadBalancer работали нативно.
Логика за этим простая: Kubernetes-сервис, изолированный от остальных возможностей облака, — это не managed-сервис, а просто автоматизированный деплой. Ценность managed-решения именно в том, что клиент получает интеграции, не занимаясь ими самостоятельно.
К этому моменту архитектура заметно усложнилась по сравнению с первой версией 2018 года. Но усложнилась снаружи — со стороны провайдера. Для клиента картина оставалась прежней: кластер с рабочими узлами в его инфраструктуре. Следующая итерация изменила это соотношение радикально — и затронула уже не рабочие узлы, а Control Plane.
KubeInception: Control Plane уходит в контейнеры
К тому моменту, когда рынок окончательно созрел до запроса на полностью управляемый Kubernetes, у нас накопилось собственное понимание того, что в архитектуре не так.
Проблема была не функциональной — кластеры работали. Проблема была структурной: мастер-узлы с Control Plane жили в инфраструктуре клиента на выделенных виртуальных машинах, и эти машины большую часть времени простаивали. Пиковая нагрузка на Control Plane возникает в конкретные моменты — во время релизов, при активном логировании, при массовых изменениях состояния кластера. Всё остальное время мощности резервируются впустую. Платить за этот резерв приходилось постоянно, а использовался он эпизодически.

Но важнее экономики было другое: пока Control Plane находится в инфраструктуре клиента, провайдер не может обслуживать его без запроса доступов. Любое вмешательство — плановое обновление, экстренное восстановление, диагностика — требовало координации с клиентом. Это противоречило самой идее managed-сервиса.
Решение, к которому мы пришли, изменило топологию кластера принципиально: Control Plane покидает инфраструктуру клиента и переезжает в контейнеры внутри облачной инфраструктуры провайдера. У клиента остаются только рабочие узлы — виртуальные машины, на которых работает его нагрузка.
Как это устроено технически
Связь между рабочими узлами клиента и Control Plane провайдера обеспечивает специальный компонент — EKS Network Interface. Это сетевой интерфейс, который одним концом подключается к подсети клиента рядом с его виртуальными машинами, а другим — к контейнерам Control Plane в инфраструктуре провайдера. С точки зрения кластера топология остаётся прежней: рабочие узлы видят API-сервер, API-сервер видит узлы. Физически они теперь находятся в разных сегментах инфраструктуры, но это скрыто за сетевым интерфейсом.
Реализовать такую схему позволил переход на SDN пятью годами ранее — программно-определяемая сеть дала возможность строить подобные соединения без физических ограничений традиционной сетевой архитектуры.
Контейнеры Control Plane запускают те же компоненты, что раньше жили на мастер-узлах: API-сервер, scheduler, controller manager. Для хранения данных etcd используются те же облачные диски, что и в инфраструктуре клиентов, — это обеспечивает единый уровень надёжности и резервирования. Все вспомогательные контроллеры, которые раньше занимали ресурсы на рабочих узлах, также переехали на сторону провайдера.

Сами контейнеры Control Plane развёрнуты в Kubernetes — нашем внутреннем кластере, который управляет облачной инфраструктурой. Kubernetes для управления Kubernetes. Мы называем это KubeInception: у нас есть облако в облаке, и теперь там — куб в кубе.

Почему не готовое решение
Прежде чем писать своё, мы изучили существующие инструменты для hosted Control Plane. Наиболее зрелым на тот момент выглядел Kamaji. Но при детальном рассмотрении выяснилось, что он проектировался под приватные облака внутри компаний — там, где требования к безопасности и мультитенантности устроены иначе, чем в публичном облаке. Доработка под наши требования потребовала бы больше усилий, чем написание собственного решения с нужными характеристиками с нуля.
Мы выбрали второй путь — и не пожалели.
Что это изменило
Для клиента изменения выглядят минималистично: инфраструктура стала проще, Control Plane исчез из его VPC. На практике это означает, что провайдер теперь видит состояние кластеров на верхнем уровне и может реагировать на аварии без привлечения клиента — именно то, чего не хватало в предыдущих архитектурах. Плановые обновления, восстановление компонентов, диагностика — всё это происходит на стороне провайдера, не затрагивая рабочую нагрузку клиента.
Дополнительный эффект — сокращение точек отказа. Компоненты, которые раньше конкурировали за ресурсы с рабочей нагрузкой на узлах клиента, теперь вынесены в отдельную изолированную среду.
Нерешённый вопрос
Честность требует зафиксировать одну открытую инженерную задачу. Биллинг рабочих узлов и дисков etcd устроен понятно: есть виртуальные машины с определёнными характеристиками, есть диски с заранее известным размером — всё это тарифицируется стандартно. С Control Plane в контейнерной модели сложнее.
Контейнеры потребляют ресурсы динамически: нагрузка на API-сервер во время релиза и нагрузка в спокойное время отличаются на порядок. Как справедливо тарифицировать этот ресурс — через количество запросов к API-серверу, через количество нод, через какой-то иной показатель — вопрос, на который у нас пока нет окончательного ответа. До релиза новой архитектуры время на его решение ещё есть.
Роадмап, миграция и аддоны
Новая архитектура существует, но большинство действующих кластеров работают на предыдущей. Это стандартная ситуация для любого продукта, который эволюционирует под нагрузкой: нельзя остановить работающие системы клиентов ради архитектурного улучшения. Нужен инструмент, который позволяет перейти без остановки.
К концу года мы выпустим механизм миграции — для кластеров как на EKS-совместимой архитектуре с Node Groups, так и на Legacy-версии первых лет. Миграция переводит Control Plane из инфраструктуры клиента в контейнеры на стороне провайдера. Для клиента это означает всё то, о чём шла речь в предыдущем разделе: меньше компонентов в собственной инфраструктуре, полное обслуживание Control Plane без передачи доступов, видимость состояния кластера на стороне провайдера.
Второе изменение касается расширяемости кластера. Сейчас добавление нового компонента — например, Ingress-контроллера — требует пересоздания кластера или ручных операций. Это создаёт трение именно там, где его быть не должно: при стандартных операциях, которые клиент хочет выполнять быстро и без погружения в детали. В новой архитектуре появится механизм аддонов — компонентов, которые устанавливаются в кластер на лету, без пересоздания и без простоя. Аддоны можно будет добавлять по мере выпуска новых версий, независимо от жизненного цикла самого кластера.
Отдельно стоит упомянуть cluster-autoscaler — компонент автоматического масштабирования рабочих узлов, который мы используем в собственном облаке. Он уже опубликован в открытом доступе: если вы строите собственный или поддерживаете приватное облако, его можно взять и использовать самостоятельно. Документация с инструкцией по установке доступна в нашей документации.
Что касается геораспределения — в рамках московского региона архитектура уже работает с распределением по зонам доступности. Расширение на петербургский регион технически не требует принципиальных изменений: та же архитектура разворачивается в новом регионе. Вопрос не в том, как это сделать, а в том, когда это станет приоритетом.
Мы намеренно не превращаем этот раздел в список обещаний. Роадмап — это направление, а не контракт. Семь лет разработки этого сервиса научили нас одному: самые важные изменения редко бывают теми, которые планировались заранее. Чаще они приходят от клиентов, от инцидентов и от столкновения архитектурных решений с реальной нагрузкой. Именно так появился Self-manager, именно так появились Node Groups, именно так появился KubeInception.
Заключение
Вернёмся к тому, с чего начали. В 2018 году мы думали, что Kubernetes — это задача деплоя. Оказалось, что деплой — это только начало разговора. Настоящая задача — эксплуатация: обновление сертификатов, масштабирование узлов, обслуживание Control Plane, реакция на аварии без привлечения клиента. Всё то, что клиент не видит и именно поэтому считает само собой разумеющимся.
Семь лет и несколько архитектурных итераций привели к простому выводу, который, впрочем, легко формулируется только постфактум.
Слово «managed» — это обязательство, а не описание. Пока провайдер и клиент не договорились явно о том, что оно означает, между ними накапливается долг. Технический — в виде деградирующих кластеров. Репутационный — в виде разрыва между ожиданием и реальностью. Архитектурный — в виде решений, которые казались достаточными, но не учитывали, как клиент на самом деле использует сервис.
Каждая итерация, которую мы прошли, была ответом именно на этот долг. Cert-manager закрыл разрыв в ответственности за сертификаты. Node Groups закрыли разрыв между однородной инфраструктурой провайдера и разнородными требованиями клиентов. KubeInception закрыл главный разрыв: пока Control Plane живёт в инфраструктуре клиента, провайдер не может обслуживать его по-настоящему незаметно.
Если обобщить этот опыт в выводы, применимые за пределами нашей истории, они выглядят так.

Kubernetes — это эксплуатация, а не деплой. Это звучит как трюизм, но большинство команд приходят к этому пониманию через собственные инциденты, а не через чужой опыт. Лучше через чужой.
Граница ответственности между провайдером и клиентом должна быть проведена явно и зафиксирована до того, как что-то сломается. Неявная граница — это не отсутствие границы, это граница, которую каждая сторона рисует по-своему.
Если вы строите собственную платформу — заведите в ней Kubernetes для управления инфраструктурой. Не потому что это модно, а потому что это работает как генератор требований изнутри: каждая новая возможность платформы немедленно проверяется на практике, каждое ограничение становится очевидным раньше, чем о нём сообщит клиент.
И последнее. Архитектура, которую мы описали в этой статье, — не финальная точка. Открытый вопрос с биллингом Control Plane, миграция действующих кластеров, аддоны — всё это работа, которая ещё впереди. Но именно так и выглядит живой продукт: не законченная схема, а система, которая продолжает отвечать на вопросы, которые ей задаёт реальность.

