Золотой путь: помощь, которая не должна стать забором
В понедельник команда «Платежи» создала новый сервис: одна команда в терминале, через двадцать минут в проде крутится заготовка с health-чеком, метриками, трейсингом, алертом на ошибки, страницей в каталоге и записанным дежурным. Восемь месяцев назад на это уходило три недели переписки с четырьмя отделами. Платформенная команда справедливо гордится.
В тот же понедельник команда «Аналитика» третий месяц живёт в форке того же шаблона. Им нужен долгоживущий gRPC-стрим с большими сообщениями, а платформенный ingress режет соединение на 60 секундах и не отдаёт настройку наружу. Тикет висит с апреля. Они собрали свой конвейер, подняли свои виртуалки в отдельном облачном аккаунте, оплаченном с корпоративной карты руководителя, и живут там: без аудита доступов, без общих алертов, без записи в каталоге. Формально «на платформе» 27 сервисов из 29. Фактически два самых нагруженных находятся в слепой зоне.
Оба факта — про одну и ту же вещь. Золотой путь работает ровно до того момента, пока он остаётся путём: маршрутом, по которому идти дешевле, чем не идти. Как только с него нельзя сойти, он перестаёт быть путём и становится забором — а забор в инженерной организации не удерживает, он выдавливает. В предыдущей главе мы договорились, что у платформы есть пользователи и они могут уйти. Эта глава — про главный инструмент платформы и про то, как этот инструмент калечит, если держать его не за ту сторону.
Что такое золотой путь и чем он не является
Золотой путь — это поддерживаемый, задокументированный и по-настоящему работающий маршрут от «есть идея сервиса» до «сервис в проде и за ним кто-то следит». Не «единственный разрешённый способ». Не «архитектурный стандарт». Не «политика». Термин пришёл из двух источников. Netflix называет это paved road — «мощёная дорога»: платформа обеспечивает поддержку тем, кто едет по ней, а съехавшие в поле едут сами и чинят себя сами (Full Cycle Developers at Netflix). Spotify ввёл в оборот сам термин golden path — описанный сквозной маршрут для самого частого сценария, а не для всех сразу (How We Use Golden Paths to Solve Fragmentation).
У настоящего золотого пути ровно три обещания, и все три проверяемые:
- Он работает сегодня. Не «в вики описано», а «пройди эти шаги — получишь результат». Проверка: платформенный инженер проходит путь с нуля раз в спринт, на чистом ноутбуке, и это часть его работы, а не подвиг.
- У него есть владелец с именем. Сломался шаблон — известно, кто чинит и за какое время. Без этого путь — просто чей-то старый скрипт.
- С него можно сойти. Съезд стоит дороже, чем движение по пути, но не требует разрешения свыше и не выкидывает команду за пределы общих гарантий. Это обещание нарушают чаще всего, и именно оно отличает путь от забора.
| Что это | Механика | Что происходит при отклонении | Сколько таких должно быть |
|---|---|---|---|
| Путь (golden path) | самый дешёвый маршрут, поддерживается платформой | команда идёт своим маршрутом, теряя поддержку | 1–3 на организацию: по одному на класс рабочих нагрузок |
| Ограждение (guardrail) | автоматическая проверка свойства, а не реализации | сборка/деплой падает с понятным сообщением | 5–15, все проверяются машиной |
| Забор | путь объявлен единственным, съезды закрыты | обход через теневую инфраструктуру | 0 |
| Документация | текст без исполняемого кода | устаревает за квартал | сколько угодно, но она не заменяет путь |
Отдельно проговорю: вики-страница «как поднять сервис» на 40 экранов — это не золотой путь. Это его отсутствие, оформленное как забота. Признак прост: если шаг документации нельзя выполнить командой или кнопкой, а нужно «согласовать с Ильёй», то путь состоит из Ильи, и его пропускная способность — реальный SLO вашей платформы.
Из чего состоит путь: карта жизненного цикла
Самая частая ошибка новичка в платформенной команде — сделать золотой путь до первого деплоя и остановиться. Это самый заметный кусок, но по времени жизни сервиса — самый маленький. Сервис создают один раз, а живут с ним годами.
| Этап | Что даёт путь | Частота на сервис | Чем платят, если пути нет |
|---|---|---|---|
| Создание | шаблон репозитория, скелет, владелец, запись в каталоге | один раз | 3–15 дней согласований |
| Сборка и проверки | конвейер, кэш, линтеры, сканеры | каждый коммит | своя копия конвейера в каждой команде |
| Конфигурация и секреты | схема конфига, выдача секретов без копипаста | еженедельно | секреты в переменных CI и в личных чатах |
| Выкатка | стратегия релиза, откат одной командой | 2–20 раз в неделю | ручные шаги, разный смысл слова «релиз» |
| Наблюдаемость | метрики, логи, трейсы, дашборд из коробки | всегда | инцидент вслепую |
| Дежурство | маршрут алерта, расписание, ранбук | всегда | алерт уходит в никуда |
| Обновления | автообновление базовых образов и библиотек | ежемесячно | «мы не обновлялись два года» |
| Вывод из эксплуатации | снос ресурсов, архивация, снятие с дежурства | один раз | зомби-сервисы и счета за них |
Последняя строка — та, которую забывают почти все. Без неё через три года у вас каталог с 400 сервисами, из которых живых 260, и никто не может сказать, какие именно. Вывод из эксплуатации — часть пути, а не «потом отдельно разберёмся». Так выглядит сквозной маршрут, если он собран честно:
команда-владелец, критичность"] B --> C["Репозиторий из шаблона:
код, тесты, Dockerfile, service.yaml"] C --> D["Запись в каталоге:
владелец, зависимости, канал дежурства"] D --> E["Конвейер: сборка, тесты,
сканеры, подпись артефакта"] E --> F{"Ограждения
пройдены?"} F -->|"нет"| G["Отказ с текстом:
что нарушено и как починить"] G --> E F -->|"да"| H["Стенд: превью-окружение
на время жизни ветки"] H --> I["Прод: канареечная выкатка,
откат одной командой"] I --> J["Из коробки: дашборд, алерты,
SLO по умолчанию, бюджет ошибок"] J --> K["Обновления шаблона приходят
автоматическими PR"] K --> L["Вывод из эксплуатации:
снос ресурсов и снятие с дежурства"]
Обратите внимание на узел G: отказ ограждения — это часть пользовательского опыта, а не системное сообщение. «Policy violation: PSP-0042» — плохо. «В манифесте нет лимита памяти. Добавьте resources.memory: 512Mi в service.yaml, пример: ссылка» — хорошо. По этому одному тексту можно с высокой точностью предсказать, будут ли вашу платформу обходить.
Отношения команды с путём: где именно ломается
У любой команды путь проходит через одни и те же состояния. Важна одна-единственная развилка — момент, когда команда во что-то упёрлась.
Переход Blocked --> Shadow — это и есть провал платформы. Он бесплатен для команды и почти невидим для платформенной команды: никто не приходит и не говорит «мы вас обошли». Поэтому его надо ловить не опросами, а данными — об этом ниже. Второй важный переход — Trying --> Shadow. Первый контакт с платформой имеет свойство однократности: если первая попытка стоила инженеру дня и закончилась ничем, он больше не придёт, и его мнение станет мнением всей команды. Это ровно та же воронка, что в продуктовой аналитике, и её надо так и считать — см. метрики продукта и юзабилити-тестирование, которое к платформе применимо буквально: посадите инженера из продуктовой команды пройти путь и молча смотрите.
Метрики: принятие важнее полноты
Платформенная команда естественным образом меряет то, что сделала: количество шаблонов, покрытие функций, число плагинов портала. Все эти числа могут расти при нулевой и даже отрицательной пользе. Мерить нужно принятие — то, что делают пользователи, а не то, что вы им предоставили.
Рабочий набор, минимальный и честный:
- Доля новых сервисов на пути за последние 90 дней. Именно новых: доля от всего парка растёт сама по себе за счёт старья и маскирует отток.
- Доля деплоев через платформенный конвейер от всех деплоев в прод. Знаменатель берётся из событий деплоя, а не из вашего же конвейера, иначе он равен единице по построению.
- Время до первого деплоя нового сервиса, p50 и p90. p90 важнее: он показывает не идеальный сценарий, а тот, где что-то пошло не так.
- Число активных форков шаблона и их расхождение с апстримом в строках. Форк — это голос: команде нужен путь, но не ваш.
- Поток запросов на исключение и медиана времени ответа. Растущая очередь при неизменной пропускной способности — заявка на теневой контур.
- Отток. Команды, которые были на пути и ушли: одна ушедшая команда информативнее десяти пришедших.
Самая полезная метрика считается не по вашим данным, а по чужим: расхождение между инвентарём облака и каталогом сервисов. Всё, что работает и не числится, — обход.
-- Детектор теневого контура: рабочие нагрузки, которых нет в каталоге платформы.
-- cloud_inventory наполняется из API провайдера (AWS Config, Azure Resource Graph и т. п.),
-- service_catalog — из платформенного каталога.
SELECT i.account_id, i.workload_id, i.owner_tag, i.monthly_cost_usd, i.first_seen
FROM cloud_inventory AS i
LEFT JOIN service_catalog AS c ON c.workload_id = i.workload_id
WHERE c.workload_id IS NULL -- нет записи в каталоге
AND i.kind IN ('compute', 'database', 'queue')
AND i.first_seen < now() - interval '14 days' -- не свежий эксперимент
ORDER BY i.monthly_cost_usd DESC;
Первый прогон такого запроса в организации на 30 команд почти всегда даёт неприятный результат. Это не повод устраивать разбор: каждая строка — не нарушение, а дефект вашего пути, у которого есть причина. Спрашивать надо «почему было дешевле сделать так», а не «кто разрешил». И главное правило приоритизации: полнота функций не является целью. Путь, покрывающий 70 % сценариев и используемый в 90 % случаев, лучше пути, покрывающего 95 % сценариев и используемого в 30 %. Первый экономит время организации, второй — витрина. Приоритизация фич пути — обычная продуктовая работа, с теми же инструментами, что в приоритизации.
Правый нижний квадрант — источник большинства провальных платформенных абстракций: задача частая, а требования у всех разные. Туда лезут охотнее всего, потому что «часто» видно, а «разнородно» — нет.
Сколько стоит путь и когда он окупается
Платформенная команда не пишет продукт. Значит, она обязана возвращать организации больше инженерного времени, чем потребляет. Если этот расчёт никто не делает, платформа незаметно превращается в налог: она есть в бюджете, её результат — в ощущениях.
Считать удобнее не в деньгах, а в человеко-годах: тогда разговор не упирается в валюту и грейды. Возьмём типичную конфигурацию: 30 продуктовых команд по 5 инженеров (150 инженеров), платформенная команда из 5 человек. Порог окупаемости очевиден: 5 / 150 = 3,3 % общего инженерного времени. При 1700 продуктивных часов в году это примерно 1,3 часа в неделю на каждого инженера организации. Меньше — платформа съедает больше, чем отдаёт.
"""Прикидка окупаемости золотого пути в человеко-годах.
Считаем не «сколько мы сделали», а «сколько времени вернули другим».
Числа — пример организации на 150 инженеров; подставляйте измеренные, а не желаемые.
"""
HOURS_PER_FTE_YEAR = 1700 # продуктивных часов в году на инженера
def fte(hours: float) -> float:
return hours / HOURS_PER_FTE_YEAR
savings = {
# источник экономии: (событий в год, сэкономлено часов на событие)
"создание сервиса": (20, 12 * 8), # было 3 недели, стало 2 дня
"деплой в прод": (5400, 10 / 60), # 30 команд x 4 в неделю x 45 недель
"обязательный апгрейд": (120, 6), # 30 команд x 4 раза в год
"онбординг инженера": (40, 5 * 8), # первый коммит в прод быстрее на неделю
"разбор инцидента": (200, 40 / 60), # единые дашборды режут MTTR
}
total = 0.0
for name, (count, hours_each) in savings.items():
saved = fte(count * hours_each)
total += saved
print(f"{name:<24} {saved:5.2f} чел.-лет")
platform_team = 5.0
print(f"{'ИТОГО возвращено':<24} {total:5.2f} чел.-лет")
print(f"{'Стоимость платформы':<24} {platform_team:5.2f} чел.-лет")
print(f"{'Баланс':<24} {total - platform_team:+5.2f} чел.-лет")
# создание сервиса 1.13 · деплой 0.53 · апгрейды 0.42 · онбординг 0.94 · инциденты 0.08
# ИТОГО возвращено 3.10 чел.-лет
# Стоимость платформы 5.00 чел.-лет
# Баланс -1.90 чел.-лет
Этот расчёт стоит проделать ровно потому, что он часто выходит отрицательным — и это самый полезный результат главы. Что он говорит:
- Экономия на создании сервисов почти никогда не окупает платформенную команду. Сервисы создают редко. Демо «сервис за 20 минут вместо трёх недель» продаёт платформу руководству, но в годовом балансе даёт около одного человеко-года. Продавать нужно повседневное: деплои, апгрейды, дежурство, онбординг.
- Отрицательный баланс не значит «платформу закрыть». Он значит одно из трёх: команда великовата для текущего масштаба (5 человек на 150 инженеров — это верхняя граница; на 60 инженеров хватает 2–3), либо путь покрывает редкие операции вместо частых, либо часть ценности лежит не в экономии времени.
- Ценность в снижении риска считается отдельно и называется отдельно. «Секреты не утекают, потому что их физически нет в репозиториях», «нет сервиса без владельца и дежурного» — это законные аргументы. Но их нельзя молча подмешивать в «мы ускоряем разработку»: цифра станет нечестной, и первый же въедливый финансовый директор это найдёт.
- Стоимость владения инструментами входит в цену. Ваш кластер Kubernetes — это апгрейды контрол-плейна несколько раз в год, CNI, CSI, RBAC, сетевые политики, обучение. Глава про Kubernetes описывает, что именно вы подписываетесь эксплуатировать. Для 12 инженеров и 8 сервисов честный золотой путь может быть managed-контейнерным сервисом облака или вовсе systemd на паре виртуалок, и это не отсталость, а арифметика. Подробный разбор учёта расходов и разговора с командами о деньгах — в главе про стоимость, а состав и загрузка самой команды — в главе про платформенную команду.
Как путь превращается в забор
Никто не принимает решение «давайте построим забор». Забор вырастает сам, из последовательности локально разумных решений. Классическая локальная оптимизация: платформенная команда оптимизирует свою метрику — однородность парка, скорость закрытия тикетов, «покрытие политиками», — а страдает пропускная способность системы целиком. Механика подробно разобрана в главе о локальной оптимизации.
Пять конкретных механизмов, каждый из которых надо уметь узнавать в своей организации:
- Путь стал носителем доступа. Доступ в прод выдаётся только сервисам, созданным из шаблона. Технически удобно, организационно катастрофа: теперь отказ от пути равен отказу от работы, и любой недостаток пути становится нерешаемым для команды. Лечение: доступ выдаётся за выполнение свойств (подписанный артефакт, известный владелец, включённый аудит), а не за происхождение из шаблона.
- Обновления шаблона обязательны и ломающие. Платформа катит версию, у команд падает сборка, платформа отвечает «читайте changelog». Через три таких раза команды прибивают версию шаблона гвоздями и живут в форке. Лечение — политика версий, см. ниже.
- Ответ «мы это не поддерживаем» без второго предложения. Это не ответ, это выталкивание. Ответ состоит из двух частей всегда: «в пути этого нет» + «вот как сделать самим и что при этом остаётся на вас».
- Человек в качестве шлюза. Любой шаг пути, требующий ревью живым человеком, — это очередь. Очередь с интенсивностью выше пропускной способности растёт неограниченно; при загрузке 80 % среднее время ожидания уже вчетверо выше, чем при 50 %. Один архитектурный комитет по вторникам превращает «путь» в «две недели ожидания», и обход становится рациональным решением, а не саботажем.
- Путь построен на одном профиле нагрузки. Шаблон, выросший из stateless HTTP-сервиса, встречает стрим-обработчик или задачу с GPU и не гнётся. Дальше либо путь ветвится честно, либо начинается натягивание — «оформите свой стрим как HTTP-сервис с фиктивным health-чеком».
Разница между двумя картинками не в наличии правил. Правила в обоих случаях есть. Разница в том, что в первом случае общий минимум действует и на дороге, и на обочине, а во втором — обход выносит команду за пределы вообще всех гарантий. Забор не удерживает границу, он переносит её туда, где вы её не видите.
Свойства обязательны, реализация — нет
Это главный работающий приём, отделяющий путь от забора. Организации нужны не ваши инструменты, а свойства систем. Свойства обязательны для всех и проверяются машиной. Реализация — рекомендуется, поддерживается и заменяема.
| Свойство (обязательно всем) | Реализация на пути (по умолчанию) | Как проверяется у тех, кто вне пути |
|---|---|---|
| Секретов нет в репозитории и в образе | выдача через платформенный агент | сканер репозиториев и образов, общий для всех |
| Артефакт подписан и происхождение прослеживаемо | подпись в платформенном конвейере | проверка подписи на входе в реестр |
| У сервиса есть владелец и канал дежурства | заполняется мастером создания | запись в каталоге обязательна, иначе нет прод-доступа |
| Доступы к данным аудируются | общий прокси и роли | включённый лог аудита в аккаунте, экспорт в общий сток |
| Есть SLO и маршрут алерта | шаблон SLO из коробки | наличие SLO-спецификации в реестре |
| Есть план вывода из эксплуатации | автоматический снос ресурсов | тег с датой ревизии на ресурсах |
Формулировка «все обязаны использовать наш Helm-чарт» — это забор. Формулировка «в проде не бывает контейнера с секретом внутри, вот проверка, вот чарт, который её проходит из коробки» — это путь плюс ограждение: второе можно выполнить своим способом. И ограждения обязаны быть исполняемым кодом, а не пунктом регламента:
# conftest / OPA: ограждение уровня организации.
# Проверяет свойство, а не происхождение манифеста из шаблона.
package platform.guardrails
# 1. Секрет не приезжает значением переменной окружения.
deny contains msg if {
input.kind == "Deployment"
c := input.spec.template.spec.containers[_]
e := c.env[_]
regex.match("(?i)(password|secret|token|api_?key)", e.name)
e.value != ""
msg := sprintf("переменная %q содержит секрет открытым текстом. Используйте secretKeyRef: docs/secrets", [e.name])
}
# 2. Владелец обязателен: сервис без владельца — будущий зомби.
deny contains msg if {
not input.metadata.labels["platform.io/owner"]
msg := "нет метки platform.io/owner. Заведите сервис в каталоге: docs/catalog/register"
}
# 3. Лимит памяти обязателен: без него один сервис уносит узел целиком.
deny contains msg if {
input.kind == "Deployment"
c := input.spec.template.spec.containers[_]
not c.resources.limits.memory
msg := sprintf("контейнер %q без limits.memory. Добавьте 512Mi, почему: docs/guardrails/G-002", [c.name])
}
Заметьте формат сообщений: что нарушено, что сделать, где прочитать почему. Ограждение, которое не объясняет себя, воспринимается как произвол и порождает изобретательные обходы — вплоть до генерации манифестов, проходящих проверку, но не отражающих реальность. Подробнее про политику как код и режим «сначала предупреждаем, потом запрещаем» — в главе о безопасности по умолчанию и в разборе управления секретами.
Команда, которой золотой путь не подходит
Это самый практически важный раздел главы. Такая команда будет всегда: ML-инференс с GPU, обработчик потоков с сотней тысяч сообщений в секунду, легаси на .NET Framework, купленная компания со своим стеком, жёсткое требование регулятора о размещении данных. Вопрос не «как их убедить», а «какая у нас процедура».
Три исхода, и все три легитимны:
Расширить путь. Порог — правило трёх: одно требование делает исключением, три однотипных требования делают дефект пути. Считайте не слова, а код: три команды, просящие «нам нужен свой ingress», могут иметь три разные причины, и тогда это не одно расширение, а три разных.
Дать съезд с контрактом. Ключевое слово — контракт. Команда получает свободу реализации и в обмен принимает на себя обязанности, которые до этого несла платформа: дежурство по своему куску, обновления базовых образов, сроки закрытия уязвимостей, отчёт о соблюдении общего минимума. Это не наказание, а честный обмен: Team Topologies описывает это как перенос когнитивной нагрузки обратно на команду — вместе с полномочиями.
Сказать «нет». Законно ровно тогда, когда требование противоречит неотчуждаемому свойству: «нам нужны прод-данные на ноутбуках разработчиков», «нам не нужен аудит доступов». Тогда «нет» звучит как «нет, потому что вот это свойство обязательно всем; вот два способа получить то, что вам нужно на самом деле».
Исключение — это запись, а не устная договорённость: оно живёт в репозитории, ревьюится и имеет срок.
# platform/exceptions/analytics-stream-ingress.yaml
apiVersion: platform.io/v1
kind: PathException
metadata:
name: analytics-stream-ingress
team: analytics
spec:
reason: >
Долгоживущие gRPC-стримы с сообщениями до 8 МБ.
Платформенный ingress рвёт соединение на 60 с и не отдаёт настройку наружу.
deviates_from:
- golden-path/ingress # своя точка входа
- golden-path/deploy-pipeline # свой шаг выкатки после сборки
keeps: # общий минимум остаётся полностью
[no-secrets-in-image, signed-artifacts, owner-and-oncall, audit-logs-exported]
team_takes_over: # что команда берёт на себя явно
- обновление базового образа в течение 14 дней после релиза платформы
- дежурство по собственному ingress, включая ночи
- закрытие критических CVE в течение 7 дней
granted_at: 2026-04-14
expires_at: 2026-10-14 # TTL обязателен, продление — тот же процесс
review_owner: platform-team
linked_backlog_item: PLAT-1187 # запрос на расширение пути
Четыре правила, без которых эта схема разваливается:
- TTL обязателен. Бессрочное исключение — это второй стандарт де-факто. Полгода — разумный срок: достаточно, чтобы работать, мало, чтобы забыть.
- Исключение не отменяет общий минимум. Оно отменяет реализацию, а не свойства. Блок
keeps— не формальность, он проверяется теми же машинными проверками. - Каждое исключение попадает в бэклог платформы. Не как «жалоба», а как вход. Исключения — самый качественный источник требований, какой у вас есть: за ним стоит команда, которая уже заплатила за свою потребность своим временем.
- Исключение выдаётся быстро. Если получение съезда занимает три недели согласований, команда пойдёт в тень: это дешевле. Целевое время ответа — дни, и оно само по себе метрика платформы.
Съезды: четыре уровня, а не два
«Или наш шаблон, или сами» — ложная дихотомия, из которой и растёт большинство заборов. Настоящий путь имеет градиент: каждый следующий уровень свободы дороже предыдущего, но доступен без разрешения. Уровни 1 и 2 — то, что спасает от форков: форк шаблона появляется, когда между «параметром» и «полным выходом» ничего нет.
| Уровень | Что можно | Цена для команды | Поддержка платформы |
|---|---|---|---|
| 0. Параметры | значения в service.yaml |
нулевая | полная |
| 1. Хуки | свои шаги в конвейере до и после стандартных | тесты своих шагов | полная на стандартной части |
| 2. Патчи | наложение патча на сгенерированные манифесты | ломается при мажорных обновлениях | базовая, разбор при инцидентах |
| 3. Выход | свой конвейер и свои манифесты, общий минимум остаётся | дежурство и обновления на команде | консультации, ограждения, каталог |
# service.yaml — декларативный интерфейс золотого пути.
# Команда описывает намерение, платформа отвечает за реализацию.
apiVersion: platform.io/v1
kind: Service
metadata:
name: payments-api
team: payments
tier: critical # влияет на дефолты SLO, дежурство и репликацию
spec:
runtime: java21
path_version: "3.4" # версия золотого пути, зафиксирована явно
http:
port: 8080
healthcheck: /healthz
resources:
profile: medium # профиль, а не голые cpu/memory: платформа калибрует
scaling: { min: 3, max: 24, metric: rps }
dependencies: [postgres/orders-db, queue/payment-events]
slo:
availability: 99.9 # дефолт для tier=critical, можно ужесточить
latency_p99_ms: 300
oncall: { schedule: payments-primary }
# Уровень 1: свои шаги конвейера без выхода с пути.
hooks:
post_build:
- { name: контрактные тесты партнёра, run: make contract-test }
pre_deploy:
- { name: прогрев кэша тарифов, run: ./scripts/warm-cache.sh }
# Уровень 2: точечный патч на сгенерированное. Не запрещён, но виден:
# платформа знает про этот патч и предупредит при мажорном обновлении.
overrides:
ingress:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
justification: "gRPC-стримы, см. PLAT-1187"
И отдельный, недооценённый инструмент — команда «покажи, что под капотом»:
# Что реально будет применено — без запуска и без чтения исходников платформы
platform render --env prod > /tmp/manifests.yaml
# Полный выход с пути: генерируем всё в репозиторий команды и отвязываемся.
# Это НЕ наказание и НЕ требует согласования — только регистрация факта.
platform eject --into ./infra --keep-guardrails
# Проверка общего минимума работает одинаково на пути и вне его
platform verify ./infra --profile org-baseline
eject — не поражение платформы. Это её главный аргумент в разговоре с недоверчивой командой: «попробуй, а если не подойдёт — вот выход, и он реально работает». Обратный эффект известен: наличие двери резко снижает желание в неё выйти. Платформа без eject вынуждена удерживать пользователей отсутствием альтернатив — а это ровно определение забора.
Версионирование пути: обновлять, не принуждая
Путь живёт, только если умеет обновляться. И ломается, если обновления приходят как принуждение.
- Версия пути фиксируется в репозитории сервиса (
path_versionвыше). Команда всегда знает, на чём стоит, и может отложить переход на спринт. - Поддерживаются текущая и предыдущая мажорные версии. Не «все версии, что когда-то были» — это разорит платформенную команду. И не «только последняя» — это принуждение.
- Обновления приезжают пул-реквестами, а не письмами. Автоматический PR с изменением, зелёным конвейером и changelog принимается за минуты; письмо «просьба всем обновиться до конца квартала» не принимается никогда. Механика — как у Renovate для зависимостей. Ломающее изменение сопровождается кодмодом: тот, кто ломает формат, тот и пишет скрипт миграции.
- Срок вывода старой версии — не меньше двух кварталов, и он объявлен заранее, с датой. Массовый перевод десятков команд — отдельная дисциплина, разобранная в главе про миграции.
Метрика здоровья версий одна: доля сервисов на N и N-1. Если она ниже 70 %, ваш путь фактически разошёлся на десяток несовместимых путей, и следующий инцидент это покажет.
Типовые провалы
Обёртка над облаком, которая только мешает
Симптом: платформа принимает YAML на 20 полей и генерирует терраформ. Через полгода в этих 20 полях — 30 % возможностей провайдера, а любая новая фича облака превращается в тикет в платформу и три недели ожидания. Ошибки провайдера всплывают наружу как Error: plan failed без контекста, потому что слой перевода потерял сообщение. Сломано здесь то, что обёртка забрала возможности, ничего не добавив взамен. Обёртка над облаком оправдана только тогда, когда она даёт то, чего у провайдера нет: безопасные дефолты, связку с каталогом и учётом расходов, автоматическую разметку ресурсов, единый способ выдачи доступа. И обязана иметь спуск на нижний уровень — возможность передать сырой блок конфигурации провайдера, пусть с пометкой «поддерживается командой». Иначе вы конкурируете с документацией AWS, которую читают все и пишут сотни технических писателей, а вашу вики пишете вы вдвоём. Про сам инструмент — инфраструктура как код; про то, где абстракция уместна, а где вредна — глава 05.
Портал, которым никто не пользуется
Портал разработчика — витрина, а не ценность. Если за витриной пусто, красивая витрина не помогает. Backstage — хороший пример честной цены владения: это не «поставили и работает», а фронтенд на React, который вы форкаете, набор плагинов, которые вы допиливаете, своя аутентификация, свои интеграции, апгрейды апстрима каждые несколько недель. Реалистичная оценка — от одного до двух инженеров на постоянку. Эти люди в вашем балансе окупаемости уже посчитаны как расход; вопрос, что они возвращают.
Померьте не просмотры, а действия из портала: создан сервис, выдан доступ, запущена выкатка, найден владелец. Если 90 % трафика — это «посмотреть, кто владеет сервисом», вам нужен каталог с быстрым поиском и интеграцией в мессенджер, а не портал. Если действий нет вообще — портал сделан вместо пути, а не поверх него. Порядок всегда один: сначала работающий путь через CLI и конвейер, потом витрина для тех, кому удобнее кликать.
«Мы сделали абстракцию» поверх трёх разных потребностей
Классика. Три команды просят шаблон: одна — stateless HTTP API, вторая — ночной батч по расписанию, третья — консьюмер очереди с ручным управлением оффсетами. Платформа видит общее («всё это сервисы, у всех сборка, деплой, метрики») и делает один шаблон. Через год в шаблоне 40 флагов, if service_type == встречается в генераторе, в чарте и в конвейере, половина комбинаций никогда не тестировалась, и каждый релиз шаблона ломает кому-то одну из веток. Общего в трёх нагрузках оказалось меньше, чем различий: у батча нет health-чека и есть дедлайн, у консьюмера нет ingress и есть лаг, у API есть SLO по латентности, которого нет у остальных.
Правильный ход — три шаблона, разделяющие библиотеки: общий базовый образ, общий модуль наблюдаемости, общий формат каталога. Дублирование трёх похожих шаблонов дешевле одной неправильной абстракции — Sandi Metz формулирует это резко и точно в The Wrong Abstraction. Проверка перед объединением: не «называются ли эти вещи одинаково», а «меняются ли они по одним и тем же причинам».
Инструмент вместо задачи
Отдельный сорт провала — начать с выбора инструмента. «Нам нужна платформа, значит нужен Kubernetes, Backstage, Crossplane и ArgoCD» — это список расходов, а не решение. У каждого пункта есть цена владения в человеко-часах, и она не разовая:
| Инструмент | Что реально покупаете | Постоянная цена |
|---|---|---|
| Kubernetes | планировщик, декларативность, экосистема | апгрейды несколько раз в год, сеть, хранилище, RBAC, обучение всех команд |
| Backstage | каталог и витрина | форк фронтенда, плагины, апгрейды, аутентификация |
| Service mesh | mTLS, трафик-политики, телеметрия | ещё один контрол-плейн в критическом пути запроса и в отладке |
| Собственный CLI | единая точка входа | поддержка на трёх ОС, обновления, обратная совместимость |
Ни один из них не является золотым путём. Золотой путь — это маршрут; инструменты — материал, из которого он вымощен. Если маршрут работает на GitHub Actions, готовых шаблонах и managed-сервисах облака — маршрут работает. Вопрос «а у нас настоящая платформенная инженерия?» стоит меньше, чем вопрос «сколько времени в неделю мы вернули командам».
Диагностика: путь или уже забор
Пройдитесь по списку честно. Каждое «да» — балл в сторону забора.
- Число форков шаблона выросло за последние два квартала
- Есть шаг, где надо ждать живого человека, и медиана ожидания больше двух дней; стандартный ответ на нестандартное требование — «мы это не поддерживаем», без второй части
- Исключения выдаёт комитет, они бессрочные и их никто не считает
- Нет команды
ejectили её аналога; уйти с пути технически невозможно - Доступ в прод привязан к происхождению из шаблона, а не к выполнению свойств
- Инвентарь облака расходится с каталогом, и никто не смотрит на разницу
- Метрика принятия считается по данным самой платформы (знаменатель равен единице по построению)
- Обновления шаблона обязательны, приходят письмом и ломают сборку
- Окупаемость платформенной команды никогда не считалась в человеко-годах
- На вопрос «кто наши пользователи» команда отвечает «вся компания» вместо конкретных имён
Три и больше — пора чинить путь, а не команды.
Мини-итог
- Золотой путь — самый дешёвый поддерживаемый маршрут от идеи до прода, а не единственный разрешённый. Три обещания: работает, у него есть владелец, с него можно сойти. Покрывает он весь жизненный цикл, включая обновления и вывод из эксплуатации, а не только эффектное «сервис за 20 минут».
- Мерить надо принятие: долю новых сервисов на пути, долю деплоев через платформу, p90 времени до первого деплоя, форки, исключения, отток. Полнота функций — не цель.
- Расхождение инвентаря облака с каталогом — самый честный детектор обхода. Каждая строка расхождения — дефект пути, а не нарушение дисциплины.
- Платформа обязана окупаться в человеко-годах. Экономия на создании сервисов почти никогда не окупает команду; окупает повседневное. Ценность в снижении риска считается и называется отдельно.
- Обязательны свойства, а не реализация: ограждений мало, они машинные и объясняют себя текстом с инструкцией. Съезд — часть конструкции: четыре уровня свободы,
ejectбез разрешения, исключение с TTL и контрактом, три однотипных исключения переводят требование в путь. - Инструменты имеют цену владения. Kubernetes, Backstage и mesh — материал, а не цель; называйте их стоимость вслух.
Источники
- Evan Bottcher, What I Talk About When I Talk About Platforms — определение платформы как продукта с добровольным принятием
- Spotify Engineering, How We Use Golden Paths to Solve Fragmentation in Our Software Ecosystem
- Netflix Technology Blog, Full Cycle Developers at Netflix — paved road и «свобода и ответственность»
- Matthew Skelton, Manuel Pais, Team Topologies — платформенная команда как X-as-a-Service и когнитивная нагрузка
- Camille Fournier, Ian Nowland, Platform Engineering: A Guide for Technical, Product, and People Leaders, O’Reilly, 2024
- CNCF App Delivery TAG, Platforms White Paper — определения, зрелость, что считать платформой
- Sandi Metz, The Wrong Abstraction; DORA: State of DevOps — в отчётах 2023–2024 отдельно разбирается влияние внутренних платформ
- Backstage и software templates — устройство каталога и шаблонов
- Open Policy Agent, Conftest, Kyverno — ограждения как код; Renovate — обновления через автоматические пул-реквесты
Что дальше
Мы разобрали маршрут и его границы. Но «путь работает» — утверждение, которое надо чем-то подтверждать, и ощущений платформенной команды тут недостаточно: она проходит свой путь ежедневно и не замечает мест, где новичок спотыкается. Нужны измерения опыта разработчика — и такие, которые нельзя нарисовать в презентации.
Опыт разработчика: что измерять и как это делать честно — какие сигналы брать из систем, а какие только из опросов, почему DORA-метрики не описывают DX целиком, как ловить время ожидания и трение, и как не превратить измерение в очередной отчёт ради отчёта.