Стоимость: учёт расходов, лимиты и разговор с командами о деньгах
В марте счёт за облако был 186 000 USD. В октябре — 264 000 USD. Выручка за это время выросла на 9 %. Финансовый директор приходит к директору по инженерии, тот — к платформенной команде: «разберитесь».
Платформенная команда делает ровно то, что делает большинство: открывает консоль биллинга, час смотрит на график, находит, что «выросло всё», и рассылает в общий инженерный чат письмо: «Коллеги, счёт за облако вырос на 42 %. Просьба всем командам проверить свои ресурсы и удалить лишнее». В ответ приходят четыре сообщения: два «а как посмотреть свои ресурсы?», одно «у нас всё нужное» и один эмодзи.
Через месяц счёт — 271 000 USD.
Это не история про недостаток дисциплины. Это история про разорванный контур обратной связи. Инженер, который в четверг поставил replicas: 12 вместо трёх и memory: 8Gi вместо гигабайта, никогда не увидит последствий своего решения: счёт придёт через сорок дней, будет анонимным, попадёт другому человеку и будет обсуждаться на встрече, куда этого инженера не позовут. Ни одного из четырёх условий работающей обратной связи — адресность, своевременность, соразмерность, возможность действовать — не выполнено (обратные связи, задержки).
Работа платформы со стоимостью — это не «сэкономить деньги компании». Это починить контур: сделать расход адресным, показать его тому, кто принимает решение, в момент решения, и дать возможность решить иначе. Экономия — следствие. Если начинать с экономии, получается кампания по борьбе за экономию: шумная, краткосрочная и заканчивающаяся ровно тогда, когда заканчивается внимание руководства.
Эта глава — про механику: как из счёта провайдера сделать адресный счёт, что делать с общими ресурсами, которые невозможно разметить, как ставить лимиты, не ломая работу и инциденты, как разговаривать с командами так, чтобы разговор заканчивался изменением, и как считать стоимость самой платформенной команды, чтобы она не превратилась в налог. Про цену облачных сервисов как таковых — отдельная глава в треке DevOps; здесь мы не пересказываем прайс-листы, а строим то, что делает платформа: учёт, ограничения и разговор.
Три счёта, которые обычно путают
Первая ошибка почти любого обсуждения — считать, что «стоимость платформы» это одно число. Их три, и живут они в разных местах.
| Счёт за инфраструктуру | Счёт за платформенную команду | Счёт за время пользователей | |
|---|---|---|---|
| Что это | облако, лицензии, SaaS-инструменты | зарплаты, найм, обучение | обходы, ожидания, миграции, обучение вашему контракту |
| Кто видит | финансовый отдел, ежемесячно | бюджет штата, раз в год | никто |
| Как растёт | ступеньками и незаметно | ступеньками при найме | линейно с числом команд и обязательных шагов |
| Как сокращается | техническими решениями | сокращением набора обязательств | улучшением опыта, а не документацией |
| Типичный порядок | 60–75 % общей суммы | 15–25 % | 10–30 %, но не измеряется |
Числа на картинке — из сквозного примера этой главы: 18 продуктовых команд, 110 инженеров, платформа из пяти человек, полная стоимость часа инженера 78 USD (зарплата, налоги, оборудование, доля офиса и менеджмента — берите свою цифру у финансистов, а не выдумывайте). Третий столбец получается из простого замера: 2,5 часа в неделю на инженера уходит на то, чего бы не было при идеальной платформе. Это скромная оценка: в реальных замерах опыта разработчика цифра обычно выше (как её мерить).
Смысл картинки не в том, что «время дороже железа». Смысл в асимметрии внимания: первый счёт обсуждают ежемесячно, третий не обсуждают никогда, хотя он больше всего бюджета платформенной команды. Отсюда типовой перекос: платформа месяцами оптимизирует облако на 4 % и одновременно добавляет обязательный шаг, который стоит организации больше, чем вся сэкономленная сумма.
Дальше в главе первые два счёта разбираются подробно, третий возвращается в разделе про налог. Держите в голове все три: любое предложение «давайте запретим командам делать X, чтобы сэкономить» — это перенос денег из первого счёта в третий, и почти всегда с потерями.
Почему анонимный расход всегда растёт
Общий ресурс без адресного счёта деградирует предсказуемо. Каждый участник получает всю выгоду от своего потребления и несёт лишь малую долю затрат — классическая структура, описанная Гарретом Хардином (The Tragedy of the Commons, Science, 1968) и подробно разобранная Элинор Остром, которая показала главное: общий ресурс не обязан деградировать, если у сообщества есть границы, наблюдаемость потребления и понятные правила («Governing the Commons», 1990). Это буквально техническое задание для платформенной команды.
В инженерной практике механизм выглядит так:
requests и реплики"] --> B["Никаких последствий
здесь и сейчас"] B --> C["Сервис перестал падать
в 3 часа ночи"] C --> D["Практика закрепляется:
«ставь с запасом»"] D --> E["Копируется в шаблон
и в соседние команды"] E --> F["Счёт растёт
на 3–5 % в месяц"] F --> G["Через полгода:
кампания по экономии"] G --> H["Разовое сокращение
на 8 %"] H --> I["Внимание уходит,
рост возобновляется"] I --> D F -.-> J["Разрыв: тот, кто платит,
и тот, кто решает, —
разные люди"] J -.-> B
Обратите внимание на петлю D → E → F → G → H → I → D: кампании по экономии не меняют структуру, поэтому система возвращается в исходное состояние. Это архетип «подмена проблемы симптоматическим решением» (архетипы). Точка воздействия находится не в кампании, а в правилах и дефолтах (точки воздействия).
Из этого следуют три рабочих принципа, на которых держится вся глава.
- Расход должен быть адресным. У каждой строки счёта есть владеющая команда, иначе разговаривать не с кем. Доля неразнесённого расхода — метрика качества вашей платформы, а не бухгалтерии.
- Цена должна приходить в момент решения. Не в квартальный отчёт, а в план изменений в пул-реквесте, в шаблон сервиса, в ответ CLI. Отсрочка в 40 дней превращает данные в исторический факт.
- У получателя должна быть возможность действовать. Сообщение «ваш сервис стоит 12 000 USD» без ответа на вопрос «и что мне с этим сделать» — просто источник тревоги.
Учёт: как из счёта провайдера сделать адресный счёт
Разметка — контракт платформы, а не просьба к людям
Все системы учёта расходов в облаке в конечном счёте опираются на метки ресурсов: теги в AWS (cost allocation tags), labels в GCP, tags в Azure, labels и namespace в Kubernetes. Отсюда правило, которое надо принять до начала работы: разметка, которую проставляют люди руками, всегда неполна. Не потому, что люди плохие, — потому что это работа без немедленной пользы для того, кто её делает.
Работающая схема ровно одна: метки проставляет платформа в момент создания ресурса, а человек их не видит. Шаблон сервиса, модуль IaC, контроллер — любой из механизмов инфраструктуры как API знает, кто владелец, потому что владелец указан в описании сервиса.
Минимальный набор ключей, который окупается:
# Обязательные метки, проставляются шаблоном и модулями IaC, не человеком.
# Ключи стабильны навсегда: переименование ключа обнуляет всю историю расходов.
labels:
owner-team: payments # команда-владелец из каталога сервисов
service: payments-api # логический сервис, а не имя ресурса
environment: prod # prod | staging | preview | dev
cost-center: cc-4102 # то, что понимает финансовый отдел
criticality: tier-1 # влияет на допустимый запас и класс хранилища
managed-by: platform # платформенный ресурс или созданный командой сам
Шесть ключей, не двадцать. Каждый лишний обязательный ключ — это ещё одна причина, по которой ресурс не будет создан, и ещё один повод для команды обойти платформу. criticality в этом списке стоит не ради красоты: он связывает деньги с надёжностью — сервису класса tier-3 не нужен горячий резерв в трёх зонах, и это решение принимается один раз в описании, а не каждым инженером заново (SLO и классы обслуживания).
Три механизма, которые удерживают разметку в живом состоянии:
- Запрет на создание без меток. Политика в admission-контроллере или в проверке плана IaC: ресурс без
owner-teamне создаётся. Важно, чтобы отказ был человеческим — с примером правильного описания и ссылкой, а неadmission webhook denied the request. - Ежедневный поиск сирот. Отчёт «ресурсы без владельца, старше 7 дней» с автоматическим тикетом на команду, чья учётная запись их создала (это видно в журнале аудита облака, даже когда меток нет).
- Метка неразнесённого как метрика. Долю расхода без владельца показывают на том же дашборде, где стоимость. Разумная цель — ниже 5 % и снижающаяся. Пока эта доля 40 %, любые разговоры о деньгах с командами будут упираться в «а это точно наше?».
Общие ресурсы: то, что разметить невозможно
Дальше начинается трудная часть. Значительная доля счёта не принадлежит никому конкретно: control plane кластера, NAT-шлюз и egress, балансировщики, реестр образов, стек наблюдаемости, CI-раннеры, резервные мощности, сама инфраструктура платформы. В типичном кластере картина выглядит так:
Здесь два вывода, которые стоит проговорить.
Первый: разговор о requests касается 41 % счёта, а разговор о «выключите лишнее» — почти ничего. Между «сколько запрошено» и «сколько используется» лежит основная сумма, и она полностью в руках продуктовых команд — но только если они видят обе цифры рядом. Именно это платформа обязана показывать по умолчанию: не «ваш сервис стоит X», а «вы платите за 8 ГБ, используете 1,4 ГБ на 95-м процентиле».
Второй: 21 % — это запас под пики и обновления, и он не ошибка. Часть неиспользуемой ёмкости — плата за надёжность и за возможность выкатываться без простоя (ёмкость и запас). Платформа обязана назвать эту сумму вслух и защищать её, иначе первая же кампания по экономии срежет именно её — как самую беззащитную статью.
Общий расход надо разнести. Способов три, и выбор между ними — политический, а не технический.
| Способ | Как считается | Что поощряет | Где ломается |
|---|---|---|---|
| По потреблению | доля от фактического использования (CPU-секунды, ГБ, запросы) | точность, снижение реального потребления | требует данных потребления; малые команды платят мало и не замечают |
| По запросу (requests) | доля от зарезервированного, а не использованного | реалистичные requests, снятие запаса |
команда, честно поставившая запас под пик, платит больше «оптимистов» |
| Поровну между командами | общая сумма делится на число команд | простота, никто не спорит с формулой | крупный потребитель субсидируется мелкими; сигнал бесполезен |
Практика, которая работает лучше всего: разносить по max(потребление, requests) — это одновременно честно (вы платите за то, что зарезервировали) и полезно (мотивирует приводить requests в соответствие с реальностью). Накладные расходы, которые не привязать вообще (control plane, платформенная команда), либо оставляют на общем центре затрат, либо разносят пропорционально уже разнесённой части.
Три правила, которые экономят месяцы споров:
- Метод разнесения — публичный документ на одну страницу, а не логика внутри скрипта. Команда должна уметь воспроизвести свой счёт.
- Метод не меняют чаще раза в год. Каждое изменение обнуляет сравнимость с историей и запускает новый круг споров.
- Спор о методе почти всегда дороже спорной суммы. Если разногласие касается 2 % счёта, зафиксируйте любой вариант и вернитесь к работе.
Конвейер учёта
Технически всё это — конвейер данных, у которого есть свои отказы, своя стоимость и свой владелец.
CUR / BigQuery / Azure Exports"] A2["Потребление в кластере
usage по подам и namespace"] A3["Объёмы наблюдаемости
ГБ логов, ряды метрик"] A4["CI: минуты, кэш, артефакты"] A5["Каталог сервисов:
владельцы и критичность"] end subgraph N["Нормализация"] B1["Единая схема строки расхода
FOCUS: дата, ресурс, метки, сумма"] B2["Валюта, скидки, амортизация
резервов и Savings Plans"] end subgraph AL["Разнесение"] C1["Прямые ресурсы
по меткам"] C2["Общие пулы
по max(usage, requests)"] C3["Остаток:
unallocated"] end subgraph OUT["Доставка"] D1["Витрина: команда × сервис × день"] D2["В момент решения:
план в PR, шаблон, CLI"] D3["Алерты аномалий
в канал команды"] D4["Квартальный отчёт
для руководства"] end A1 --> B1 A2 --> B1 A3 --> B1 A4 --> B1 A5 --> C1 B1 --> B2 --> C1 B2 --> C2 C1 --> D1 C2 --> D1 C1 -.->|нет владельца| C3 C3 --> D4 D1 --> D2 D1 --> D3 D1 --> D4
Полезные опоры, которые не надо изобретать:
- FOCUS — открытая спецификация формата данных о расходах, снимающая различия между провайдерами (focus.finops.org). Если строите витрину — стройте сразу в её терминах: иначе первый же второй провайдер потребует переписать модель.
- FinOps Framework — словарь и набор практик, полезный в первую очередь тем, что даёт общий язык с финансистами (finops.org/framework).
- OpenCost — открытая спецификация и реализация расчёта стоимости нагрузки в Kubernetes (opencost.io); коммерческая надстройка — Kubecost. Полезно, но помните: это ещё один компонент в проде со своими метриками, обновлениями и потреблением.
- Экспорты биллинга: AWS Cost and Usage Report, GCP billing export в BigQuery, Azure Cost Management exports.
- Книга «Cloud FinOps» (J. R. Storment, M. Fuller, O’Reilly) — единственная известная мне книга, где подробно разобран именно организационный слой: кто с кем разговаривает и о чём.
Цена самого учёта. Экспорт CUR за год для организации среднего размера — десятки-сотни гигабайт; запросы к нему в BigQuery или Athena стоят денег; витрина требует ежедневного обновления и присмотра; модель разнесения ломается при каждом изменении инфраструктуры. Реалистичная оценка — 0,2–0,5 человека постоянно плюс несколько сотен долларов в месяц. Это нормально, если счёт измеряется сотнями тысяч, и явно избыточно, если счёт 6 000 USD в месяц: там достаточно тегов и встроенного отчёта провайдера.
Разнесение в коде
Модель разнесения полезно держать в виде читаемого кода, а не в кликах по интерфейсу: её ревьюят, воспроизводят и покрывают тестами.
"""Разнесение месячного счёта по командам: прямые ресурсы плюс доли в общих пулах."""
from collections import defaultdict
from dataclasses import dataclass
@dataclass(frozen=True)
class Line:
"""Строка счёта после нормализации. team=None — ресурс без владельца."""
resource: str
team: str | None
amount: float # USD за период
shared_pool: str | None = None # имя пула, если ресурс общий
@dataclass(frozen=True)
class Usage:
"""Потребление команды в общем пуле за период, нормированные единицы."""
team: str
pool: str
used: float
requested: float
def allocate(lines: list[Line], usage: list[Usage]) -> dict:
direct, pools = defaultdict(float), defaultdict(float)
unallocated = 0.0
# 1. Прямые ресурсы — по метке владельца, общие складываем в пулы. O(L)
for ln in lines:
if ln.shared_pool:
pools[ln.shared_pool] += ln.amount
elif ln.team:
direct[ln.team] += ln.amount
else:
unallocated += ln.amount # сирота: разговаривать не с кем
# 2. Веса в пулах по max(использовано, запрошено). O(U)
weight, pool_total = defaultdict(float), defaultdict(float)
for u in usage:
w = max(u.used, u.requested) # платит тот, кто зарезервировал
weight[(u.pool, u.team)] += w
pool_total[u.pool] += w
shared = defaultdict(float)
for (pool, team), w in weight.items():
if pool_total[pool] > 0:
shared[team] += pools.get(pool, 0.0) * w / pool_total[pool]
# Пул без данных о потреблении не «растворяется» тихо, а честно виден как ничей.
unallocated += sum(a for p, a in pools.items() if pool_total.get(p, 0.0) == 0)
per_team = {t: {"прямые": round(direct.get(t, 0.0), 2),
"общие": round(shared.get(t, 0.0), 2)}
for t in sorted(set(direct) | set(shared))}
billed = sum(v["прямые"] + v["общие"] for v in per_team.values()) + unallocated
return {"по командам": per_team,
"неразнесено": round(unallocated, 2),
"доля неразнесённого": round(unallocated / billed, 4) if billed else 0.0}
Сложность — O(L + U) по времени и O(T + P) по памяти, где L — строк счёта, U — записей потребления, T — команд, P — пулов; сортировка ключей в выводе добавляет O(T log T). Для месячного CUR это миллионы строк, поэтому в проде агрегация делается запросом на стороне хранилища, а этот код служит исполняемой спецификацией модели, по которой можно проверить любой отчёт.
Отдельный запрос, который стоит настроить в первую неделю, — не «кто самый дорогой», а «что изменилось»:
-- Неделя к неделе по командам: ищем не самых дорогих, а тех, у кого поменялось.
-- Абсолютный размер счёта — известный факт; изменение — новость.
WITH weekly AS (
SELECT owner_team,
service,
date_trunc('week', usage_date) AS wk,
sum(amortized_cost) AS usd
FROM billing_normalized
WHERE usage_date >= current_date - INTERVAL '9 weeks'
GROUP BY 1, 2, 3
),
paired AS (
SELECT owner_team, service, wk, usd,
lag(usd) OVER (PARTITION BY owner_team, service ORDER BY wk) AS prev,
avg(usd) OVER (PARTITION BY owner_team, service
ORDER BY wk ROWS BETWEEN 8 PRECEDING AND 1 PRECEDING) AS base
FROM weekly
)
SELECT owner_team, service, wk,
round(usd) AS usd,
round(usd - prev) AS delta_usd,
round(100 * (usd - prev) / nullif(prev, 0)) AS delta_pct
FROM paired
WHERE wk = date_trunc('week', current_date - INTERVAL '1 week')
AND usd - prev > 400 -- порог в деньгах, а не в процентах
AND usd > 1.4 * nullif(base, 0) -- и это не обычная сезонность
ORDER BY delta_usd DESC
LIMIT 20;
Два порога здесь принципиальны. Порог в деньгах отсекает шум вида «сервис подорожал на 300 %, с 4 до 16 USD». Сравнение с собственной базой отсекает нормальные недельные колебания. Без них вы получите отчёт, который через две недели перестанут открывать.
Юнит-экономика: абсолютный счёт почти ничего не значит
«Счёт вырос на 42 %» — не факт о проблеме. Если за то же время число запросов выросло вдвое, стоимость единицы работы упала, и это успех. Если выручка выросла на 9 % — это проблема. Абсолютное число не значит ничего без знаменателя.
Поэтому платформа должна публиковать не сумму, а набор удельных величин. Каждая из них измерима на существующих данных, и у каждой есть известный способ соврать — фиксируйте его сразу.
| Метрика | Знаменатель | Что показывает | Как врёт |
|---|---|---|---|
| Стоимость 1 млн запросов | запросы к прод-сервисам | эффективность рантайма | падает при росте нагрузки сама по себе, из-за постоянных затрат |
| Стоимость сервиса в месяц | живые сервисы из каталога | цену «ещё одного сервиса» | мёртвые сервисы в каталоге занижают показатель |
| Стоимость выката | успешные выкаты в прод | цену конвейера и непрода | ухудшается при редких выкатах: постоянные затраты делятся на меньшее число событий |
| Стоимость окружения в сутки | активные превью-стенды | цену изоляции | не учитывает стенды, забытые включёнными |
| Инфраструктура на инженера | инженеры в продуктовых командах | масштабируемость модели | растёт при найме продуктовых команд медленнее, чем кажется |
| Доля непрода в счёте | весь счёт | дисциплину окружений | легко «улучшить», перенеся нагрузку в прод-аккаунт |
| Доля неразнесённого | весь счёт | качество учёта | улучшается «настройкой правил по умолчанию», а не разметкой |
Хороший набор для дашборда руководителя — три строки: общий счёт, стоимость единицы бизнес-работы и доля неразнесённого. Всё остальное — рабочие метрики платформы, не для еженедельного показа (что вообще считать метрикой продукта, юнит-экономика).
Отдельно стоит сказать про стоимость надёжности. Разница между 99,9 % и 99,99 % — это не «чуть больше усилий», это вторая зона доступности, горячий резерв, дублирование хранилища и, часто, удвоение части счёта. Разговор о деньгах и разговор об SLO — один разговор:
запрошенный уровень → архитектурное следствие → строка в счёте
99,0 % (tier-3) одна зона, восстановление из бэкапа 1,0×
99,9 % (tier-2) две зоны, тёплый резерв, реплика 1,6×
99,99 % (tier-1) две-три зоны, горячий резерв, мультирегион 2,4–3,0×
Когда команда просит «сделайте нам как у платежей», правильный ответ — не «нельзя», а «можно, это добавит примерно столько-то в месяц; вот три варианта, выбирайте». Это превращает спор о качестве в решение с ценой (SLO, бюджет ошибок).
Лимиты и квоты: ограничивать, не ломая работу
Учёт показывает, ограничения удерживают. Инструментов здесь пять, и они очень разные по силе побочных эффектов.
| Механизм | Как работает | Сила | Побочный эффект |
|---|---|---|---|
| Разумные значения по умолчанию | шаблон сервиса задаёт скромные requests, срок жизни, класс хранилища |
очень высокая | почти нет, если легко переопределить |
| Мягкий бюджет с уведомлением | порог на команду, уведомление при 80 % и 100 % | высокая | привыкание, если уведомление приходит всем и всегда |
| Автоматический срок жизни (TTL) | превью-стенды и временные ресурсы умирают сами | высокая | ярость, если умерло во время демо: нужна кнопка продления |
| Жёсткая квота | нельзя создать ресурс сверх лимита | средняя | блокирует работу, включая срочную; требует канала эскалации |
| Перевыставление счёта (chargeback) | сумма списывается с бюджета команды | зависит от компании | политика, торг, оптимизация отчётности вместо расхода |
Главный вывод, который стоит запомнить: самый сильный рычаг — не запрет, а значения по умолчанию. Если шаблон сервиса даёт requests: 200m/256Mi, TTL превью-стенда 48 часов и класс хранилища «стандартный», то большинство сервисов останутся в этих рамках просто потому, что менять их незачем. Это работа на уровне правил игры, а не параметров, и она в разы дешевле любой системы согласований (точки воздействия).
Технические опоры в Kubernetes — ResourceQuota (потолок на namespace) и LimitRange (значения по умолчанию и границы для пода). Вторая штука интереснее первой: она задаёт дефолты, то есть работает без конфликта.
# Бюджет команды: описание, из которого генерируются квота, алерты и строка в отчёте.
apiVersion: platform/v1
kind: TeamBudget
metadata:
name: payments
spec:
costCenter: cc-4102
monthlyBudgetUSD: 18000 # согласовано с владельцем команды, не спущено сверху
notify:
channel: "#team-payments" # в канал команды, а не в общий чат платформы
thresholds: [0.8, 1.0, 1.25] # предупреждение, достижение, серьёзное превышение
environments:
prod:
hardQuota: false # прод жёстко не режем никогда
overspendAction: notify
staging:
hardQuota: true
cpu: "120"
memoryGi: "480"
preview:
hardQuota: true
maxConcurrent: 12
ttlHours: 48 # продлевается кнопкой, продление логируется
emergencyOverride:
allowed: true
maxDurationHours: 24 # аварийный подъём лимита без согласований
autoTicket: true # запись создаётся автоматически, ревью на следующий день
Три правила, нарушение которых превращает квоты во врага.
Первое: квота никогда не мешает тушить инцидент. Дежурный в три часа ночи должен уметь поднять лимит одной командой, без согласований, а система обязана сама создать запись об этом и напомнить через сутки. Любой механизм, который мешает восстановлению, будет обойден — и правильно сделает (разбор инцидентов).
Второе: прод и непрод — разные режимы. Жёсткие квоты уместны на превью-стендах и стейджинге, где цена ошибки — неудобство. На проде жёсткая квота означает, что в момент пикового трафика платформа запретит автомасштабирование, и вы получите инцидент, за который заплатите больше, чем сэкономили (окружения и их цена).
Третье: бюджет согласуется с владельцем, а не спускается. Число, которое команда не признала своим, не работает как ограничение — оно работает как повод для спора при первом же превышении.
Жизненный цикл бюджета команды удобно держать как явный автомат: он определяет, какие уведомления и какие действия существуют вообще.
Заметьте, чего в автомате нет: состояния «платформа сама срезала ресурсы команде». Такое действие допустимо ровно в двух случаях — ресурс без владельца старше согласованного срока и превью-стенд с истёкшим TTL. Всё остальное решает команда: платформа предлагает, показывает цену и напоминает, но не удаляет чужое.
Команда, которой стандартная модель не подходит
У части команд профиль расходов не описывается общими классами: команда машинного обучения с GPU и пиковыми обучениями, аналитика с ночным batch на сотни узлов, команда с арендованным железом под лицензию. Месячный потолок для них бесполезен: расход по своей природе неровный, и любой порог будет либо пробит всегда, либо бессмысленно велик.
Правильный ход — не втискивать их в общую форму и не оставлять без учёта, а дать третий вариант: отдельный класс с собственным правилом. Бюджет на задачу или на эксперимент вместо месячного потолка; разнесение по GPU-часам вместо CPU; отдельный проект в облаке с обязательством по разметке и еженедельной выгрузкой в общую витрину. Требований к такой команде остаётся ровно два: расход виден в общей витрине и у него есть владелец. Всё остальное — их дело.
Это тот же принцип, что и с золотым путём в целом: путь остаётся путём, пока у съезда есть обозначенный вход, а не забор (золотой путь). Команда, которую вынудили изображать соответствие чужой модели, начнёт прятать расход — заводить ресурсы в личных аккаунтах, класть тяжёлые задачи в общий кластер под видом обычных подов. Вы потеряете и учёт, и доверие, причём в таком порядке.
Разговор с командами о деньгах
Техническая часть — процентов сорок работы. Остальное — разговор, и именно на нём чаще всего всё разваливается.
Три режима видимости
| Режим | Что происходит | Эффект | Издержки |
|---|---|---|---|
| Невидимо | счёт общий, команды не знают своих цифр | расход растёт, оптимизирует только платформа и только техническими средствами | ноль политики, максимум расхода |
| Показ расхода (showback) | команда видит свой счёт, деньги списываются централизованно | 70–80 % возможного эффекта | нужна витрина и разговоры |
| Перевыставление (chargeback) | сумма уходит в бюджет команды | сильнейший стимул | торг, оптимизация отчётности, попытки уйти в «серые» ресурсы, месяцы согласований |
Для подавляющего большинства компаний правильный ответ — showback: он даёт основную часть эффекта при малой политической цене. Chargeback оправдан там, где у команд есть настоящие бюджеты и настоящая свобода ими распоряжаться, — иначе вы получите не экономию, а перекладывание строк между центрами затрат и очень много встреч.
И зеркальное предупреждение: chargeback без возможности влиять на расход — чистое зло. Если команда не может выбрать класс хранилища, потому что платформа спрятала выбор за абстракцией, а счёт за это хранилище ей выставляют, вы наказываете людей за решения, которые приняли сами (где скрывать сложность).
Что не работает
- Письмо «всем командам сократить расходы на 20 %». Адресат — все, значит никто. Команды с малым счётом потратят время впустую, крупные потребители проигнорируют.
- Рейтинг «топ-10 самых дорогих команд». Самый дорогой сервис часто и есть самый нагруженный — публичный рейтинг наказывает за успех. Плюс порождает локальную оптимизацию: команды начинают улучшать свою строчку, а не общий результат (локальная оптимизация).
- Общий дашборд без адресата. Красивая витрина, куда заходят три человека из платформы. Данные должны приходить к людям, а не ждать их.
- Требование обосновать каждый ресурс. Превращает платформу в бюрократию и убивает самообслуживание, ради которого всё строилось (самообслуживание).
- Разговор в терминах процентов. «Ваш расход вырос на 60 %» — непонятно. «Ваш расход вырос на 3 400 USD в месяц, это две трети стоимости стажёра» — понятно.
Что работает
Хороший разговор о деньгах устроен как разговор о продукте: адресно, с ценой, с вариантами и с уважением к чужим приоритетам.
реплик 12 при средней загрузке 11 % P->>T: сообщение в канал команды, а не в общий чат Note over P,T: «Ваш payments-worker: 8 200 USD/мес.
Похожие сервисы — 1 800–2 400.
Три варианта, оценка экономии у каждого.» T-->>P: «8Gi поставили после OOM в феврале, боимся снижать» P->>T: предлагает VPA в режиме рекомендаций + алерт на OOM P->>R: готовый PR: requests 2Gi, limits 3Gi, HPA по нагрузке T->>R: ревью, свои правки, merge R-->>D: через неделю: 2 600 USD/мес P->>T: подтверждение результата и благодарность в канал Note over P,T: закрытие петли — то, ради чего всё делалось
Разберём, что именно сделало этот разговор рабочим.
- Разбор до предъявления. Платформа пришла не с вопросом «почему у вас дорого», а с готовым диагнозом. Требовать от команды самостоятельно расследовать счёт — перекладывать свою работу.
- Сравнение с похожими. «8 200 против 1 800–2 400 у похожих сервисов» действует сильнее любого абсолютного числа, потому что отвечает на вопрос «а это вообще много?».
- Названа причина отказа и снят страх. «Боимся снижать после OOM» — реальная и уважительная причина. Правильный ответ — не «всё равно снижайте», а инструмент, снимающий риск: рекомендации VPA, алерт, постепенное снижение.
- Готовый PR. Разница между «сделайте» и «вот изменение, посмотрите» — это разница между задачей в чужом бэклоге и десятью минутами ревью.
- Закрытие петли. Показать результат и сказать спасибо — то, что делает следующий разговор возможным. Без этого шага у команды остаётся ощущение, что её отчитали.
Шаблон сообщения, который стоит держать под рукой:
Сервис: payments-worker (команда payments)
Сейчас: 8 200 USD/мес — 3,1 % всего счёта организации
Похожие сервисы того же класса: 1 800–2 400 USD/мес
Что видим: requests 8Gi при p95 потребления 1,4Gi; 12 реплик при средней загрузке 11 %
Варианты:
A. requests 2Gi, HPA 3–12 реплик → примерно −5 600 USD/мес, риск: нужен алерт на OOM
B. requests 4Gi, реплик 6 → примерно −3 100 USD/мес, риск минимальный
C. оставить как есть → это осознанное решение, зафиксируем причину
PR с вариантом B готов: <ссылка>. Нужно ваше решение до конца недели, дальше не напоминаем.
Последняя строка важна: «дальше не напоминаем». Платформа предлагает и показывает цену, но не преследует. Вариант «оставить как есть» — легитимный: у команды может быть контекст, которого у вас нет. Зафиксированная причина превращает расход из бесхозного в осознанный, а это и была цель.
Куда приходит цена
Отчёт раз в месяц — слабейшая форма обратной связи. Сильные — те, что встроены в момент решения:
- План изменений в пул-реквесте показывает дельту стоимости: «+2 узла, примерно +410 USD/мес» (инфраструктура как API).
- Шаблон сервиса при генерации называет ожидаемую стоимость выбранного класса.
- Ответ CLI после создания ресурса содержит строку с ценой и ссылкой на способ её снизить.
- Комментарий бота в PR превью-стенда: «стенд живёт 48 часов, продление — кнопкой».
- Страница сервиса в каталоге показывает расход рядом с SLO и владельцем — там, где люди и так бывают.
Соотношение примерно такое: одно сообщение в момент решения стоит десяти строк в квартальном отчёте.
Стоимость платформенной команды: граница между сервисом и налогом
Пять инженеров в платформе — это пять инженеров, которые не пишут продукт. Общая арифметика окупаемости разобрана в главе про платформу как продукт и в главе про самообслуживание; здесь добавим то, что обычно теряется, — вычитаемое.
чистая выгода = экономия
− стоимость платформенной команды
− инфраструктура самой платформы
− цена входа (обучение + миграции)
− цена обходов (сколько команды делают мимо платформы)
− цена ожиданий (заявки, согласования, ответы поддержки)
Три последних слагаемых — это и есть тот самый третий счёт с картинки в начале главы. Их не надо оценивать «в процентах продуктивности», их надо считать по операциям: сколько раз в год команда проходит миграцию, сколько часов занимает обучение новому контракту, сколько инженеро-часов ушло на собственные скрипты в обход платформы (это видно по репозиториям — см. принятие).
Практика, которая делает разговор честным: квартальный публичный отчёт платформы на одну страницу.
Q3, платформенная команда (5 человек)
1. Стоимость команды 187 500 USD
2. Инфраструктура платформы 31 200 USD
3. Сэкономлено времени команд (по операциям) 1 420 ч ≈ 110 760 USD
4. Сэкономлено на счёте за облако (проверяемо) 96 000 USD
5. Цена входа: 2 миграции × 18 команд 310 ч ≈ 24 180 USD
6. Оценка обходов: 4 команды со своими пайплайнами 260 ч ≈ 20 280 USD
Итого: 206 760 − 218 700 = −11 940 USD за квартал
Вывод: около нуля. Сокращаем поддержку gRPC-шаблона (2 пользователя)
и переносим ёмкость на приёмку конвейера, где очередь.
Отчёт с отрицательным итогом — признак зрелой команды, а не провала. Он предъявляет два числа, которые обычно прячут: цену миграций и цену обходов. И он ведёт к правильному действию: не «добавим ещё функций, чтобы оправдаться», а «сократим то, чем не пользуются». Функция с двумя пользователями почти всегда убыточна: экономия делится на двоих, поддержка постоянна и навсегда.
Отсюда — гейт для любой новой платформенной функции, три вопроса перед началом:
- Сколько команд будут пользоваться этим через полгода? Если ответ «одна-две», это работа самой команды, а платформа даёт точку расширения.
- Сколько часов в месяц это экономит и откуда взята цифра? Не «ускорит разработку», а измеримая базовая линия и способ проверки.
- Сколько это стоит в поддержке через год? Обновления, дежурство, обратная совместимость. Если ответа нет — цена принимается равной 0,25 человека, и решение пересматривается с этой цифрой.
И самое неприятное следствие: если платформа не окупается, правильный ход — сокращать её набор обязательств, а не расширять. Расширение увеличивает знаменатель издержек, а принятие растёт медленнее. Подробнее — в главе про платформенную команду.
Типовые провалы
Обёртка над облаком, скрывающая цену. Платформа спрятала выбор типа инстанса и класса хранилища за абстракцией tier: standard, но не показала, во что это обходится. Команда не может ни узнать стоимость, ни повлиять на неё. Через год никто в компании не в состоянии объяснить счёт, а любая попытка оптимизации упирается в «это платформа так решила». Правило: абстракция может убрать выбор, но обязана показать цену.
Портал с дашбордом расходов, которым никто не пользуется. Классика: полгода работы, красивые графики, восемь заходов в месяц, из них шесть — от автора. Причина всегда одна: данные ждут человека вместо того, чтобы приходить к нему. Лечится не редизайном, а доставкой цены в момент решения.
«Мы сделали абстракцию» поверх трёх разных потребностей. У аналитики почасовые пики и терабайты холодных данных, у платежей — постоянная нагрузка и жёсткие требования к задержке, у внутренних инструментов — почти нулевой трафик. Единая модель tier не описывает ни одну из трёх: платежи получают избыточное хранилище, аналитика — недостаточную ёмкость, внутренние инструменты платят за резерв, который им не нужен. Разнесение расходов по такой модели ещё и врёт, потому что удельные величины несравнимы между группами. Признак вовремя: если для трёх ваших крупнейших потребителей приходится делать три исключения — это не одна абстракция, а три продукта (где скрывать сложность).
Оптимизация ради оптимизации. Три недели инженера платформы (около 9 400 USD полной стоимости), чтобы сэкономить 400 USD в месяц. Окупаемость — два года, при условии что решение доживёт. Такие задачи надо приоритизировать явно, как продуктовые (приоритизация):
Резервирование под то, что скоро мигрирует. Годовые Savings Plans или зарезервированные инстансы (что это такое) дают скидку 20–40 %, но фиксируют вас в конкретном профиле потребления. Купить резерв за месяц до переезда на другой тип инстансов — оплатить скидку дважды. Правило: резервируется только та часть базовой нагрузки, про которую вы уверены, что она не изменится в горизонте обязательства, обычно 50–70 % минимума за прошлый квартал. Остальное — по требованию.
Учёт без полномочий. Появляется человек «по FinOps», который делает отчёты и рассылает их по почте. Полномочий менять шаблоны, дефолты и политики у него нет. Через два квартала отчёты перестают открывать. Учёт расходов работает только когда он встроен в платформу, а не пристроен сбоку (границы команд).
Уплотнение узлов как ответ на всё. Bin-packing и консолидация действительно снижают счёт за узлы, но они же увеличивают связность отказов: одна нода теперь несёт больше сервисов, вытеснение затрагивает больше пользователей, а всплеск потребления одного пода задевает соседей. Это сделка, а не улучшение: считайте её вместе с надёжностью, а не отдельно (ёмкость и запас).
Цена владения инструментами учёта
Никакой инструмент здесь не является ни решением, ни признаком зрелости. У каждого есть цена, и назвать её надо до внедрения.
| Инструмент | Что реально получаете | Чем платите | Когда не нужен |
|---|---|---|---|
| Встроенные отчёты провайдера | ноль усилий, приемлемая точность по тегам | нет разнесения общих ресурсов, слабо про Kubernetes | почти никогда — начинать надо с них |
| Экспорт биллинга в хранилище (CUR, BigQuery) | полный контроль, свои модели, история | хранение и запросы стоят денег; нужен владелец витрины | счёт меньше нескольких десятков тысяч в месяц |
| OpenCost / Kubecost | стоимость нагрузки в кластере по namespace и поду | ещё один компонент в проде: обновления, метрики, ресурсы | один кластер и десяток сервисов: хватит usage-метрик |
| Коммерческие FinOps-платформы | готовые дашборды, рекомендации, поддержка | подписка, обычно процент от счёта; чужая модель разнесения | пока не сделана разметка — инструмент не починит её за вас |
| Плагин расходов в портале | цена там, где люди уже бывают | ещё один плагин портала со своими обновлениями | если портал не используется, ничего не изменится |
Общее правило: инструмент не создаёт данные, он их показывает. Если 40 % расхода не размечено, покупка платформы за 30 000 USD в год покажет вам те же 40 % неизвестности в более красивом виде. Порядок работ всегда один: разметка → модель разнесения → доставка в момент решения → и только потом, если объём оправдывает, инструмент.
С чего начать: первые шесть недель
Практический маршрут для команды, у которой сейчас нет ничего.
- Неделя 1. Включить экспорт биллинга в хранилище. Посчитать одну цифру — долю расхода без метки владельца. Это ваша базовая линия и первый разговор с руководством.
- Неделя 2. Договориться о шести обязательных ключах меток. Внести их в шаблон сервиса и модули IaC. Не рассылать просьбу проставить теги руками — это не сработает.
- Неделя 3. Собрать простейшую витрину: команда × сервис × день. Опубликовать модель разнесения на одну страницу. Показать трём командам их цифры и спросить, узнают ли они себя.
- Неделя 4. Настроить алерт на изменение, а не на абсолютное значение, с порогом в деньгах. Отправлять в каналы команд.
- Неделя 5. Взять три самых дорогих сервиса и провести три личных разговора по шаблону из этой главы, с готовыми PR. Не рассылать письмо всем.
- Неделя 6. Поставить скромные значения по умолчанию в шаблоне и TTL на превью-стенды. Это даст больше, чем всё остальное вместе взятое, и не потребует ни одного согласования.
Чего в этом списке нет: покупки инструмента, введения chargeback, жёстких квот на проде и общего письма про экономию. Всё это может понадобиться позже — но не в первые шесть недель, когда нет данных.
Типичные ошибки
- Начинать с экономии, а не с видимости. Кампания без данных даёт разовый эффект и стойкое раздражение.
- Требовать разметку от людей. Метки ставит платформа при создании ресурса, иначе их не будет.
- Считать абсолютный счёт без знаменателя. Рост счёта при росте бизнеса нормален; рост удельной стоимости — сигнал.
- Жёсткие квоты на проде и публичные рейтинги «самых дорогих команд». Первое покупает инцидент за небольшие деньги, второе наказывает за нагрузку и поощряет локальную оптимизацию.
- Предъявлять счёт без диагноза и без вариантов. Перекладывание своей работы на пользователя.
- Прятать цену за абстракцией и одновременно выставлять её команде. Ответственность без возможности влиять — самый быстрый способ потерять пользователей платформы.
- Не считать цену миграций и обходов. Без этих слагаемых модель окупаемости всегда положительна и всегда неверна.
- Оптимизировать то, что легко измерить, вместо того, что дорого. Разрыв между
requestsи потреблением обычно интереснее, чем весь длинный хвост мелких сервисов. - Менять модель разнесения каждый квартал. История становится несравнимой, доверие к цифрам — нулевым.
Мини-итог
Стоимость платформы — это три разных счёта: инфраструктура, платформенная команда и время продуктовых команд. Первый обсуждают каждый месяц, третий не считают никогда, хотя он часто больше бюджета всей платформенной команды.
Работа со стоимостью — это не экономия, а починка контура обратной связи. Расход должен быть адресным (метки ставит платформа, а не человек), общие ресурсы должны разноситься по публично описанному правилу (лучше всего по max(потребление, requests)), а доля неразнесённого — быть метрикой качества платформы, а не бухгалтерской деталью. Абсолютное число ничего не значит без знаменателя: считайте удельные величины и следите за ними, а не за суммой.
Ограничения работают в обратном порядке к своей заметности: скромные значения по умолчанию и TTL сильнее любых жёстких квот, а жёсткая квота на проде — способ купить инцидент за небольшие деньги. Квота никогда не должна мешать тушить пожар.
Разговор о деньгах устроен как продуктовый: адресно, с готовым диагнозом, со сравнением с похожими, с вариантами и ценой каждого, с готовым PR и с правом команды сказать «оставим как есть». А сама платформенная команда обязана публиковать честный счёт своей окупаемости — вместе с ценой миграций и обходов. Отчёт, из которого следует «сокращаем то, чем не пользуются», полезнее любого отчёта, из которого следует «нам нужно больше людей».
Что дальше
Мы научились считать деньги и разговаривать о них. Следующая тема — самая дорогая операция платформы из всех: перевод десятков команд с одного решения на другое, где цена входа из абстрактного слагаемого превращается в реальные месяцы чужой работы.
Миграции: как переводить десятки команд и не сорвать поставку
Источники
- FinOps Foundation: FOCUS — открытая спецификация данных о расходах, и FinOps Framework — общий язык с финансистами.
- J. R. Storment, M. Fuller. Cloud FinOps, O’Reilly — организационный слой: кто с кем разговаривает и о чём. OpenCost — расчёт стоимости нагрузки в Kubernetes.
- Kubernetes: ResourceQuota, LimitRange. AWS: CUR, cost allocation tags, Savings Plans. GCP billing export, Azure Cost Management exports.
- G. Hardin. The Tragedy of the Commons, Science, 1968; E. Ostrom. Governing the Commons, Cambridge University Press, 1990 — почему общий ресурс не обязан деградировать, если есть границы, видимость потребления и правила.