Привет, Хабр!
Меня зовут Игорь Шишкин, я руковожу отделом разработки облачной платформы Рег.облака. Несколько лет назад, пока облако росло, интеграционный слой между сервисами и брендами естественным образом усложнялся. Каждый новый сценарий требовал отдельного решения — и со временем стало понятно, что нужен общий подход.

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

Навигация по тексту:

Сами сервисы — OpenStack, DBaaS, KaaS (Kubernetes as a Service)  — работали нормально и развивались независимо. Проблема была в другом: во внешнем доступе к ним. Если одному бренду нужна была новая функция, он писал интеграцию сам — даже если точно такую же задачу уже решили в соседнем бренде.

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

Зачем нам понадобился единый Platform API

Внутри облака инфраструктурными сервисами управляет МПУ — внутренний API который запускает услуги для клиентов и следит за их работой. При этом МПУ ничего не знает ни о брендах, ни о конкретных клиентах, ни о биллинге и ни даже о логической группировке ресусов: он получает техническую команду и выполняет ее.

Поверх этой инфраструктуры работают разные бренды. Раньше каждый интегрировался с МПУ сам: свой слой аутентификации, своя логика авторизации, своя модель тенантов, своя интеграция с учетом аккаунтинга и биллинга. Сами сервисы — OpenStack, DBaaS, KaaS — работали стабильно. Сложность накапливалась не в них, а на уровне доступа к ним: каждый бренд строил не просто собственный интерфейс к одной и той же платформе, а реализовывал всю логику группировки услуг, аккаунтинга, биллинга и реализацию интерфейсов доступа.

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

  • аутентификацию пользователей;

  • авторизацию действий;

  • унификацию тенантной модели;

  • ресурсный аккаунтинг;

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

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

Сразу заложили и внешние интерфейсы: cloudcli — единый CLI-инструмент для работы с сервисами — и Terraform-провайдер для декларативного управления всем облаком, а также SDK (пока только на Go) и описания protobuf’ов (Platform API реализует gRPC c пейлоадом в виде protobuf’ов), если кому-то хочется сделать клиент/инструмент не на Go.

Публичный и административный API

Когда мы начали проектировать Platform API, сразу встал простой вопрос: кто и как будет с ним работать.

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

Если всё это собрать в один интерфейс, управлять моделью доступа становится сложнее. Поэтому мы разделили Platform API на два контура:

public-api — для пользовательских операций над сервисами;
admin-api — для административных и служебных операции.

Разделение построено не по типу потребителя, а по типу сценария. Один и тот же интерфейс — например, личный кабинет бренда — может работать с обоими API. Пользователь создаёт кластер базы данных: личный кабинет выступает фронтендом и обращается к public-api. Нужна служебная операция или действие с расширенными правами — в ход идет admin-api.

Контуры физически разделены и доступны по разным адресам. Механизм аутентификации в обоих случаях одинаковый: связка access- и refresh-токенов. Базы пользователей при этом тоже разделены — клиентские аккаунты и административные хранятся отдельно.

Как устроена архитектура

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

  1. Клиент или внутренняя система отправляет запрос в Platform API.

  2. API проводит аутентификацию на уровне домена: проверяет, откуда пришёл запрос, и валидирует токен.

  3. Затем — авторизация с учётом домена и конкретного тенанта: есть ли у пользователя право выполнить это действие внутри тенанта.

  4. После проверок Platform API формирует техническую команду и передает ее в МПУ — модуль предоставления услуги.

Например, пользователь создаёт базу данных: Platform API передаёт команду в МПУ DBaaS, тот через оператор в Kubernetes инициирует создание инстанса. Об архитектуре DBaaS мы писали отдельно — здесь важно лишь то, что Platform API не управляет ресурсами в Kubernetes напрямую.

Если пользователь создает кластер Kubernetes, в дело вступает МПУ для KaaS — это разные МПУ. Шаги могут отличаться, но принцип тот же: Platform API отвечает за проверку, маршрутизацию и учет, МПУ — за реализацию сервиса на уровне инфраструктуры.

Как выполняются операции в Platform API

Все операции в Platform API асинхронны. Клиент не получает готовый результат в момент запроса — только идентификатор операции, по которому потом отслеживает выполнение.

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

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

Модель пользователей и тенантов

Platform API не хранит персональные данные пользователей. Внутри системы пользователь — это технический идентификатор. Имя, почта и всё остальное живет в системах брендов. Platform API оперирует только внутренним обезличенным ID.

Это не просто дополнительная мера безопасности, а архитектурный принцип. Platform API планируется как публично доступный сервис, поэтому минимизация чувствительных данных заложена в дизайн с самого начала. Чем меньше информации хранится в слое клиентского интерфейса, тем меньше поверхность атаки. Сам Platform API разворачивается в Kubernetes в виде контейнеров из distroless-образов без привилегий (capabilities: drop: ["ALL"]) — никаких лишних компонентов, библиотек или приложений, никаких расширенных прав на даже открытие соединения. Это снижает риски уже на уровне инфраструктуры.

Теперь о тенантах.

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

Вопрос переноса ресурсов между тенантами мы обсуждали. Основное ограничение здесь не на стороне Platform API, а глубже — в OpenStack, KaaS и DBaaS. Эти системы просто не поддерживают операцию перемещения ресурса из одного проекта в другой. Теоретически можно собрать процедуру с временной остановкой сервиса и пересозданием в другом тенанте, но это в большинстве случаев влечет даунтайм.

Аутентификация и ротация токенов

Platform API работает не только через Web-интерфейсы — ЛК брендов, но и через свои собственные инструменты и библиотеки — через CLI, SDK и Terraform-провайдер. Поэтому аутентификация должна работать без интерактивного входа и без постоянной передачи учетных данных пользователя. Мы используем классическую схему с access- и refresh-токенами:

  1. короткоживущий access-токен;

  2. более долгоживущий refresh-токен.

Access-токен используется в запросах и действует ограниченное время (единицы минут). Когда срок истекает, клиент (SDK) обращается к API с refresh-токеном и получает новую пару. Refresh-токен нельзя использовать для выполнения операций — только для обновления пары. Обмен происходит атомарно: при выдаче новых токенов старый refresh-токен сразу инвалидируется. Повторно использовать его уже не получится.

SDK берет эту механику на себя. Клиент (конкретное приложение, использующее SDK) работает с напрямую с gRPC и ничего не знает про токены и механики аутентификации, т.к. все это зашито в интерсепторы SDK и обновление access-токена происходит автоматически.

Почему мы не дублируем OpenStack API

Когда мы проектировали Platform API, встал вопрос: стоит ли сразу закрыть за ним OpenStack. Единая точка входа выглядит логично. Если все операции проходят через один слой, проще контролировать сценарии использования и не допускать действий, которые выходят за рамки продуктовой логики.

Для Рег.облака это особенно актуально. У нас уже сложился набор собственных сценариев работы с инфраструктурой. Если клиент идёт в OpenStack API напрямую, он может наткнуться на возможности и параметры, которые в эти сценарии не вписываются. Отсюда лишняя сложность и риск неконсистентного поведения.

Тем не менее мы приняли прагматичное решение: на первом этапе OpenStack не закрывать. Полное покрытие его API потребовало бы слишком много разработки. Нам было важнее быстро вынести общую логику — аутентификацию, тенантную модель, ресурсный учет — и унифицировать новые сервисы. Поэтому сейчас OpenStack там, где нужно, используется напрямую. Platform API его не ограничивает. Закрыть OpenStack через Platform API — возможное направление развития, но стартовым приоритетом оно не было.

Учет ресурсов без учета денег

Platform API фиксирует только фактическое потребление ресурсов: объем хранилища, количество инстансов, объем резервных копий и другие технические показатели.

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

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

Нотификации и синхронизация

Создание или изменение ресурса в Platform API — только начало. Об этом событии должны узнать другие системы: биллинг, продуктовые сервисы, внутренние панели. Для этого мы реализовали механизм нотификаций: при изменении состояния сущности Platform API формирует событие, на которое могут подписаться внешние системы.

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

Первый полностью реализованный сервис в Platform API — управление резервным копированием для инстансов OpenStack. Поддерживаются все сценарии, которые доступны в самом сервисе:

  • создание бэкапа вручную;

  • восстановление из бэкапа;

  • настройка политики хранения;

  • управление расписанием.

Это полноценная реализация, не урезанный прототип.

CLI, SDK и открытая публикация

В основе Platform API — gRPC и protobuf. SDK написан на Go. Для работы с API есть два CLI-инструмента: административный для внутренних задач и пользовательский для клиентов и интеграций.

С самого начала мы проектировали API с расчетом опубликовать клиентские части под свободной лицензией. В планах — открыть proto-файлы публичных API, SDK, CLI и Terraform-провайдер. Последний станет ключевым инструментом для тех, кто хочет управлять инфраструктурой декларативно. Мы хотим, чтобы Platform API был не просто бекендом, но и позволял пользователям нашего облака расширять способы взаимодействия за счет реализации CI/CD, инфраструктурных пайплайнов, интеграций, IaC.

Что дальше и зачем это всё

Platform API — не разовый эксперимент. Мы строим на его основе новую модель подключения сервисов, а после добавим управление S3. Речь не о замене AWS-совместимого API — а о том, чтобы управлять S3 как сервисом платформы через Platform API, CloudCLI и Terraform. 

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

Platform API помог навести порядок в управлении инфраструктурой. Мы отделили слой доступа и учёта от конкретных реализаций сервисов, не стали дублировать зрелые инструменты вроде OpenStack и заложили основу для автоматизации через SDK, CLI и Terraform. Для нас это шаг к предсказуемой и расширяемой платформе. Для тех, кто интегрируется с облаком, — меньше разнородных интерфейсов и больше прозрачности.

Если тема построения платформенных API вам близка или вы решаете похожие задачи — буду рад обсудить в комментариях.