{ "index": 115, "slug": "editorial-2024-10-field-capacity-cost", "title": "Ёмкость и стоимость: как выбрать следующий предел, а не самый большой инстанс", "excerpt": "Большой инстанс может улучшить одну latency-метрику и одновременно увеличить расход и операционный риск. Разбираем причинную цепочку, проверку и безопасный критерий следующего шага.", "contentHtml": "

На разборе ёмкости команда видит знакомую картину: после перехода на 4 CPU p95 снизился, а резерв и счёт выросли. Карточка с 1,6 CPU уже показывала очередь и почти касалась SLO, поэтому большой инстанс выглядит очевидным ответом. Цена ошибки — закрепить дорогую конфигурацию без доказанного эффекта, принять одну удачную latency-точку за решение и потерять понятную границу возврата.

\n

Тезис: следующий размер выбирают не по лучшему p95, а по следующему проверяемому пределу. В одной карточке нужно связать workload, выделенный ресурс, сигнал производительности, единицу тарификации и условие остановки. Если хотя бы одно звено неизвестно, результатом должна быть остановка или отдельный разбор, а не заказ самого большого инстанса.

\n

Начните с одного вопроса

\n

Вопрос «сколько CPU нам нужно?» слишком короткий. Для проверки запишите: какой workload измеряем, в каком окружении, за какой период, какой SLO защищаем и что именно меняем. Для сервиса это может быть поток HTTP-запросов, число реплик, requests и limits контейнера, p95 задержки, ошибки и сигнал очереди. Для финансовой части добавьте billing unit: час виртуальной машины, CPU-hour, GiB-hour, запрос или трафик.

\n

Так появляются разные, но связанные факты. Workload описывает работу. Allocation описывает обещанный системе резерв. Usage показывает фактическое потребление. Performance показывает результат для пользователя. Billing expression объясняет, как провайдер переводит использование или резерв в деньги. Низкий usage не заменяет allocation и не доказывает экономию. Зелёный SLO не объясняет тариф.

\n

Ситуация считается описанной только после фиксации scope. Запись «p95 стал лучше» не воспроизводится без маршрута, нагрузки, числа реплик, периода, версии и способа измерения. Запись «стоимость выросла на 20%» не воспроизводится без счёта, валюты, региона, скидки и правила агрегации.

\n
Диаграмма трёх вариантов ёмкости: текущий предел, следующий малый предел и большой инстанс с разными кривыми производительности, модельной стоимости и операционного риска
Кривая показывает, почему лучший p95 не является достаточным критерием. На каждой точке нужно отдельно проверить стоимость и риск, а пунктирная граница задаёт момент остановки сравнения.
\n

Разделите request, limit, quota и цену

\n

В Kubernetes эти слова отвечают на разные вопросы. Scheduler использует resource request при выборе узла. Limit задаёт ограничение выполнения контейнера: CPU может быть ограничен throttling, а превышение memory limit может привести к вмешательству OOM-механизма. ResourceQuota ограничивает суммарные ресурсы или объекты в namespace. LimitRange задаёт допустимые minimum, maximum и default для объектов на этапе admission.

\n

Ни один из этих механизмов не является тарифом. Если в namespace осталось 6 CPU quota, это говорит о политике допуска, но не о свободной ёмкости кластера, состоянии соседних workloads, задержке сервиса или цене часа. Если контейнер использует 31% CPU, это говорит об usage относительно выбранной базы, но не о том, сколько провайдер выставит в счёте. Сначала подтверждают policy и runtime, затем отдельно сверяют billing.

\n
Как читать сигнал перед изменением ёмкости
СигналЧто он подтверждаетЧего он не подтверждаетСледующая проверка
CPU usage 31%Наблюдаемое потребление в выбранном окнеБезопасный request и ценуPeak, throttling, p95 и billing unit
Quota ещё не исчерпанаОграничение namespace не достигнутоДоступный узел и запас по SLOAdmission, cluster capacity и saturation
p95 ниже SLOМетрика укладывается в заданное окноЭкономичность и причинность улучшенияОдинаковый workload, период и allocation
Счёт выросИзменился итог billing-периодаПричину в одном ресурсеSKU, rate, replicas, storage и traffic
\n

Практический вывод отрицательный: нельзя уменьшать request только потому, что средний CPU низкий, и нельзя увеличивать limit только потому, что quota это допускает. Для памяти редкий пик важнее среднего значения. Для CPU полезно посмотреть throttling и очередь. Для latency — выяснить, не сидит ли время в сети, блокировке или внешнем сервисе.

\n

Соберите прозрачную модель стоимости

\n

Формула нужна не для имитации счёта, а для проверки причинной цепочки. В учебной модели отдельно обозначим фиксированную часть, резерв CPU, резерв памяти и ставки. Если провайдер считает инстанс целиком, а не request контейнера, формула должна отражать инстанс. Если billing unit неизвестна, арифметику нужно остановить, а не заменить процентом utilization.

\n
node - <<'NODE'\nconst model = {\n  fixedPerHour: 0.36,\n  cpuHour: 0.08,\n  memoryGiBHour: 0.01,\n  hours: 24,\n  replicas: 1\n};\n\nconst variants = [\n  { name: 'current', cpu: 1.2, memoryGiB: 2, p95Ms: 248, queueGrowth: false },\n  { name: 'next-limit', cpu: 1.4, memoryGiB: 2, p95Ms: 248, queueGrowth: false },\n  { name: 'saturation-stop', cpu: 1.6, memoryGiB: 2, p95Ms: 296, queueGrowth: true },\n  { name: 'large-instance', cpu: 4, memoryGiB: 6, p95Ms: 208, queueGrowth: false }\n];\n\nfor (const variant of variants) {\n  const hourly = model.fixedPerHour\n    + variant.cpu * model.cpuHour\n    + variant.memoryGiB * model.memoryGiBHour;\n  const daily = hourly * model.hours * model.replicas;\n  const headroomMs = 300 - variant.p95Ms;\n  console.log({ ...variant, hourly, daily, headroomMs });\n}\nNODE
\n

Команда не обращается к кластеру, облаку или telemetry. Она воспроизводит только учебную арифметику на Node.js и печатает одинаковые результаты на машине с установленным Node. Для current получится 0,476 условной единицы в час и 11,424 за сутки; для next-limit — 0,492 в час и 11,808 за сутки. Это не валюта и не реальный invoice.

\n

В рабочей карточке рядом с формулой должны лежать источник значения, единица и период. Ставка может быть tiered, а расход — агрегироваться по проекту или аккаунту. Поэтому сравнение двух конфигураций по одной строке «CPU × ставка» безопасно только как черновая модель, пока billing export или rate card не подтверждают остальные компоненты.

\n

Пример: три предела и отрицательная ветка

\n

Рассмотрим фиксированный workload за 24 часа, одну реплику и SLO p95 не выше 300 мс. Текущая точка использует 1,2 CPU и 2 GiB памяти. Следующий малый предел меняет CPU до 1,4, сохраняя память и окно. В обоих учебных случаях p95 равен 248 мс, поэтому улучшение от изменения не доказано: новая точка всего лишь остаётся сравнимой.

\n

Вариант saturation-stop с 1,6 CPU показывает p95 296 мс и рост очереди. Headroom равен 4 мс. Даже если quota позволяет такой request, сравнение нужно остановить. Условная стоимость по скрипту равна 12,192 за сутки. Следующий рост ресурса не объясняет, почему растёт очередь.

\n

Большой вариант резервирует 4 CPU и 6 GiB. p95 равен 208 мс, CPU usage в карточке — 31%, а модельная стоимость — 17,76 за сутки. Это показывает, что latency можно получить за счёт большого резерва. Это не доказывает, что резерв нужен, что применена правильная ставка или что его можно без риска вернуть назад.

\n
Учебное сравнение вариантов; числа не являются production-измерениями
ВариантРесурсPerformanceСтоимость за 24 чРешение
current1,2 CPU; 2 GiBp95 248 мс; запас 52 мс11,424 uБаза сравнения
next-limit1,4 CPU; 2 GiBp95 248 мс; запас 52 мс11,808 uПроверить соседний шаг
saturation-stop1,6 CPU; 2 GiBp95 296 мс; запас 4 мс; растёт очередь12,192 uОстановить сравнение
large-instance4 CPU; 6 GiBp95 208 мс; CPU 31%17,76 uНе выбирать по одному p95
\n

Таблица не превращает performance, cost и risk в общий score. Для такого score пришлось бы заранее определить веса, владельца и допустимые trade-off. Здесь важна сохранённая отрицательная ветка: хороший p95 при неизвестной цене не даёт согласия, а низкая цена при headroom 4 мс не даёт разрешения продолжать.

\n

Где проходит граница Kubernetes

\n

Request влияет на планирование, но не обещает, что приложение всегда получит ровно это количество CPU. Limit задаёт верхнюю границу исполнения и имеет собственные последствия. Quota и LimitRange могут не пропустить объект или назначить ему default, но не проверяют пользовательский SLO и не рассчитывают итоговый счёт.

\n

Из этого следует последовательность владельцев. Платформенная policy подтверждает, что объект допустим. Scheduler и runtime показывают, что workload размещён и исполняется. Сервисные метрики показывают latency, ошибки и saturation. Финансовый источник показывает SKU, usage unit, base unit, tier и период. Один владелец или один dashboard не заменяют четыре вида evidence.

\n

Особенно опасна фраза «в quota есть запас, значит можно уменьшить инстанс». Quota — это потолок политики, а не рекомендация по размеру и не прогноз поведения. И обратная фраза тоже неверна: превышение request не означает автоматически, что надо покупать большой инстанс. Сначала нужно установить bottleneck и проверить малый соседний шаг.

\n

Определите stop condition заранее

\n

Stop condition защищает решение от давления одного удачного графика. Для учебного примера зададим остановку при headroom меньше 15 мс, при росте очереди, при неизвестной billing unit или при одновременной смене CPU, памяти, сети и класса машины. Эти условия не выбирают победителя. Они говорят, когда текущая гипотеза больше не проверяется тем же экспериментом.

\n

Возврат тоже имеет границу. Запись «при ухудшении откатить» неполна, пока не названы владелец, изменение, разрешённый способ возврата и сигнал успешного восстановления. Эта статья не даёт команды rollback для чужого кластера: команда зависит от вашего deployment-процесса, прав и политики изменений. Если такой путь не описан, это причина остановить change, а не повод скрыть риск.

\n

Порядок воспроизводимой проверки

\n
  1. Зафиксируйте scope. Назовите workload, окружение, маршрут, версию, число реплик, период и владельца.
  2. Запишите текущую точку. Сохраните request, limit, quota, p50/p95, ошибки, очередь, peak usage и источник каждого значения.
  3. Разделите policy и runtime. Отдельно подтвердите admission, LimitRange и ResourceQuota; отдельно проверьте placement, throttling, memory pressure и saturation.
  4. Назовите billing unit. Зафиксируйте SKU, usage unit, base unit, tier, валюту, регион, скидку и период. Неизвестное поле оставьте неизвестным.
  5. Выберите соседний шаг. Измените один основной управляемый параметр и сохраните workload, окно, версию и способ измерения.
  6. Запустите расчёт. Выполните учебную команду или свой эквивалент, проверьте единицы и пересчитайте marginal cost только для сопоставимых входов.
  7. Проверьте stop condition. Сверьте headroom, очередь, ошибки и согласованность billing. При срабатывании остановитесь и оформите следующий вопрос.
  8. Зафиксируйте outcome. Запишите compare, stop или human review, владельца и границу возврата. Не называйте учебный результат командой для production.
  9. Попросите повторить расчёт. Другой инженер должен восстановить входы, формулу, отрицательную ветку и причину выбора без устного контекста.
\n

Ограничения применимости

\n

Все значения в примере — учебные: 0,36, 0,08, 0,01, p95 248/296/208 мс, 31%, 1,2/1,4/1,6/4 CPU и 2/6 GiB. Они не описывают конкретный provider, cluster, invoice, telemetry или production workload. Их можно использовать, чтобы проверить форму расчёта и смысл stop condition, но нельзя переносить их в бюджет, SLO или capacity plan.

\n

Модель не включает автоматически autoscaling, burst, cold start, noisy neighbor, storage, network egress, commitments, скидки, налоги, стоимость лицензий и сопровождения. Они могут изменить решение. Если хотя бы один фактор существенен, добавьте его в ту же billing-модель или остановите сравнение до получения данных.

\n

Источники Kubernetes подтверждают семантику ресурсов и policy, а источник Cloud Billing — форму описания SKU и тарифных ступеней. Ни один источник не знает вашу нагрузку, не обещает нужный p95 и не доказывает экономию. Доказательство для своего решения появляется только после выравнивания scope, измерения и финансового источника.

\n

Критерий готовности

\n

Решение о следующем пределе готово, когда одна карточка связывает workload, allocation, performance signal, billing unit, stop condition, owner и return boundary. В ней видно, какой параметр изменился, за какой период проведено сравнение и какой сигнал остановит эксперимент. Policy не выдана за runtime, usage не выдан за цену, а лучший p95 не выдан за универсальное решение.

\n

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

\n

Проверяемые источники

\n" }