Самообслуживание: от заявки в тикете к платформе, которой пользуются
Инженеру нужна новая тема в Kafka. Он открывает Jira, находит проект INFRA, выбирает тип «Запрос ресурса», заполняет восемь полей, из которых на четыре он не знает ответа, и нажимает «Создать». Через шесть рабочих дней тема появляется.
Работа на стороне платформы заняла сорок минут: посмотреть заявку, дописать три строки в манифест, применить, проверить. Остальные 47 часов — очередь, уточнения, ожидание окна изменений и время, пока заявитель заметил ответ. Соотношение 40 минут к 48 часам — не признак ленивой платформенной команды, а нормальная физика очередей: когда между потребностью и её удовлетворением стоит человек с собственным списком приоритетов, время ожидания определяется загрузкой этого человека, а не сложностью задачи. И именно это соотношение делает разговор о самообслуживании не вопросом удобства, а вопросом денег.
Эта глава — про то, как из очереди тикетов получается самообслуживание, во что оно обходится, и почему построенное самообслуживание регулярно оказывается никому не нужным. Она опирается на «Платформу как продукт» (у неё есть пользователи), на «Золотой путь» (у неё есть проложенная дорога) и на «Опыт разработчика» (это измеряется). Здесь мы разбираем механику: что именно надо построить, чтобы человек исчез из критического пути, и как понять, что построенным пользуются.
Что такое самообслуживание и что им не является
Определение стоит дать узкое, иначе термин расползётся до «у нас есть портал».
Самообслуживание — это свойство операции, при котором инженер продуктовой команды получает нужный результат без появления человека из другой команды в критическом пути.
Три слова здесь несут вес.
- Операции, а не платформы целиком. Самообслуживание — свойство каждой конкретной операции по отдельности, а не бинарное состояние компании. Полностью самообслуживаемый деплой рядом с заявкой на доступ к продовой базе через тикет — нормальная, осознанная конфигурация.
- Человека в критическом пути, а не человека вообще. Асинхронное ревью, которое не блокирует, — самообслуживание. Проверка политикой, отклоняющая запрос за две секунды, — самообслуживание. Кнопка «Одобрить» у дежурного платформы, без которой ничего не поедет, — тикет с красивым интерфейсом.
- Результат, а не заявка. Форма, которая создаёт тикет, — это более удобный тикет. Разница видна на метрике: время до результата не изменилось.
Пять уровней, между которыми выбирают
| Уровень | Как выглядит | Время до результата | Кто отвечает за корректность |
|---|---|---|---|
| L0. Анархия | каждая команда сама делает что хочет прямо в облаке | минуты | никто; выясняется на аудите |
| L1. Тикет | заявка в очередь, платформа делает руками | дни | платформа, вручную |
| L2. Заявка с автоматикой | форма запускает пайплайн, но нужно одобрение | часы | платформа, через одобрение |
| L3. Самообслуживание с политикой | инженер применяет сам, политика проверяет автоматически | минуты | политика как код |
| L4. Инвариант | ресурс появляется сам из описания сервиса | нет запроса вовсе | платформа, через шаблон |
Важная и неочевидная вещь: L0 не хуже L1 по всем осям. Анархия быстрее тикета и часто быстрее плохого L3. Именно поэтому команды в неё сползают, когда платформа тормозит: обход — не саботаж, а рациональный выбор при заданных издержках. Задача платформы не в том, чтобы закрыть L0 запретом, а в том, чтобы L3 был быстрее и удобнее, чем L0. Это точка воздействия на уровне правил игры, а не на уровне параметров.
Обратите внимание и на L4. Самая дешёвая заявка — та, которой нет. Если у сервиса в описании указано, что он читает из темы orders.v2, тема должна появиться сама вместе с правами и алертами; отдельная операция «создать тему» тут лишняя. Это переход от «запроса ресурса» к декларативному описанию сервиса, и по возможности стоит целиться сразу туда.
Что вообще отдают в самообслуживание
Список неслучайно упорядочен по убыванию обратимости. Об этом — ниже, потому что обратимость и есть главный критерий, что можно безопасно отдать.
Цена заявки: арифметика вместо жалоб
Разговор «у нас всё через тикеты, это плохо» бесполезен, пока у него нет числа. Число получают из журнала тикетов за квартал — данные уже есть в трекере, их достаточно выгрузить.
Возьмём условную компанию: 12 продуктовых команд, около 90 инженеров, платформенная команда из трёх человек. За квартал в очередь платформы пришло 612 заявок. Разложим их по типам операций.
| Операция | Штук за квартал | Медиана ожидания | Работа платформы | Обратима | Нужно суждение |
|---|---|---|---|---|---|
| Новый сервис из шаблона | 22 | 4,0 дня | 90 мин | да | нет |
| Тема или очередь | 96 | 1,5 дня | 40 мин | да | нет |
| Изменение квоты CPU/памяти | 143 | 0,5 дня | 15 мин | да | нет |
| Превью-стенд на ветку | 88 | 1,0 дня | 25 мин | да | нет |
| Новый секрет или ротация | 71 | 2,0 дня | 20 мин | да | нет |
| Дашборд и алерт из шаблона | 64 | 3,0 дня | 45 мин | да | нет |
| Доступ к логам прода на время | 58 | 1,5 дня | 10 мин | да | частично |
| Схема в общей БД | 41 | 3,5 дня | 60 мин | нет | да |
| Новый домен и сертификат | 18 | 2,5 дня | 50 мин | да | нет |
| Доступ на запись в продовую БД | 11 | 5,0 дней | 30 мин | нет | да |
Считать надо две разные величины, и их постоянно путают.
Работа платформы — сколько часов команда платформы тратит на очередь. Здесь это 312 часов за квартал: около 23 % ёмкости команды из трёх человек. Это то, что видит руководитель платформы, и это заметная, но не катастрофическая цифра.
Потерянное время продуктовых команд — сколько ждут заявители. Здесь оно другого порядка. Ожидание само по себе не всегда простой: инженер переключается на другую задачу. Но переключение стоит денег, и незавершённая работа копится. Практичная оценка: считать не всё время ожидания, а стоимость переключения — примерно 20–30 минут потерянного контекста на каждый цикл «отправил заявку — вернулся к другой работе — получил ответ — вспомнил, зачем это было». При двух циклах уточнений на заявку получается около часа на заявку, то есть 612 часов за квартал. Плюс задержка поставки, которую в часах не измерить, но которая видна в lead time изменений (см. DORA-метрики и «Метрики продукта»).
Итого: очередь тикетов стоит компании порядка 920 инженерных часов в квартал, из которых платформа видит только 312. Это и есть главная причина, по которой очередь живёт годами: издержки несёт одна сторона, а решение принимает другая. Классическая локальная оптимизация: у платформы всё в порядке, SLA по тикетам выполняется, 90 % закрыто в срок.
Что автоматизировать первым
Не самое неприятное, а то, где произведение частоты на потери максимально, а стоимость автоматизации умеренна. Простой скрипт по выгрузке из трекера:
"""Приоритизация операций для перевода в самообслуживание.
Вход — журнал тикетов за квартал; выход — операции, упорядоченные по тому,
за сколько месяцев окупится разработка самообслуживания для каждой."""
from dataclasses import dataclass
SWITCH_COST_HOURS = 1.0 # потери на переключение контекста, часов на заявку
QUARTERS_PER_YEAR = 4
@dataclass
class Operation:
name: str
count_per_quarter: int
platform_minutes: int # ручная работа платформы на одну заявку
build_days: int # оценка разработки самообслуживания, человеко-дней
reversible: bool # можно ли откатить результат без последствий
needs_judgement: bool # требуется ли человеческое решение по существу
def annual_savings_hours(op: Operation) -> float:
"""Часы в год, которые вернёт перевод операции в самообслуживание."""
per_quarter = op.count_per_quarter * (
op.platform_minutes / 60 + SWITCH_COST_HOURS
)
return per_quarter * QUARTERS_PER_YEAR
def payback_months(op: Operation) -> float:
"""За сколько месяцев окупится разработка. inf — не окупится в горизонте."""
saved = annual_savings_hours(op)
if saved <= 0:
return float("inf")
cost_hours = op.build_days * 8 * 1.6 # 1,6 — поддержка и доработки в первый год
return cost_hours / (saved / 12)
def rank(ops: list[Operation]) -> list[tuple[Operation, float, float]]:
"""Ранжирование по окупаемости. Исключаются операции, требующие суждения,
и необратимые: у них автоматизируется подготовка, но не само действие."""
candidates = [op for op in ops if not op.needs_judgement and op.reversible]
scored = [(op, annual_savings_hours(op), payback_months(op)) for op in candidates]
scored.sort(key=lambda row: row[2]) # сначала самые быстро окупаемые
return scored
journal = [
Operation("квота CPU/память", 143, 15, 4, True, False),
Operation("тема или очередь", 96, 40, 8, True, False),
Operation("превью-стенд", 88, 25, 15, True, False),
Operation("секрет и ротация", 71, 20, 10, True, False),
Operation("дашборд и алерт", 64, 45, 6, True, False),
Operation("схема в общей БД", 41, 60, 20, False, True),
Operation("доступ на запись в прод", 11, 30, 12, False, True),
]
for op, saved, months in rank(journal):
print(f"{op.name:24} {saved:7.0f} ч/год окупаемость {months:4.1f} мес")
Вывод для этих данных: квоты (715 ч/год, окупаемость 0,9 месяца), темы (640 ч/год, 1,9), дашборды (448 ч/год, 2,1), секреты (379 ч/год, 4,1), превью-стенды (499 ч/год, 4,6). Порядок не совпадает ни с частотой заявок, ни с ощущением «что больше всего бесит»: превью-стенды раздражают сильнее всего, но окупаются впятеро медленнее квот. Схема в БД и доступ на запись отфильтрованы — там нужно суждение, и автоматизировать надо не решение, а подготовку к нему.
Сложность: сортировка даёт O(n log n) по времени и O(n) по памяти, где n — число типов операций (десятки). Вычислительно задача тривиальна; ценность скрипта в том, что он превращает спор о приоритетах в таблицу с числами. Подробнее про эту арифметику — в главе «Рутина и автоматизация» трека SRE, там же разобраны признаки рутины и ловушки её измерения.
Две поправки, без которых числа врут:
- Автоматизация не убирает работу целиком. Самообслуживание требует поддержки: разбор непонятных отказов, обновления, миграции. Закладывайте 15–25 % исходной ручной работы как остаточную.
- Часть заявок исчезнет, а часть — размножится. Когда квоту можно поднять за минуту, её начинают поднимать в десять раз чаще. Это не баг: раньше люди терпели неудобство, потому что заявка была дороже. Но в расчёте экономии на это надо закладываться, а в лимитах — предусматривать потолок.
Цена платформенной команды: почему это не бесплатно
Здесь самая честная и самая пропускаемая часть разговора. Платформенная команда не пишет продукт. Три инженера платформы — это три инженера, не сделавших фичи. Значит, платформа обязана возвращать больше инженерного времени, чем потребляет, иначе она — налог, распределённый по всем командам и оформленный как «инвестиция в инфраструктуру».
Модель простая, и её стоит держать в голове при каждом решении о новой функции платформы:
- стоимость — ёмкость платформенной команды: 3 инженера × 150 рабочих часов = 450 часов в месяц. Она почти не зависит от числа обслуживаемых команд;
- экономия — число команд, реально пользующихся платформой, умноженное на сэкономленные часы на команду в месяц;
- точка окупаемости — там, где вторая величина превысила первую.
При 30 сэкономленных часах на команду в месяц окупаемость наступает на 15 команде. При принятии в 40 % экономия на команду падает до 12 часов, и точка окупаемости уезжает за 37 команд — то есть в компании из двенадцати команд платформа не окупится никогда, сколько бы функций ни добавили. Наклон линии экономии определяется принятием, а не полнотой функций. Отсюда практический вывод, который в этом треке повторяется многократно: новая функция сдвигает точку окупаемости вправо (растёт стоимость), рост принятия — влево. Если приходится выбирать, выбирайте принятие.
Из той же модели следуют три неудобных вывода.
- В маленькой компании выделенная платформенная команда не окупается. При трёх-четырёх продуктовых командах экономия не покроет стоимость даже одного выделенного инженера. Это не значит, что не нужно самообслуживание: нужно, но как побочный продукт работы продуктовых команд — общие шаблоны, общий пайплайн, тонкий слой скриптов. Подробно — в «Платформе в небольшой компании».
- Функция, которой пользуется одна команда, почти всегда убыточна. Экономия делится на одну команду, стоимость поддержки — постоянная и навсегда. Такие вещи должна писать сама команда, а платформа — дать точку расширения.
- Стоимость владения инструментами входит в стоимость платформы. Кластер Kubernetes — это регулярные обновления control plane, CVE в CNI, миграции API-версий: по опыту это 0,3–1 человека постоянно, независимо от того, сколько сервисов в нём крутится («Kubernetes»). Backstage — это не портал из коробки, а фреймворк на TypeScript, который вы форкаете и поддерживаете: обновления ломают плагины, каталог требует автозаполнения из реальных источников, реалистичная оценка — 1–1,5 человека постоянно (backstage.io/docs). Terraform и Crossplane — это state, дрейф, права и разбор «почему план хочет пересоздать базу» («Terraform и IaC»). Ни один из этих инструментов не плох; плохо брать их, не назвав цену вслух и не заложив её в расчёт.
Разговор про деньги — не разовое упражнение перед защитой бюджета, а постоянная практика; ей посвящена глава «Стоимость».
Из чего состоит самообслуживание: пять свойств
Самообслуживание — не «дать всем права». Это пять свойств, и без любого из них конструкция разваливается в предсказуемую сторону.
- Декларативный интерфейс. Пользователь описывает желаемое состояние, а не последовательность действий. Императивная команда «создай тему с тремя партициями» не отвечает на вопрос, что делать при повторном вызове, при частичном отказе и при изменении требований. Декларация отвечает: приводим к описанному состоянию, операция идемпотентна.
- Политика вместо ревью. Всё, что человек проверял бы глазами, формулируется как автоматическая проверка: лимиты, обязательные метки, запрет публичного доступа, требования к владельцу. Политика отвечает мгновенно и одинаково всем — в отличие от ревьюера, у которого пятница и плохое настроение. Цена: политики надо тестировать и версионировать, иначе однажды они заблокируют выкатку в неудачный момент по причине, которую никто не сможет объяснить.
- Обратимость. Ключевой критерий: самообслуживание безопасно ровно настолько, насколько дёшев откат. Создать превью-стенд — обратимо, отдавайте без разговоров. Удалить продовую базу — необратимо, здесь человек в критическом пути оправдан. Между ними — серая зона, где обратимость создаётся инженерно: soft delete, снапшот перед изменением, TTL на ресурс, окно отмены.
- Ограничители. Квоты на команду, потолок на размер ресурса, TTL на временные объекты, лимит расхода. Без них первое же самообслуживание превращается в неконтролируемый рост расхода, и следующим шагом менеджмент закрывает всё обратно в тикеты. Ограничитель — не недоверие, а условие, при котором доверие можно отдать без согласования каждый раз.
- Наблюдаемость запроса. Пользователь видит, что происходит с его запросом и почему он не прошёл, без похода в чат платформы. Отказ обязан быть читаемым: не
admission webhook denied the request, а «в манифесте нет меткиowner; добавьте её, см. ссылку». Именно этот пункт отличает работающее самообслуживание от формально существующего: если каждый второй отказ приводит к вопросу в чате, вы построили тикеты с лишним шагом.
Жизненный цикл запроса
Две вещи на этой схеме легко упустить при проектировании, и обе потом стоят дорого.
- Дрейф. Ресурс, созданный самообслуживанием, кто-нибудь однажды поправит руками через консоль облака. Без периодической сверки описания с фактом платформа теряет знание о происходящем и превращается в источник вранья. Сверка должна либо возвращать к описанию, либо явно помечать ресурс как «под ручным управлением» — оба варианта лучше молчания.
- Удаление. Самообслуживание без удаления — генератор мусора. TTL на временные ресурсы (превью-стенды, тестовые базы, временные доступы) обязателен с первого дня, иначе через полгода вы будете вручную разбирать 400 забытых стендов и всё это время их оплачивать.
Как это выглядит в коде
Декларация сервиса, из которой выводится всё остальное:
# service.yaml — единственный файл, который пишет продуктовая команда
apiVersion: platform.example.com/v1
kind: Service
metadata:
name: orders-api
labels:
owner: team-checkout # владелец обязателен: политика отклонит без него
tier: "1" # влияет на SLO, дежурство и лимиты расхода
spec:
runtime:
language: go
cpu: 500m
memory: 512Mi
replicas: { min: 2, max: 10 }
dependencies:
- kind: KafkaTopic # тема создастся сама, отдельная заявка не нужна
name: orders.v2
partitions: 6
retention: 7d
- kind: PostgresDatabase
name: orders
size: small # small/medium/large — за ними стоят проверенные профили
observability:
slo:
availability: 99.9 # из этого генерируются дашборд и алерты
latency_p99_ms: 300
escape:
raw_terraform: false # честный флаг выхода за золотой путь, см. ниже
Проверка политикой — вместо ревьюера:
# policy/platform-baseline.yaml — политика как код (синтаксис Kyverno)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: platform-baseline }
spec:
validationFailureAction: Enforce
rules:
- name: owner-label-required
match:
any: [{ resources: { kinds: ["platform.example.com/v1/Service"] } }]
validate:
# Сообщение пишется для человека, а не для лога: это часть интерфейса
message: >-
У сервиса нет метки owner. Укажите команду-владельца:
docs.example.com/platform/ownership
pattern:
metadata: { labels: { owner: "?*" } }
Тем же способом описываются ограничители: «больше 24 партиций в теме — не самообслуживание, нужна оценка нагрузки». Важно не правило, а текст отказа: он адресован человеку, содержит причину и следующий шаг. Сообщение политики — часть пользовательского интерфейса платформы, и писать его надо с тем же вниманием, что и текст кнопки.
Сессия инженера, который этим пользуется:
# Всё самообслуживание доступно из одной команды — её знают наизусть
$ plat new service orders-api --template go-http --owner team-checkout
создан каталог orders-api/ с service.yaml, Dockerfile и пайплайном
# Изменение ресурса — тот же путь, что и изменение кода: через pull request
$ vim service.yaml # replicas.max: 10 -> 20
# Локальная проверка политик до пуша: отказ должен приходить за секунды, а не в CI
$ plat validate
✗ 20 реплик × 500m = 10 CPU, квота team-checkout — 8 CPU
поднять квоту: plat quota request --team team-checkout --cpu 8
решение автоматическое в пределах бюджета команды
$ plat quota request --team team-checkout --cpu 8
✓ квота увеличена: 8 -> 16 CPU (в пределах месячного бюджета, 62 % использовано)
# Что происходит с моим запросом — видно без похода в чат платформы
$ plat status orders-api
service orders-api готов (обновлён 3 мин назад)
kafka-topic orders.v2 готов
database orders (small) создаётся ~2 мин
slo 99.9 / p99 300ms дашборд: grafana.example.com/d/orders-api
Ключевое в этой сессии — не команды, а то, что отказ приходит локально, за секунды, с готовым следующим шагом. Отказ в CI через восемь минут — это уже наполовину тикет. Про то, где такие проверки должны жить, — в главе «Конвейер как платформенный сервис».
Четыре поверхности: CLI, PR, API, портал
Самообслуживание доставляется через интерфейсы, и выбор интерфейса — не вопрос вкуса. У каждого своя ниша, своя цена владения и свой типовой провал.
| Поверхность | Кому и когда | Цена владения | Как проваливается |
|---|---|---|---|
| CLI | ежедневные операции инженера, быстрый цикл | сборка под три ОС, версии, автообновление, справка | ставится не у всех, версии расходятся, справка врёт |
| PR в репозиторий | изменения, которые нужно ревьюить и хранить в истории | шаблоны, боты, время прохождения пайплайна | 12-минутный пайплайн убивает цикл правок |
| API | автоматизация, интеграции, скрипты команд | версионирование, совместимость, аутентификация | нет версий — ломается всё сразу у всех |
| Портал (UI) | редкие операции, новички, обзор и поиск | фронтенд как продукт: дизайн, доступность, поддержка | делается первым и потому пустует |
Порядок в таблице не случаен. Портал — последняя поверхность, а не первая. Инженер, который каждый день деплоит, не пойдёт в браузер: у него терминал открыт. Портал выигрывает там, где операция редкая (раз в квартал — не вспомнишь синтаксис), где нужен обзор (кто владелец этого сервиса, что от него зависит) и где пользователь новый. Строить портал раньше CLI и PR-пути — самый популярный способ потратить полгода и получить страницу, куда заходят двенадцать человек в месяц, из них восемь — сама платформенная команда.
Отдельно про каталог сервисов, который обычно и есть повод завести портал. Каталог полезен ровно до того момента, пока он правдив. Каталог, заполняемый руками, устаревает за квартал и после этого вреден: он не просто бесполезен, он даёт неверные ответы на вопрос «кто владелец» в момент инцидента. Единственный работающий вариант — каталог как производная: владелец, зависимости, SLO и дежурство выводятся из service.yaml в репозитории и из фактических данных выкаток. Никакой отдельной формы «зарегистрируйте свой сервис в портале» быть не должно — это заявка, замаскированная под каталог.
Три способа провалить самообслуживание
Дальше — разбор провалов, которые встречаются чаще всего. Все три выглядят как работа, у всех трёх есть демо, и у всех трёх катастрофические метрики принятия.
Провал 1: обёртка над облаком, которая только мешает
Симптом: платформа предоставляет plat create database, за которым стоит вызов Terraform с шестью параметрами из сорока доступных. Команде нужен седьмой — репликация в другой регион. Ответ платформы: «добавим в следующем спринте». Команда идёт в консоль облака и делает руками.
Механика провала: абстракция без собственной модели. Обёртка не упрощает мышление, она лишь переименовывает поля и при этом отрезает 85 % возможностей исходного инструмента. Пользователь платит полную цену изучения новой абстракции, не получая взамен новых понятий, — и одновременно теряет доступ к документации облака, к StackOverflow и к своему прошлому опыту.
Признаки, что вы строите именно это:
- каждый новый параметр требует релиза платформы, и в бэклоге платформы очередь из параметров;
- документация платформы состоит из отсылок «см. документацию AWS/GCP по этому полю»;
- нет способа выйти за пределы обёртки, кроме как обойти платформу целиком;
- у абстракции нет ни одного понятия, которого нет в исходном API.
Что делать:
- Пропускать неизвестные параметры насквозь. Блок
raw:в манифесте, куда можно положить любое поле исходного провайдера. Да, это протечка абстракции; альтернатива — что вас обойдут целиком. - Скрывать не поля, а решения. Профиль
size: smallполезен не тем, что прячет три параметра, а тем, что за ним стоит выбор: тип диска, настройки бэкапа, окно обслуживания, лимиты соединений — то, о чём продуктовая команда не хочет думать и не должна. Это содержательная абстракция; подробно — в «Абстракциях». - Считать долю пользователей, ушедших в
raw:. Если 40 % манифестов используют сырые поля для одного и того же параметра — это не нарушение, это заявка на функцию, поданная голосованием.
Провал 2: портал, которым никто не пользуется
Симптом: полгода разработки, красивая главная страница, 30 уникальных пользователей в месяц при 90 инженерах, и половина из них — платформенная команда.
Причины почти всегда из этого списка, и обычно их несколько сразу:
- Портал построен вокруг сущностей платформы, а не вокруг задач пользователя. Разделы «Кластеры», «Неймспейсы», «Пайплайны» — это модель платформы. Инженер приходит с задачей «выкатить хотфикс» и не знает, в каком разделе она живёт. Лечится сценариями и задачами пользователя, а не перестановкой меню.
- Портал делает 60 % пути и бросает. Создать сервис можно, а посмотреть его логи — нельзя, идите в другую систему. Обрыв посередине хуже отсутствия: пользователь запоминает, что здесь всё равно не доделаешь, и перестаёт заходить.
- Второй логин и третья ролевая модель. Каждый лишний барьер на входе отсекает часть пользователей, и они не возвращаются.
- Данные врут. Один раз увиденный неверный владелец сервиса или устаревший статус деплоя обнуляет доверие к порталу целиком.
- Портал построен раньше, чем автоматизация под ним. Кнопка, создающая тикет, — это тикет. Портал — витрина над работающими API; без API он декорация.
Проверка честности за пять минут: возьмите три реальные задачи (выкатить, откатить, найти причину алерта) и попросите инженера из продуктовой команды сделать их через портал, молча наблюдая. Это юзабилити-тест в самой дешёвой форме, и он вскрывает почти всё.
Про Backstage конкретно, без рекламы и без злорадства. Это открытый фреймворк, из которого можно собрать хороший портал, и в нём есть готовые ответы на каталог, шаблоны и техдоки. Цена: это TypeScript-приложение, которое вы разворачиваете, конфигурируете и обновляете сами; плагины ломаются при обновлениях; часть нужного вам всё равно придётся писать. Реалистичная оценка владения — один-полтора инженера постоянно, плюс несколько месяцев до первой полезной версии. Это разумная сделка при 30+ командах и сомнительная при пяти, где вместо портала лучше работает страница в вики со ссылками и хороший CLI.
Провал 3: абстракция поверх трёх разных потребностей
Симптом: платформа сделала единый «сервис базы данных». Через год у него 34 параметра, три взаимоисключающих режима и документация на 20 страниц. Ни одна из трёх команд, ради которых он делался, им не довольна.
Механика: три команды пришли с похоже звучащими запросами — «нам нужна база». На деле у них были разные потребности: транзакционная база под API с жёсткой латентностью, аналитическое хранилище под отчёты, кэш-подобное хранилище под сессии. Платформа услышала общее слово и построила объединение требований. Объединение требований — не абстракция, а свалка с общим входом.
Диагностический признак простой и измеримый: число параметров растёт линейно с числом команд-клиентов. Настоящая абстракция ведёт себя противоположно — десятый клиент не добавляет параметров, потому что попадает в уже существующий профиль.
Что делать:
- Не абстрагировать раньше третьего похожего случая. До этого — три отдельных решения, пусть с дублированием. Дублирование дешевле неправильной абстракции: его видно и его легко удалить.
- Разделять по режиму использования, а не по технологии. Не «сервис PostgreSQL», а «база для транзакций сервиса» и «хранилище для аналитики» — даже если под обоими сначала одна и та же СУБД. Выбор технологии — детали реализации платформы, и он может измениться («Выбор и миграция БД»).
- Считать флаги как техдолг. Каждый новый параметр в общем интерфейсе — сигнал, что модель неверна. Раз в квартал стоит смотреть на список параметров и спрашивать, какие профили за ними прячутся.
Золотой путь: помощь ровно до того момента, пока он путь
Золотой путь — проложенная дорога: набор технологий и практик, для которых у платформы есть шаблоны, автоматика, дежурство и ответы. Он работает, пока остаётся дорогой, по которой удобно идти. Он перестаёт работать в тот момент, когда становится забором — единственным разрешённым вариантом, за пределами которого только запрет.
Разница не в технике, а в том, что происходит с командой, которой путь не подходит.
чего нет на золотом пути"] Q1{"Это разовая
особенность или
устойчивая потребность?"} Q2{"Похожий запрос
приходил от других
команд?"} Q3{"Команда готова
взять эксплуатацию
на себя?"} ADAPT["Расширить золотой путь:
это заявка на функцию
от трёх и более клиентов"] HATCH["Съезд с чеком:
явный список обязанностей,
срок пересмотра, метка в каталоге"] HELP["Платформа помогает точечно:
консультация, не владение"] STOP["Не съезд, а недоразумение:
чаще всего решается
параметром или документацией"] FEED["Съезд записан в бэклог
платформы как сигнал"] START --> Q1 Q1 -->|разовая| STOP Q1 -->|устойчивая| Q2 Q2 -->|"да, третий раз"| ADAPT Q2 -->|"нет, единственный случай"| Q3 Q3 -->|да| HATCH Q3 -->|нет| HELP HATCH --> FEED HELP --> FEED ADAPT --> FEED
Практика, которая делает съезд управляемым, — выход с чеком. Команда имеет право уйти с золотого пути, но уход оформляется явно и симметрично:
- команда берёт на себя эксплуатацию: своё дежурство, свои алерты, свой SLO («SLI и SLO»);
- обязательства по безопасности и соответствию остаются в силе — они не часть золотого пути, а часть периметра компании («Безопасность по умолчанию»);
- в каталоге появляется метка: этот сервис вне золотого пути, при инциденте будить его команду;
- назначается дата пересмотра — обычно через два квартала: возможно, платформа к тому времени закроет потребность.
Ключевая часть: съезд не должен быть стыдным. Если за уход с золотого пути наказывают, команды не перестают уходить — они перестают об этом сообщать. Так появляется теневая инфраструктура: ресурсы, о которых платформа узнаёт на счёте от облака или во время инцидента. Это ровно тот случай, когда измерительный контур ломается из-за наказания по его показаниям.
И вторая часть, ещё важнее: каждый съезд — вход в бэклог платформы. Одна команда съехала — особенность. Три команды съехали по одной и той же причине — это не три нарушителя, это непокрытая потребность и приоритетная задача. Здесь самообслуживание смыкается с дискавери в продуктовой работе: обходы платформы — самый честный источник требований, который у вас есть, потому что за него проголосовали действиями, а не словами в опросе.
Полезная граница: золотой путь описывает как удобно, периметр безопасности описывает что нельзя. Их смешение — главная причина, по которой золотой путь превращается в забор. «Нельзя выкатывать без ревью безопасности» — периметр. «Мы поддерживаем Go и Kotlin» — путь; команда на Rust не преступник, она просто дальше от готовых инструментов и знает об этом.
Метрики принятия: что считать вместо функций
Платформа с двадцатью функциями и принятием 30 % хуже платформы с пятью функциями и принятием 90 %: у первой сдвинута вправо точка окупаемости, а операционная нагрузка выше. Поэтому набор метрик выстраивается вокруг принятия, а не полноты.
| Метрика | Определение | Ловушка | Откуда данные |
|---|---|---|---|
| Доля операций без человека | операции, прошедшие без вмешательства платформы / все операции | считать только успешные — тогда отказы прячутся | журнал событий платформы |
| Время до результата (p50/p90) | от запроса до готового ресурса | считать только p50: боль живёт в хвосте | тот же журнал |
| Принятие | сервисов на золотом пути / всех продовых сервисов | считать команды, а не сервисы: команда с 20 сервисами и одним на платформе засчитывается | каталог + фактические выкатки |
| Время до первого успеха | от старта новой команды до первой выкатки в прод | измеряется редко, поэтому шумно; берите медиану за полгода | онбординг новых команд |
| Возвраты в тикеты | заявок по операциям, у которых есть самообслуживание | нулевое значение подозрительно: значит, люди сдались и делают руками | трекер |
| Обходы | ресурсов, созданных мимо платформы | требует сверки с облаком; без неё метрика оптимистична | инвентаризация облака |
| Удержание | команды, активные в платформе месяц назад и сегодня | новые команды маскируют уход старых — считайте когортами | журнал событий |
| Успешность операций | доля операций, завершившихся без ошибки | 100 % означает, что операций мало | журнал событий |
Что считать не надо, хотя очень хочется: число зарегистрированных пользователей портала (регистрация не результат), число созданных ресурсов (рост может означать бардак), число выпущенных функций платформы (это выход, а не исход), NPS без разреза по командам (усреднение прячет ровно ту команду, которая уже уходит).
Считается всё это из одной таблицы событий, которую надо завести с первого дня:
-- События платформы: одна строка на операцию, независимо от поверхности
-- (CLI, портал, API, пайплайн). Без этой таблицы метрик принятия не существует.
CREATE TABLE platform_events (
id bigserial PRIMARY KEY,
ts timestamptz NOT NULL DEFAULT now(),
team text NOT NULL, -- владелец, из метки owner
service text,
operation text NOT NULL, -- create_topic, deploy, quota_change, ...
surface text NOT NULL, -- cli | portal | api | pipeline
outcome text NOT NULL, -- ok | policy_denied | error | escalated
human_needed boolean NOT NULL, -- потребовалось ли вмешательство платформы
duration_ms integer NOT NULL -- от запроса до готового результата
);
-- Главный отчёт: доля операций без человека и время до результата по типам
SELECT
operation,
count(*) AS total,
round(100.0 * avg((NOT human_needed)::int), 1) AS self_service_pct,
round(100.0 * avg((outcome = 'ok')::int), 1) AS success_pct,
percentile_cont(0.5) WITHIN GROUP (ORDER BY duration_ms) AS p50_ms,
percentile_cont(0.9) WITHIN GROUP (ORDER BY duration_ms) AS p90_ms
FROM platform_events
WHERE ts >= now() - interval '30 days'
GROUP BY operation
ORDER BY total DESC;
-- Удержание: команда была активна в прошлом месяце и молчит в этом.
-- Уходящие команды видно здесь раньше, чем в любом опросе.
SELECT team, max(ts) AS last_seen
FROM platform_events
WHERE ts >= now() - interval '60 days'
GROUP BY team
HAVING max(ts) < now() - interval '30 days';
Отдельная строка — SLO самой платформы. Самообслуживание, которое падает, хуже тикета: тикет хотя бы предсказуемо медленный. Если выкатка недоступна два часа в разгар рабочего дня, платформа заблокировала все команды разом — это инцидент масштаба компании, а не «внутренняя штука». Значит, у платформенных операций есть свои показатели, свой бюджет ошибок и своё дежурство: подробно про механику — в «SLI и SLO» и «Дежурстве», про то, как это устроить внутри платформенной команды — в «Платформенной команде».
Про измерение опыта разработчика в целом — в «Опыте разработчика»; там же разбирается, почему одни только системные метрики врут и зачем к ним нужны восприятие и поток (подход SPACE, «DevEx: What Actually Drives Productivity», ACM Queue, 2023).
Когда самообслуживание не нужно
Не всякий тикет — провал. Провал — это тикет на частую обратимую операцию. Обратное тоже верно: попытка автоматизировать всё подряд производит хрупкую автоматику, которую никто не понимает и боится трогать.
| Признак операции | Пример | Что делать вместо самообслуживания |
|---|---|---|
| Редкая: 1–3 раза в год | новый регион присутствия | документированный чек-лист, делает платформа |
| Необратимая | удаление продовой БД, отзыв домена | человек в критическом пути осознанно |
| Требует суждения по существу | схема данных, влияющая на смежные сервисы | автоматизировать подготовку и проверки, решение оставить людям |
| Требует переговоров вне инженерии | доступ к персональным данным | процесс согласования, автоматизировать выдачу после решения |
| Высокая цена ошибки при низкой частоте | смена корневого сертификата | репетиция и парная работа, не кнопка |
Даже в этих случаях автоматизируется многое: подготовка контекста, проверки предусловий, генерация черновика изменения, запись решения. Человек должен принимать решение, а не собирать данные для него руками — это разные вещи, и вторая автоматизируется всегда.
И отдельно: необратимость часто мнимая. «Удалить базу» становится обратимым, если удаление — это перевод в состояние «помечена к удалению» с снапшотом и семидневным окном отмены. Инженерная работа по превращению необратимой операции в обратимую окупается лучше, чем любая форма согласования, потому что снимает не задержку, а страх.
Типичные ошибки
- Построить портал первым. Портал — витрина над работающими API. Без них это страница с формами, создающими тикеты.
- Считать функции вместо принятия. Двадцать функций при принятии 30 % — убыток, посчитанный как достижение.
- Не считать стоимость платформенной команды. Без модели окупаемости платформа превращается в налог, который никто не решается отменить.
- Отдавать необратимое. Самообслуживание безопасно ровно настолько, насколько дёшев откат; начинать надо с обратимого.
- Забыть про удаление и TTL. Самообслуживание без удаления — генератор мусора и счетов.
- Наказывать за обход. Обходы не исчезнут, исчезнет информация о них. Обход — это заявка на функцию.
- Абстрагировать после первого клиента. До третьего похожего случая дублирование дешевле неверной абстракции.
- Отказ без следующего шага.
denied by policyбез объяснения превращает самообслуживание в тикет с лишним шагом. - Брать инструмент, не назвав цену владения. Kubernetes, Backstage, Crossplane — обязательства на годы, а не решения; закладывайте человеко-месяцы поддержки в расчёт.
- Молча менять интерфейс. Платформа — продукт с пользователями; ломающее изменение без миграционного пути стоит доверия, которое восстанавливается годами («Миграции»).
Мини-итог
- Самообслуживание — свойство отдельной операции: результат без человека из другой команды в критическом пути. Это шкала от анархии до инварианта, а не выключатель.
- Очередь тикетов имеет цену, и она считается: у условной компании из 12 команд — порядка 920 инженерных часов в квартал, из которых платформа видит только 312. Издержки несёт одна сторона, решение принимает другая.
- Платформенная команда обязана окупаться сэкономленным временем. Точка окупаемости определяется принятием, а не полнотой функций: при принятии 40 % платформа не окупается никогда.
- Пять свойств делают самообслуживание работающим: декларативность, политика вместо ревью, обратимость, ограничители, наблюдаемость запроса. Безопасность отдачи операции равна дешевизне отката.
- Поверхности строятся в порядке CLI и PR → API → портал. Портал — витрина, а не фундамент; каталог полезен, только пока он производная от реальных данных.
- Три типовых провала: обёртка без собственной модели, портал вокруг сущностей платформы, абстракция как объединение требований трёх разных клиентов.
- Золотой путь остаётся путём, пока съезд с него возможен, оформлен и не стыден. Каждый съезд — вход в бэклог платформы.
- Метрики: доля операций без человека, время до результата по p50/p90, принятие по сервисам, удержание когортами, обходы. Не метрики: регистрации, число функций, объём созданных ресурсов.
Источники
- Evan Bottcher. What I Talk About When I Talk About Platforms — определение платформы через проложенную дорогу и самообслуживание.
- Matthew Skelton, Manuel Pais. Team Topologies (IT Revolution, 2019), teamtopologies.com — платформенная команда как тип, «тончайшая жизнеспособная платформа», когнитивная нагрузка.
- CNCF TAG App Delivery. Platforms White Paper и Platform Engineering Maturity Model — уровни зрелости и набор возможностей платформы.
- Google SRE. Eliminating Toil — определение рутины и арифметика её устранения.
- Noda, Storey, Forsgren, Greiler. DevEx: What Actually Drives Productivity, ACM Queue, 2023 — что измерять в опыте разработчика.
- DORA — lead time изменений, частота выкаток и связь с практиками доставки.
- Backstage documentation — что это на самом деле: фреймворк, а не готовый портал.
- Crossplane docs и Terraform docs — декларативное управление инфраструктурой.
- Open Policy Agent и Kyverno — политика как код вместо ручного ревью.
Что дальше
Самообслуживание упирается в вопрос, который мы здесь только обозначили: где именно проходит граница между «скрыть сложность» и «отрезать возможности». Профиль size: small полезен, а обёртка над Terraform — вредна, хотя обе прячут параметры. Разница не в количестве спрятанного, а в том, появились ли новые понятия. Об этом — следующая глава: «Абстракции: где скрывать сложность, а где вредно».