Окружения: локальная разработка, превью, стенды и их цена
В понедельник в 11:40 в общем чате появляется сообщение: «стейджинг лежит, кто катал?» К 12:15 выясняется, что накатили миграцию из ветки, которая ещё не прошла ревью, и она сломала схему для трёх других сервисов. К 14:00 схему откатили руками, потеряв тестовые данные, которые команда биллинга собирала две недели. К 16:00 в чате очередь: четыре команды ждут стенд, чтобы «просто проверить перед релизом». Релизное окно в среду.
Это не история про плохих инженеров. Это история про общий ресурс без владельца, без изоляции и без расписания — и она повторяется в каждой второй компании, где больше пяти команд. Стенд стоит около 2 500 USD в месяц, съедает несколько человеко-дней в месяц на починку и остаётся главным узким местом поставки. Формально он есть. Практически — это очередь. Параллельно идёт вторая история, менее заметная: новый инженер на третий день всё ещё не может поднять сервис у себя, потому что docker compose up падает на четвёртом контейнере, а README устарел на два рефакторинга. Инцидентом это не считает никто, хотя по деньгам оно дороже упавшего стенда.
Эта глава — про то, что окружения не «инфраструктурная мелочь, которую один раз настроили», а продуктовая поверхность платформы со своими пользователями, метриками принятия и очень конкретным счётом. Разберём: какой сигнал покупает каждый тип окружения, почему «сделать стенд как прод» — заведомо невыполнимое обещание, сколько всё это стоит на самом деле и почему превью-окружения — одна из немногих платформенных функций, которую команды принимают добровольно. Продуктовая рамка — «Платформа как продукт»; про право платформы прятать сложность — глава об абстракциях.
Окружение — это не копия прода, а вопрос, который вы ему задаёте
Инженеры обычно описывают окружения по железу: «стейджинг — это кластер поменьше». Для проектирования это бесполезно. Полезное определение другое:
Окружение — это механизм получения ответа на конкретный вопрос о вашем изменении раньше, чем на него ответят пользователи. У механизма три характеристики: какие вопросы он закрывает, за какое время отвечает и сколько стоит его содержание.
Отсюда сразу следует главное правило: окружение проектируется от вопроса, а не от похожести. «Копия прода» — это не вопрос. «Не сломает ли моя миграция чтение из соседнего сервиса» — вопрос, и под него нужны совсем не те ресурсы, что под «выдержит ли сервис 8 000 rps».
| Класс дефекта | Где ловится дешевле всего | Задержка сигнала | Что нужно от окружения |
|---|---|---|---|
| Синтаксис, типы, очевидная логика | Редактор, компилятор, юнит-тесты | 1–30 с | ничего, кроме кода |
| Логика модуля, граничные случаи | Юнит-тесты локально | секунды | ничего |
| Работа с БД, SQL, миграции | Локальный контур с настоящей СУБД | 10–60 с | реальный движок, крошечные данные |
| Контракт между сервисами | Контрактные тесты, затем превью | минуты | схемы, а не живые соседи |
| Сборка образа, манифесты, конфиг | Превью на PR | 5–15 мин | реальный деплой-путь |
| Права, секреты, сетевые политики | Превью или стенд с прод-подобным IAM | 10–40 мин | настоящая модель доступа |
| Совместимость с реальными данными | Стенд с обезличенным срезом прода | часы | данные, а не железо |
| Деградация под нагрузкой | Нагрузочный контур | часы | масштаб и профиль трафика |
| Поведение на реальном трафике | Прод: флаги, канарейка, теневой трафик | минуты после выката | наблюдаемость и быстрый откат |
| Отказы зон, узлов, сети | Прод плюс учения | — | реальная топология |
Обратите внимание на две вещи. Верхняя половина таблицы почти ничего не стоит, нижняя стоит почти всё. И последние три строки в принципе не закрываются никаким стендом — их закрывают в проде, и это не признак незрелости, а свойство физики; подробно об этом в SRE-главе про безопасность релизов.
Кривая насыщается — это ключевой экономический факт главы. Первые 50 % классов дефектов покупаются почти бесплатно, следующие 30 % стоят как один стенд, оставшиеся 20 % не покупаются стендами ни за какие деньги. Компании, которые этого не видят, годами вкладываются в «ещё более похожий стейджинг», получая единицы процентов точности за десятки тысяч долларов.
Читается это так. Локальный контур и тесты — единственное место, где сигнал приходит за секунды, и вложения туда умножаются на число запусков в день (десятки), а не на число релизов (единицы). Превью на PR — середина по всем осям и лучший кандидат в золотой путь. Общий стенд — самый дорогой по совокупности: деньги плюс очередь плюс регулярные поломки. Долгоживущий QA-стенд и демо-стенд обычно наследие: живут потому, что кто-то однажды попросил, а перепроверять никто не стал.
Похожесть на прод: девять осей вместо одной
Фраза «стенд должен быть как прод» звучит разумно ровно до попытки её выполнить. Двенадцатифакторное приложение формулирует принцип dev/prod parity — держите окружения максимально близкими. Принцип верный, но умалчивает, что близость не скаляр, а вектор, и компоненты вектора стоят по-разному.
Практический вывод: платформа обязана явно объявить, по каким осям её окружения дают гарантии, а по каким нет. Это не признание слабости, а контракт:
Превью гарантирует: тот же образ (тот же digest, не «пересобранный»)
ту же схему БД после ваших миграций
тот же чарт и манифесты, только с другими значениями
те же сетевые политики между вашими сервисами
Превью НЕ гарантирует: объём и распределение данных (5 000 строк вместо 40 млн)
реальные внешние интеграции (платёжка — sandbox, СМС — заглушка)
производительность (общий пул узлов, соседи шумят)
права в облаке (роль превью шире прод-роли ради отладки)
Такой список экономит десятки часов споров «у меня на стенде работало» и превращает требование «дайте более похожий стенд» в разговор про конкретную ось и её цену. Ось «объём и форма данных» стоит выделить особо: на 5 000 строк планировщик Postgres выбирает seq scan и всё летает, на 40 млн тот же запрос без индекса кладёт базу. Это самый частый способ уронить прод изменением, которое «прошло все проверки» (механика — в главе про индексы и планы запросов), и никакое количество копий стенда эту ось не закрывает, если данные в них игрушечные.
Локальная разработка: самое дешёвое окружение, о котором забывают
Локальный контур — единственное окружение, куда инженер заходит десятки раз в день. Экономия здесь умножается на самый большой множитель, а вкладываются в него в последнюю очередь, потому что «это же просто ноутбук». Сценарий деградации всегда один:
Дефолтный путь ведёт к смерти локального контура: инженеры перестают проверять что-либо у себя, нагрузка переезжает на общий стенд, очередь растёт, цикл обратной связи вырастает с секунд до десятков минут. С точки зрения системного мышления это накопление задержки в контуре обратной связи — и именно оно, а не «медленный CI», обычно объясняет, почему поставка тормозит.
| Стратегия | Что даёт | Чем платите | Когда выбирать |
|---|---|---|---|
docker compose со всеми зависимостями |
полная автономность, работает в самолёте | RAM, время старта, вечно устаревающий файл | до 5–6 сервисов |
| Testcontainers: реальные БД и брокеры из тестов | настоящий Postgres/Kafka, изоляция на тест | старт контейнеров, Docker обязателен | везде, где есть интеграционные тесты |
| Контракты и сгенерированные заглушки | цикл в секунды, соседи не нужны | контракты надо поддерживать и проверять в CI | много сервисов, стабильные API |
| Гибрид: свой процесс локально, соседи в общем контуре | реальные соседи без их поднятия | нужен туннель и живой dev-кластер | сложные графы зависимостей |
| Удалённое рабочее окружение (Codespaces, Coder, Gitpod) | одинаковая среда у всех, мощное железо | оплата по часам, зависимость от сети | тяжёлые сборки, слабые ноутбуки |
Гибридную схему часто недооценивают: приложение запускается процессом на ноутбуке, но его сетевой трафик, DNS и переменные окружения подменяются так, будто оно живёт в кластере — этим занимаются Telepresence и mirrord. Цикл правки падает до секунд, соседи настоящие. Цена честная: нужен постоянно живой dev-кластер (деньги), агент на машине каждого (поддержка) и режим, в котором инженер случайно перехватывает трафик общего контура и ломает работу коллегам. Платформа обязана по умолчанию включать копирование трафика, а не подмену, иначе первая же неделя даст инцидент и минус к доверию.
Единственная метрика локального контура, которая всё объясняет, — время от git clone на чистой машине до первого зелёного теста. Меряется ровно так: берётся новый человек или чистая виртуалка, включается секундомер. Не оценка «ну часа два», а замер. Типичное значение там, где это никогда не мерили, — от четырёх часов до трёх дней; после одной итерации работы — 20–40 минут. Инструменты сюда играют разные, и у каждого своя цена владения: devcontainers требуют Docker и приличного железа, Nix-подход (devenv) даёт идеальную воспроизводимость и высокий порог входа, mise проще, но слабее в изоляции, Testcontainers закрывают зависимости в тестах и стоят секунд старта.
# Единый интерфейс во всех репозиториях компании. Реализация внутри разная,
# интерфейс один — это и есть золотой путь для локальной разработки.
make setup # поставить тулчейн нужных версий, поднять зависимости, накатить миграции
make test # прогнать те же тесты, что прогонит CI, теми же версиями
make run # поднять сервис локально с заглушками соседей
# Раз в неделю в CI на чистом образе: «а работает ли ещё make setup на пустой машине».
# README ломается молча; эта проверка ловит поломку до того, как о неё споткнётся новичок.
Превью-окружения: то, что команды принимают добровольно
Превью-окружение — временный изолированный экземпляр приложения, поднимаемый на каждый pull request и умирающий вместе с ним. Идея не новая: Heroku Review Apps появились в 2015 году, превью-деплои Vercel сделали её нормой для фронтенда. Наивная польза — «можно потыкать до мержа». Настоящих эффектов четыре, и они сильнее: ревью по работающей ссылке вместо диффа (продакт, дизайнер и тестировщик перестают ждать мержа); проверка деплой-пути на каждом изменении, отчего класс дефектов «в main всё сломалось при выкате» почти исчезает; снятие нагрузки с общего стенда; изоляция миграций, из-за которой история из начала главы структурно невозможна.
В экосистеме Kubernetes каноничная реализация — генератор pull request у Argo CD ApplicationSet:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: preview-orders, namespace: argocd }
spec:
generators:
- pullRequest:
github: { owner: acme, repo: orders-service }
# Превью по метке, а не на каждый PR подряд: экономит деньги и не поднимает
# окружение для PR вида «поправил опечатку в README».
labels: ["preview"]
requeueAfterSeconds: 60
template:
metadata:
name: 'orders-pr-{{number}}'
annotations:
# TTL: уборщик снесёт окружение, даже если PR забыли закрыть.
platform.acme.io/ttl: "24h"
platform.acme.io/owner: "{{author}}"
spec:
project: previews
source:
repoURL: https://github.com/acme/orders-service.git
targetRevision: '{{head_sha}}' # ровно тот код, что в PR
path: deploy/chart
helm:
valueFiles: [values-preview.yaml]
parameters:
- { name: image.tag, value: '{{head_sha}}' }
- { name: database.branch, value: 'pr-{{number}}' }
destination: { server: https://kubernetes.default.svc, namespace: 'preview-{{number}}' }
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions: [CreateNamespace=true]
Две детали отличают работающую реализацию от демонстрационной: превью по метке (иначе счёт за облако удваивается на правках документации) и TTL с владельцем (иначе через полгода у вас 300 живых превью от закрытых веток — самая частая утечка денег в этой области).
Состояние failed — то место, где платформы теряют пользователей. Превью, которое падает в 15 % случаев и не объясняет причину, хуже отсутствия превью: человек потратил время, не получил результата и в следующий раз пойдёт привычным путём. Правило простое: у неудачного создания должна быть ссылка на логи, понятная причина и одна кнопка повтора — сервис без внятной диагностики ошибок не является самообслуживанием, о чём подробно в соответствующей главе.
Поднять под легко, дать ему осмысленные данные — трудно. Здесь четыре стратегии:
| Стратегия | Скорость | Реализм | Риск утечки ПДн | Когда применять |
|---|---|---|---|---|
| Пустая БД плюс миграции | секунды | нулевой | нет | проверка деплой-пути и схемы |
| Фикстуры или seed-набор в репозитории | секунды | низкий, но управляемый | нет | основной вариант для большинства |
| Обезличенный срез прода | минуты | высокий | есть, требует процесса | сложные доменные сценарии |
| Ветвление БД (copy-on-write) | секунды | высокий | есть | если провайдер это умеет |
Ветвление базы — сильный приём: Neon и подобные сервисы делают копию за секунды по принципу copy-on-write; в self-hosted Postgres похожий эффект дают шаблонные базы (CREATE DATABASE x TEMPLATE seed_db) и снапшоты ZFS/LVM. С обезличенными срезами есть жёсткое ограничение, которое платформа обязана держать за команды: персональные данные в превью — это инцидент безопасности, а не удобство. Псевдонимизация должна быть необратимой и проверяемой, доступ — журналируемым; детали в главах про секреты и про приватность, а в этом треке — в главе о безопасности по умолчанию.
Не всё складывается в модель «эфемерная копия на PR», и делать вид, что складывается, не надо:
- Платные интеграции без песочницы. Нет sandbox у платёжного провайдера — превью либо ходит в боевой контур (нельзя), либо в заглушку, и часть сигнала теряется. Скажите об этом прямо.
- Долгие миграции. Миграция на 40 минут превращает превью в бессмыслицу: нужен либо срез поменьше, либо отдельный путь проверки миграций.
- Квоты облака. 60 одновременных превью — это 60 балансировщиков, 60 адресов, 60 инстансов БД; лимиты аккаунта выясняются в самый неудачный момент.
- Мобильные и десктопные клиенты. Нужен стабильный URL на недели; TTL в сутки ломает процесс. Здесь нужен не эфемерный, а арендуемый стенд.
- Большие данные и ML. Превью с 0,1 % данных не отвечает ни на один интересный вопрос дата-команды.
Общий стенд: ресурс с конкуренцией, а не окружение
Общий стенд (staging, integration, QA — названия разные, суть одна) ломает больше всего процессов, потому что к нему применяют интуицию «окружение», хотя правильная интуиция — «общий ресурс с конкуренцией». Дальше работает всё, что известно про общие ресурсы: без владельца деградирует, без изоляции конфликтует, без учёта использования не поддаётся ни сокращению, ни защите. Это ровно та трагедия общего ресурса, которую системное мышление описывает как архетип.
Что с ним делать?"] --> B{"На какой вопрос он отвечает
такого, чего не даёт превью?"} B -->|"ни на какой,
это привычка"| C["Убить. Перед этим —
месяц метрик
реального использования"] B -->|"интеграция с внешними
системами, которых нет в превью"| D["Оставить, но сузить:
это интеграционный контур,
а не «место, где всё проверяют»"] B -->|"нужен объём данных
и прод-подобные права"| E["Стенд данных:
обезличенный срез,
журналируемый доступ"] B -->|"нужна нагрузка"| F["Отдельный нагрузочный контур,
поднимаемый под задачу
и гасимый после"] D --> G{"Конфликты между
командами есть?"} E --> G G -->|"да, постоянно"| H["Нарезать на аренды: слот
с владельцем и сроком в календаре;
кто занял — тот и чинит"] G -->|"нет"| I["Оставить, но назначить
владельца и SLO доступности"] C --> K["Через квартал проверить:
вернулась ли боль? Если да —
вернуть в узком виде"]
Вариант «убить» встречается реже, чем должен. Перед решением полезно месяц собирать данные: кто и когда реально катал на стенд, сколько времени он был сломан, сколько уникальных дефектов поймал такого, чего не поймали превью и канарейка. В половине случаев выясняется, что стенд ловит ноль уникальных дефектов и существует как ритуал.
Даже живой стенд неизбежно расходится с продом. Механика дрейфа одинакова: ручные правки «на пять минут, потом заведу в код», отставание версий, накопленный мусор в данных, расширенные права, выданные для отладки инцидента полгода назад, отключённые «шумные» алерты. Лечение одно — регулярное пересоздание с нуля из кода. Стенд, который не пересоздавался год, не является ни копией прода, ни воспроизводимым артефактом: это снежинка-сервер, про который никто не знает всей правды. Практическое правило: если стенд нельзя пересоздать из репозитория за разумное время, у вас не стенд, а технический долг с ежемесячным счётом (механика — в главах про Terraform и GitOps).
Часть точности покупается только в проде
Три нижние оси похожести — объём данных, профиль нагрузки, реальные отказы — недостижимы на стендах в принципе. Значит, сигнал по ним берут в проде безопасными способами: флаги функциональности (код в проде, поведение выключено, включается на 1 % пользователей), канареечный выкат (малая доля трафика, автоматическое сравнение метрик), теневой трафик (копия боевых запросов идёт в новую версию, ответы отбрасываются — осторожно с побочными эффектами: теневой запрос не должен списывать деньги) и синтетические проверки (постоянный поток искусственных сценариев по проду).
Это не «тестирование вместо стендов», а признание того, что часть вопросов имеет ответ только там, где живут пользователи. Чарити Мейджорс в тексте «I test in production» формулирует резко: вы всё равно тестируете в проде — вопрос лишь в том, делаете вы это осознанно и с приборами или случайно и вслепую. Платформенная ответственность здесь прямая: флаги, канарейка и наблюдаемость должны быть платформенным сервисом, а не тем, что каждая команда изобретает заново — об этом глава о наблюдаемости как платформе.
Цена окружений: считаем вслух
Платформенная команда не пишет продукт, значит должна окупаться сэкономленным временем других. На окружениях это проверяется лучше всего, потому что здесь есть и прямой счёт от облака, и измеримая экономия. Общая стоимость раскладывается на четыре слагаемых, и только первое видно в биллинге: инфраструктура (узлы, БД, балансировщики, трафик); часы платформы на поддержку и починку; часы пользователей на ожидание и разбирательства «почему у меня не поднялось»; упущенное — изменения, не доехавшие вовремя из-за очереди. Третье и четвёртое почти всегда больше первого и почти никогда не считаются, поэтому разговор про окружения сводится к «дорого, давайте урежем», а урезают то, что дешевле всего сократить в биллинге и дороже всего обходится людям.
"""Счёт по окружениям: деньги облака плюс часы людей.
Модель намеренно грубая — цель не точность, а масштаб слагаемых."""
from dataclasses import dataclass
HOUR_RATE_USD = 45.0 # полная стоимость часа инженера для компании
DEVS = 100 # 20 продуктовых команд по 5 человек
@dataclass
class Env:
name: str
cloud_usd: float # прямой счёт облака за месяц
platform_hours: float # часы платформы на поддержку
user_hours: float # часы пользователей: ожидание, разборы
def total_usd(self) -> float:
return self.cloud_usd + (self.platform_hours + self.user_hours) * HOUR_RATE_USD
envs = [
Env("общий стенд", 2500, 4, 40), # 12 узлов и БД; очередь: 20 команд × 2 ч
Env("превью на PR", 820, 6, 8), # ~55 живых одновременно, со сном и TTL
Env("локальный контур", 0, 2, DEVS * (20 / 60) * 21), # 20 мин/день на «поднять и починить»
Env("демо-стенд", 640, 1, 0), # живёт год, включается трижды в квартал
]
for e in envs:
people = e.total_usd() - e.cloud_usd
print(f"{e.name:22} {e.total_usd():>9,.0f} USD/мес (облако {e.cloud_usd:,.0f}, люди {people:,.0f})")
saved = DEVS * (14 / 60) * 21 # если довести 20 мин/день до 6 мин/день
print(f"Экономия от работы над локалкой: {saved:,.0f} ч/мес = {saved * HOUR_RATE_USD:,.0f} USD/мес")
print(f"Это оплачивает {saved / 160:.1f} инженера платформы, занятого только этим.")
общий стенд 4,480 USD/мес (облако 2,500, люди 1,980)
превью на PR 1,450 USD/мес (облако 820, люди 630)
локальный контур 31,590 USD/мес (облако 0, люди 31,590)
демо-стенд 685 USD/мес (облако 640, люди 45)
Экономия от работы над локалкой: 490 ч/мес = 22,050 USD/мес
Это оплачивает 3.1 инженера платформы, занятого только этим.
Числа условны, соотношение устойчиво: самая дорогая статья — та, которой нет в биллинге. Компания, режущая превью ради экономии 800 USD, ежемесячно теряет тридцать тысяч на локальном контуре, о которых никто не докладывает финансистам. Это ровно та локальная оптимизация, где улучшается видимая часть и ухудшается целое. Здесь же проходит граница между платформой и налогом: если команда из трёх человек тратит 450 часов в месяц на окружения, а совокупная экономия пользователей меньше 450 часов, платформа не окупается, сколько бы функций в ней ни было. Точку безубыточности считают вслух и пересчитывают раз в квартал — подробнее в главе о стоимости и в devops-главе про стоимость облака.
| Рычаг снижения счёта | Типичная экономия | Побочный эффект |
|---|---|---|
| TTL и автоудаление осиротевших окружений | 30–50 % облачного счёта | нужен внятный способ продлить аренду |
| Сон при простое, реплики в ноль | 20–40 % | холодный старт 10–30 с |
| Выключение стендов на ночь и выходные | до 65 % от их счёта | ломает работу распределённых команд |
| Превью по метке, а не на каждый PR | 30–50 % от счёта превью | лишнее действие для инженера |
| Общая БД с изоляцией по схеме вместо инстанса на PR | 40–60 % от счёта БД | слабее изоляция, сложнее чистка |
| Прерываемые (spot) узлы под превью | 50–70 % от счёта узлов | превью иногда умирает само |
| Виртуальные кластеры вместо отдельных | 60–80 % | ещё один слой в отладке |
Порядок важен: первые два рычага почти не ухудшают опыт, последние ухудшают заметно. Начинать надо сверху и останавливаться там, где цена в удобстве перестаёт окупаться. Но применять рычаги не к чему, пока нет инвентаря окружений с владельцем и стоимостью:
Три поля делают всю работу: expires_at (без него окружения копятся вечно), владелец через TEAM (без него некому написать) и contains_pii (без него срез прода однажды окажется в чьём-то ноутбуке). Остальное — удобства. Полезный регулярный отчёт: список окружений старше 30 дней без активности, с владельцем и месячной стоимостью, разосланный владельцам. Не приказ удалить — просто счёт; после первой же рассылки исчезает 20–40 % окружений, потому что половина была забыта.
Окружения как платформенный продукт
Всё сказанное становится платформенной работой при одном условии: у окружений есть владелец, обещание и метрики. Иначе это набор скриптов, который каждый обходит по-своему. Обещание оформляется как SLO — механика та же, что в SRE-главе про SLI и SLO, только пользователи здесь инженеры.
| Индикатор | Разумная цель | Почему именно он |
|---|---|---|
| Доля успешных созданий превью | ≥ 98 % | одно падение из десяти отправляет человека в обход навсегда |
| p50 / p90 времени от push до готовой ссылки | 6 / 12 мин | за 20 минут внимание переключается, превью перестают использовать |
| Доступность общего стенда в рабочее время | ≥ 97 % | «стейджинг лежит» — это остановка нескольких команд |
| Доля превью, корректно удалённых по TTL | ≥ 99 % | утечка денег и квот |
Время от git clone до зелёного теста |
< 30 мин | стоимость онбординга и переключения между репозиториями |
SLO работает только вместе с публичной панелью и разбором нарушений: платформа, которая обещает 98 % и не показывает факт, не обещает ничего. Но SLO говорит о качестве сервиса, а не о том, нужен ли он. На это отвечают метрики принятия — они про поведение, а не про полноту функций:
- доля PR с живым превью — главная: если 30 %, вы построили функцию, а не путь;
- доля выкатов в общий контур мимо платформы — прямой индикатор обхода;
- число самодельных
docker-compose.yamlи скриптов подъёма окружений в продуктовых репозиториях — считается поиском по организации за пять минут и говорит больше любого опроса; - доля репозиториев, где
make testпроходит на чистой машине — измеряется ночным прогоном; - доля времени, когда общий стенд занят одной командой — показывает, нужна ли нарезка на аренды.
Метрику обхода стоит поднять до статуса главной: любой самописный скрипт подъёма окружения — это голос против платформы. Не повод запрещать, а повод спросить, чего не хватило (как читать такие сигналы — в главе о принятии и в product-management про метрики).
Три способа провалить окружения
Обёртка над облаком, которая только мешает. Платформа делает platform env create поверх Terraform. Команда получает 40 минут ожидания, отсутствие логов при падении, невозможность посмотреть, что именно создалось, и запрет ходить в облако напрямую («у нас же платформа»). Через месяц у половины команд лежит собственный каталог terraform/ с прямым доступом. Диагноз: обёртка забрала возможности, не дав взамен ни скорости, ни диагностики. Абстракция обязана быть глубже того, что скрывает, иначе это чистая цена — см. главу об абстракциях.
Портал с кнопкой «создать окружение», которым никто не пользуется. Красивая форма на десять полей и четыре клика проигрывают git push вчистую, потому что превью должно рождаться из действия, которое инженер уже совершает. Портал полезен как витрина — посмотреть, что у меня живёт, сколько стоит, продлить аренду, — а не как вход. Портал в роли единственного входа даёт почти гарантированный ноль принятия.
Одно «окружение разработчика» поверх трёх разных потребностей. Фронтендерам нужен быстрый старт с заглушками и hot reload, бэкенду — настоящая БД и прогон миграций, дата-команде — объём данных и GPU. Платформа делает единый dev-env с объединением всех полей: 38 параметров, старт 12 минут, не подходит никому. Правильный ход — общий примитив (аренда изолированного пространства с владельцем и TTL) и три тонких профиля поверх. Общая реализация дешевле общего интерфейса.
Золотой путь по окружениям — и те, кому он не подошёл
Золотой путь здесь звучит так: «положи preview: true в контракт сервиса — получишь превью на каждый PR, ссылку в комментарии, TTL 24 часа и seed-данные; make setup поднимет то же самое локально». Пока это путь, он экономит всем время. Забором он становится в момент, когда рядом появляется формулировка «и никак иначе». Команды, которым стандартное превью не подходит, есть всегда:
| Команда | Почему не подходит | Что даёт платформа вместо |
|---|---|---|
| Мобильная | нужен стабильный URL на недели, TTL мешает | долгоживущая аренда с явным владельцем и ежемесячным счётом |
| Данные и ML | нужен объём и GPU, превью бессмысленно | пул GPU по расписанию, доступ к обезличенному срезу |
| Встраиваемые системы | нужны физические устройства, эфемерность невозможна | бронирование стенда с железом: слот, календарь, автовозврат |
| Работа с внешним монолитом | у поставщика один тестовый контур на всех | очередь с явным владельцем слота вместо «кто первый написал в чат» |
| Регуляторный сегмент | данные нельзя копировать вообще | изолированный контур с синтетикой и журналируемым доступом |
Общий принцип: не заставлять их идти золотым путём и не выталкивать за пределы платформы. Абстракция другая, примитивы те же — аренда, владелец, TTL, учёт стоимости, единый способ получить доступ. Тогда исключение остаётся внутри системы: видно в инвентаре, попадает в отчёт по деньгам, не превращается в теневую инфраструктуру. Механика простая: у каждого исключения есть заявка с причиной, сроком пересмотра и владельцем, а через квартал список исключений читается как бэклог платформы. Три команды с одинаковым исключением означают, что золотой путь надо расширить; одна уникальная команда так и останется исключением, и это нормально. Границы ответственности между платформой и продуктовыми командами — тема главы про топологии команд.
Цена владения инструментами: без рекламы
Инструменты в этой области хорошо продаются, поэтому называть надо их цену, а не свойства.
- Kubernetes для превью. Даёт дешёвую изоляцию по namespace, квоты, готовые контроллеры уборки. Цена: кластер надо обновлять, отлаживать сетевые политики и объяснять инженерам, почему под в
CrashLoopBackOff. Если у вас пять сервисов и нет кластера — превью на compose или на PaaS дешевле на порядок (devops-глава про Kubernetes). - Виртуальные кластеры (vcluster). Сильная изоляция control plane при общем железе. Цена: ещё один слой в диагностике, нетривиальные проблемы с CRD и вебхуками, зависимость от проекта, который развивается быстрее вашей готовности обновляться.
- Backstage как витрина окружений. Даёт каталог и единое окно. Цена: это полноценное Node-приложение, которое кто-то должен разрабатывать и обновлять, обычно 0,5–1 инженера постоянно. Для компании из шести команд неоправданно: страница в вики и CLI закроют ту же потребность.
- Облачные среды разработки. Убирают «у меня не собирается» и ускоряют онбординг. Цена: почасовая оплата, зависимость от сети, невозможность работать в поезде, а инженеры с мощными ноутбуками воспринимают переход как понижение.
- Ветвление БД у облачного провайдера. Отлично решает данные для превью. Цена: привязка к провайдеру и открытый вопрос, что делать с self-hosted частью.
Choose Boring Technology применимо к окружениям сильнее, чем к проду: они ломаются чаще, чинить их некому, а каждый сбой бьёт по доверию к платформе целиком.
Типичные ошибки
- Проектировать окружение от похожести, а не от вопроса. Даёт бесконечное «давайте приблизим стенд к проду» без критерия завершения.
- Считать только счёт от облака. Часы ожидания и разбирательств больше денег в разы, но их никто не суммирует.
- Превью без TTL и владельца. Через полгода — сотни живых окружений от закрытых веток и внезапный разговор с финансами.
- Молчаливые падения при создании. 10 % неудач без объяснений хватает, чтобы половина команд вернулась к своим скриптам.
- Портал как единственный вход. Превью должно рождаться из
git push, а не из формы. - Общий стенд без владельца и расписания. Гарантированная очередь, взаимные поломки и «кто катал?» в чате.
- Игрушечные данные при отсутствии нагрузочного контура. Дефект «на объёме всё легло» уходит прямиком в прод.
- Персональные данные в превью. Удобно ровно до первого разбора с юристами.
- Стенд, который нельзя пересоздать. Через год это уникальный сервер, а не окружение.
- Единый
dev-envповерх трёх разных потребностей. Объединение полей, старт 12 минут, ноль пользователей. - Отрицание того, что часть проверок возможна только в проде. Ведёт к вечным вложениям в стенды вместо флагов, канареек и наблюдаемости.
- Ноль инвестиций в локальный контур. Самая дорогая и самая невидимая статья расходов из всех.
Мини-итог
- Окружение — механизм ответа на конкретный вопрос раньше пользователей. Проектируйте от вопроса, а не от похожести на прод.
- Точность растёт по насыщающейся кривой: последние проценты стоят кратно дороже первых и стендами не покупаются вообще.
- «Как прод» — это девять осей, а не одна. Объявите публично, по каким осям у вас есть гарантии, а по каким нет.
- Локальный контур — самое дешёвое окружение с самым большим множителем использования. Мерьте время от
git cloneдо зелёного теста. - Превью на PR — сильнейший кандидат в золотой путь. Обязательны TTL, владелец и внятная диагностика падений.
- Общий стенд — не окружение, а ресурс с конкуренцией. Либо владелец и SLO, либо нарезка на аренды, либо честное закрытие.
- Считайте все четыре слагаемых: облако, часы платформы, часы пользователей, упущенное время. Третье обычно больше первого.
- У окружений есть SLO и метрики принятия. Самодельный скрипт подъёма окружения в продуктовом репозитории — голос против вашей платформы.
Источники
- The Twelve-Factor App: Dev/prod parity — исходная формулировка принципа близости окружений.
- J. Humble, D. Farley. Continuous Delivery — deployment pipeline и повторяемость выката.
- M. Fowler. QA in Production и Eradicating Non-Determinism in Tests.
- C. Majors. I test in production, Increment, 2019.
- Google Testing Blog. Test Sizes — классификация по ресурсам, а не по слоям.
- DORA Research — связь длины цикла обратной связи с результатами поставки.
- Argo CD. ApplicationSet Pull Request Generator.
- Heroku. Review Apps; Vercel. Deployment Environments.
- Testcontainers, Dev Containers Specification, devenv, mise.
- Telepresence, mirrord, vcluster, kind, KEDA.
- PostgreSQL. Template Databases; Neon. Branching.
- D. McKinley. Choose Boring Technology, 2015. CNCF TAG App Delivery. Platforms White Paper.
Что дальше
Мы разобрали окружения как продуктовую поверхность: от чего они защищают, чего не могут в принципе, сколько стоят в деньгах и часах и почему их обходят. Но почти каждое окружение из этой главы рождается одним механизмом — сборкой и выкатом. Превью появляется из пайплайна, стенд обновляется пайплайном, локальный make test обязан повторять то, что делает CI. Следующий вопрос неизбежен: конвейер у каждой команды свой или общий платформенный сервис, и где проходит граница между «мы дали шаблон» и «мы забрали контроль».
Конвейер как платформенный сервис: общее против «у каждого своё»