- 13 конвенций по осям arch / lang / stack / common; репозитории берут оттуда копии в свой docs/conventions/ и коммитят их у себя - conv — синхронизация копий: add / status / diff / pull / push, локальные регионы исключены из сравнения, поэтому расхождение не даёт шума
8.2 KiB
status
| status |
|---|
| рекомендуемая |
Конфигурация приложения
Как устроена конфигурация: где лежит, как попадает в процесс, что с секретами и когда падает.
Файл, а не окружение
Конфигурация — файл. Причины, по убыванию веса:
- Один типизированный источник. Файл несёт секции, комментарии, единицы измерения и валидируется целиком. Окружение — плоский набор нетипизированных строк, который приходится документировать отдельно; появление второго канала конфигурации гарантирует расхождение между ними.
- Окружение наследуется дочерними процессами. Всё, что приложение
запускает — конвертер,
git, шелл-хук, — по умолчанию получает копию секретов, хотя они ему не нужны. - В контейнере окружение расползается по лишним поверхностям.
docker inspectпоказывает его любому, у кого есть доступ к сокету докера; переменные оседают в compose-файле и.envна диске — то есть файл всё равно появляется, только без структуры и валидации.
Обратите внимание, чего в списке нет: /proc/<pid>/environ не является
аргументом — он имеет права 0400 и защищён проверкой PTRACE_MODE_READ,
то есть доступен ровно тому же кругу, что и файл под 0600.
Запрет держится на «один источник» и на том, что все приложения свои. Для стороннего образа, живущего на env, конвенция неприменима — это не повод отказываться от неё для своих.
Практика:
- Формат — текстовый, с комментариями и секциями (TOML, YAML — по стеку).
- Имя по умолчанию фиксировано и ищется в рабочей директории процесса; путь переопределяется опцией командной строки.
- Реальный конфиг не коммитится. В репозитории лежит образец.
Грузим один раз, дальше не перечитываем
- Разбор — один раз при старте, в одну типизированную структуру. Дальше по коду читаем только её: чтения файла в бизнес-коде нет.
- Конфиг неизменяем после старта; смена параметров — рестарт процесса. Горячая перезагрузка — отдельное решение с отдельным обоснованием, а не умолчание.
- Умолчания задаются в коде, файл их перекрывает. Образец при этом перечисляет все поля, включая те, у которых есть умолчание: поле, живущее только в коде, для читателя конфига не существует.
Образец самодокументируем
Образец коммитим как единый справочник по конфигу: все секции и все поля. Каждое поле снабжаем комментарием, из которого ясно:
- зачем поле — что оно меняет в поведении;
- диапазон или допустимые значения — перечисление либо границы;
- единицы измерения, если применимо: секунды/миллисекунды, байты, доля
0–1.
Так конфиг читается без открывания кода — этим он и полезен.
Поля по дискриминатору type
Когда набор полей секции зависит от поля-дискриминатора (выбор одного из бекендов или внешних сервисов), обязательность полей определяется его значением, а не фиксирована для секции.
- Валидация — по значению
type: для каждого поддерживаемого варианта свой набор обязательных полей; поля других вариантов не требуются. Неизвестное значение → ошибка на старте с перечислением поддерживаемых. - Образец — по значению
type: основной вариант предзаполнен рабочими значениями, альтернативные — блоками-комментариями ниже, каждый со своим описанием полей. Из примера видны все варианты, не открывая код.
Секреты приносит деплой
Секреты доставляет деплой, рендеря их прямо в конфиг. Отдельного слоя секретов в приложении нет — оно просто читает файл. Источник истины секрета — внешнее хранилище деплоя, не репозиторий и не окружение.
- Рендеренный конфиг не коммитится; права
0600, владелец — runtime- пользователь. - В образце секретные поля — пустые строки.
- Загрузчик на старте проверяет, что обязательные секреты не пусты: это ловит криво отрендеренный шаблон до того, как он превратится в 401 от внешнего API через час работы.
- В логи секреты не попадают.
Валидация и fail-fast
Конфиг валидируем на старте, до приёма трафика. Невалидный конфиг —
запись уровня ERROR и выход с ненулевым кодом: не стартуем «наполовину».
Проверяем как минимум:
- обязательные поля заданы, обязательные секреты не пусты;
- пути существуют и доступны на запись/чтение по назначению;
- числовые диапазоны и единицы (доли, таймауты, счётчики попыток);
- строки, которые парсятся во что-то (длительности, зоны, URL), реально парсятся;
- включённые секции консистентны: если интеграция включена — заданы все её обязательные поля.
Проблемы собираем и показываем разом, а не по одной за запуск.
Связано
arch/time.md— формат времени; зона отображения — единственный конфигурируемый параметр времени, семантика описана там.arch/app-directories.md— конфиг лежит в категории «конфигурация» и доступен приложению только на чтение.