db-identifiers и ansible/app-directories переписаны на формальный язык

- 7 и 9 правил соответственно, у каждого модальность и обоснование
- условие выбора первичного ключа стало правилом с таблицей веток, на
  которые можно ссылаться из отступлений и механизации
This commit is contained in:
av
2026-07-25 18:56:30 +03:00
parent 22c6855968
commit 7701a28df1
2 changed files with 203 additions and 98 deletions
+94 -40
View File
@@ -1,63 +1,117 @@
---
status: рекомендуемая
extends: arch/app-directories.md
---
# Категории директорий: реализация в Ansible
Как `arch/app-directories.md` раскладывается на сервере плейбуком.
Как категории из `arch/app-directories.md` раскладываются на сервере
плейбуком. Форма записи — `common/language.md`.
## Переменные и создание
## Область действия
- Директория объявляется переменной плейбука внутри `base_dir`, имя
переменной оканчивается на `_dir`. Для случая «одна директория на
категорию» это `config_dir`, `data_dir`, `cache_dir`; когда категория
состоит из нескольких, имя даётся по содержимому (`media_dir`,
`uploads_dir`, `dumps_dir`), а категория читается из списка бэкапа.
- Директории создаются **одной задачей циклом по списку**: список и есть
декларация того, что приложение пишет на диск. Разнесение по нескольким
задачам прячет эту декларацию.
- Владелец — пользователь, от имени которого работает приложение. Модель
выбирается на репозиторий: выделенный пользователь на приложение
(`app_owner_uid == app_owner_gid`) или общий `primary_user`. Какая модель
принята — фиксируется ниже.
Раскладка меняется вместе с миграцией данных, поэтому правила
распространяются на **новые приложения**; существующие переезжают по мере
касания, отдельной кампанией не переписываются.
## Правила
### R1. Каждая директория объявлена переменной `*_dir`
**ДОЛЖЕН.** Директория приложения объявляется переменной плейбука внутри
`base_dir`, имя оканчивается на `_dir`. Для случая «одна директория на
категорию» это `config_dir`, `data_dir`, `cache_dir`; когда категория
состоит из нескольких директорий, имя даётся по содержимому (`media_dir`,
`uploads_dir`, `dumps_dir`).
**Почему.** Переменная — единственная ссылка, которую разделяют задача
создания директории и список бэкапа (R4). Литерал пути в одном из этих мест
означает, что переименование директории молча разойдётся с бэкапом, и
обнаружится это при восстановлении.
### R2. Директории создаются одной задачей циклом по списку
**СЛЕДУЕТ.** Список директорий в единственной задаче создания.
**Почему.** Этот список — единственное место, где декларировано всё, что
приложение пишет на диск. Разнесённое по нескольким задачам создание
отвечает на вопрос «какие директории есть у приложения» только чтением
всего плейбука, а именно этот вопрос задают при заведении бэкапа и при
разборе места на диске.
### R3. Владелец директорий — пользователь, от имени которого работает приложение
**ДОЛЖЕН.** Конкретная модель — выделенный пользователь на приложение
(`app_owner_uid == app_owner_gid`) или общий `primary_user` — выбирается на
репозиторий и фиксируется ниже.
**Почему.** Правило про соответствие владельца рантайму, а не про
конкретную модель: приложение в контейнере пишет от определённого uid, и
если директория принадлежит другому, отказ произойдёт не при деплое, а при
первой записи — то есть после того, как плейбук отчитался об успехе. Выбор
же модели — свойство репозитория: сервер с одним пользователем и сервер с
изоляцией по приложениям решают разные задачи, и навязывать одну модель
обоим значит гарантировать вечное отступление.
<!-- local:модель-владельца -->
<!-- /local -->
## Список бэкапа
### R4. Список бэкапа собирается из тех же переменных
Плейбук кладёт в `base_dir` файл `backup-targets` — его читает оркестратор
бэкапов. Строки списка собираются из **тех же** переменных `*_dir`, что и
задача создания директорий: тогда переименование или перенос директории не
может разойтись с бэкапом.
**ДОЛЖЕН.** Плейбук кладёт в `base_dir` файл `backup-targets`, строки
которого ссылаются на переменные `*_dir` из R1, а не на литеральные пути.
В список идут директории категории «данные», включая директорию дампов, и
не идут конфигурация и кеш.
**Почему.** Правило вывода списка механическое (R5), но применяет его
человек или шаблон — то есть ошибиться можно. Общая переменная делает целый
класс ошибок невозможным: переименовал директорию — переименовалось в
обоих местах. Независимо набранный список расходится тихо и проявляется в
единственный момент, когда это уже неисправимо.
## Монтирование в контейнер
### R5. В список бэкапа идут только данные
- Конфигурация — `:ro`, где приложение это позволяет. Приложение, которое
переписывает свой конфиг, монтируется на запись — это отступление, и оно
записывается.
- Данные и кеш — на запись.
- `docker-compose.yml` остаётся в корне `base_dir`: туда смотрит
`project_src` модуля `docker_compose_v2`.
**ДОЛЖЕН.** Директории категории «данные», включая директорию дампов, — в
списке; конфигурация и кеш — нет.
## Секреты
**Почему.** Реализация правила базовой конвенции. Кеш раздувает снапшот без
пользы, а конфигурация содержит отрендеренные секреты — бэкап уезжает в
облако, и источником истины для секретов остаётся vault, а не снапшот.
Секреты приходят из vault-переменных и рендерятся шаблоном. Два способа, в
порядке предпочтения:
### R6. Конфигурация монтируется только на чтение
1. **В файл конфигурации** (роль `secrets`) — предпочтительный: секрет
лежит под `0600` у пользователя приложения, не наследуется дочерними
процессами и не виден в `docker inspect`.
2. **В `environment:` compose-файла** — когда приложение не умеет читать
секреты из файла. Задача рендера идёт с `no_log: true`.
**СЛЕДУЕТ.** В compose конфигурация подключается с `:ro`.
Второй способ — вынужденный: он кладёт секрет в метаданные контейнера и в
файл compose на диске. Приложение, умеющее файловые секреты, переводится на
первый способ при ближайшем касании.
**Почему.** Плейбук — источник истины для конфигурации, и `:ro` превращает
это из договорённости в свойство системы: приложение, которое втихую
переписывает свой конфиг, падает сразу, а не расходится с репозиторием
незаметно. Приложение, которому запись в конфиг нужна по устройству,
монтируется на запись — это отступление, и оно записывается.
### R7. `docker-compose.yml` лежит в корне `base_dir`
**ДОЛЖЕН.** Файл не переносится во вложенную директорию.
**Почему.** Туда смотрит `project_src` модуля `docker_compose_v2`. Правило
внешнее по происхождению, но нарушается легко — при попытке «навести
порядок» и убрать compose в `config/`, где ему по смыслу категорий было бы
место.
### R8. Секреты рендерятся в файл конфигурации
**СЛЕДУЕТ.** Значения приходят из vault-переменных и попадают в файл,
принадлежащий пользователю приложения.
**Почему.** Файл под `0600` не наследуется дочерними процессами, не виден в
`docker inspect` и не оседает в compose-файле на диске. Это те же три
довода, по которым базовая конвенция конфигурации выбирает файл вместо
окружения.
### R9. Когда приложение не умеет файловые секреты — `environment` под `no_log`
**ДОПУСКАЕТСЯ.** Задача рендера идёт с `no_log: true`.
**Почему.** Явное разрешение нужно, чтобы R8 не читался как запрет на
деплой такого приложения. Способ вынужденный: секрет попадает в метаданные
контейнера и в compose-файл на диске. Приложение, научившееся читать
секреты из файла, переводится на R8 при ближайшем касании.
<!-- local:отступления -->
<!-- /local -->