Tududi: add task tracke application
Linting / YAML Lint (push) Has been cancelled
Linting / Ansible Lint (push) Has been cancelled

This commit is contained in:
av
2026-07-04 16:56:01 +03:00
parent 8ec0327851
commit 5cd66d01bc
11 changed files with 846 additions and 162 deletions
+208
View File
@@ -0,0 +1,208 @@
# Общие Ansible-роли между серверными репозиториями
## Статус
Черновик. Подход обсуждён, реализация не начата.
## Контекст
Серверы управляются изолированными Ansible-репозиториями: по одному на
сервер (pet-project-server, buckland, далее — новый торрент-бокс).
Кастомные роли (`owner`, `eget`, `secrets`) концептуально общие, но
физически продублированы, и копии дрейфуют: `eget` разошёлся
косметически, `owner` в buckland превратился в мёртвый код, пока
актуальная версия развивалась в pet-project-server.
Нужен способ держать роли общими между репозиториями, при этом:
- правка роли применяется мгновенно — без петли commit → push →
install;
- каждый репозиторий остаётся самодостаточным: клонируется и
деплоится без внешних зависимостей, CI (yamllint + ansible-lint)
работает как сейчас;
- обновление роли на конкретном сервере — явное решение, а не
незаметный сайд-эффект.
## Осознанный выбор для pets-серверов
Это решение спроектировано под подход «pets, not cattle» и не
претендует на масштаб. Серверы — питомцы: у каждого своё железо, свой
набор приложений и свой темп жизни. Из этого следуют принципы,
противоположные командной инженерии:
- дублирование допустимо, принудительная консистентность — нет;
- каждый сервер принимает обновление общей роли явно, в свой момент,
через видимый дифф в собственной истории git;
- никакой инфраструктуры публикации (registry, версии, релизы) —
потребитель ролей один человек, который сам же их и пишет.
Если серверов станет много или появятся другие потребители — решение
нужно пересмотреть (см. «Путь миграции»).
## Решение
Вендоринг копий плюс локальная синхронизация через файловую систему.
Каноническая версия ролей живёт в отдельной директории
`~/projects/private/ansible-shared/`. Каждый серверный репозиторий
держит свою закоммиченную копию нужных ролей в `roles/` и
синхронизируется с каноном командами invoke (rsync, git не участвует).
```
~/projects/private/
ansible-shared/
roles/
owner/
eget/
secrets/
pet-project-server/
roles/owner/ # закоммиченная копия
roles/eget/
roles/secrets/
buckland/
roles/eget/ # подписан только на eget
<новый сервер>/
roles/...
```
Ключевое свойство: «пином версии» служит сама закоммиченная копия.
Репозиторий всегда деплоит ровно то, что лежит в его git; обновление
роли — это `roles-pull` + осознанный коммит с читаемым диффом.
### Подписка
Каждый репозиторий объявляет в `tasks.py` список ролей, которые он
синхронизирует:
```python
SYNCED_ROLES = ["owner", "eget", "secrets"]
```
Подписка явная и пер-серверная: buckland может не подписываться на
`owner`, пока живёт в старой архитектуре (всё под `primary_user`).
Роли вне списка синхронизация не трогает — репозиторий может держать
и сугубо локальные роли.
### Команды
Три задачи invoke, одинаковый блок (~30 строк) в `tasks.py` каждого
репозитория:
- `inv roles-pull [-- <role>]` — канон → репозиторий. Для каждой роли
из подписки (или одной указанной):
`rsync -a --delete <shared>/roles/<role>/ roles/<role>/`.
Флаг `--delete` обязателен: удалённые в каноне файлы должны исчезать
из копии, иначе дрейф через «хвосты».
- `inv roles-push [-- <role>]` — репозиторий → канон, тот же rsync в
обратную сторону.
- `inv roles-status``diff -r -q` каждой подписанной роли против
канона; выводит `ok` / `differs` / `missing in shared` по ролям,
ненулевой код выхода при расхождении.
Защита от ошибок:
- обе команды печатают список затронутых файлов (`rsync -i`) — операция
видна, а не молчалива;
- `roles-push` при отличающемся каноне показывает дифф и просит
подтверждение (защита от затирания более свежего канона устаревшей
копией; типовой случай — правили роль в двух репозиториях
параллельно);
- `roles-pull` подтверждения не требует: затирается рабочая копия, её
состояние защищено git — `git diff` покажет, что приехало, и любое
изменение можно откатить до коммита.
### Рабочий цикл
Правка роли:
1. Роль правится прямо в том репозитории, где идёт работа, — тестировать
её всё равно можно только запуском реального плейбука этого сервера.
Петля правка → запуск нулевая.
2. Когда правка устоялась: `inv roles-push` и коммит в серверном
репозитории.
3. В остальных репозиториях — `inv roles-pull`, когда их сервер готов
принять обновление. Дифф виден в `git diff`, коммитится в историю
этого репозитория.
Новый сервер:
1. Создать репозиторий, объявить `SYNCED_ROLES`.
2. `inv roles-pull` — роли приезжают из канона, коммитятся.
Новая общая роль: написать в любом репозитории, добавить в его
`SYNCED_ROLES`, `inv roles-push`; остальные подписываются по мере
надобности.
### Предохранитель в lefthook
Дрейф неизбежен (синхронизация — дисциплина, не механизм), поэтому он
должен быть видимым. В pre-commit каждого репозитория — проверка: если
в staged-файлах есть `roles/<подписанная роль>/` и роль отличается от
канона, печатается предупреждение «роль X отличается от ansible-shared,
не забудь roles-push / roles-pull». Именно предупреждение, не
блокировка: коммит локальной правки до push в канон — нормальный
рабочий момент. Если директории `ansible-shared` нет на машине,
проверка молча пропускается (CI, чужая машина).
### Статус ansible-shared
`ansible-shared` — рабочая директория, а не репозиторий-сервис. Можно
сделать её git-репозиторием для бэкапа и истории канона, но этот git
не стоит в рабочей петле: коммиты туда делаются когда угодно и ничего
не блокируют. Серверные репозитории не знают о её git-статусе — они
видят только файлы.
## Отвергнутые альтернативы
- **Общий `roles_path` в ansible.cfg** (`roles_path =
./roles:../ansible-shared/roles`). Нулевая синхронизация, но
репозитории теряют самодостаточность (CI и клон на другой машине
ломаются), а главное — правка роли молча применяется ко всем серверам
при следующем запуске. Для pets-подхода это анти-свойство.
- **Симлинки на общую директорию, закоммиченные в git.** Та же
семантика плюс ломкость: валидны только при конкретной раскладке
директорий на диске.
- **Git submodule / subtree.** Решают задачу для команд ценой петли
commit → push → update и известной UX-боли; принудительная
консистентность здесь не нужна, а петля — главное, от чего уходим.
- **Galaxy-роли из git-репозитория** (`requirements.yml` с git-URL,
версии тегами). Правильный инструмент при росте масштаба, но сейчас
церемония tag → push → install — это и есть слишком большая петля
обратной связи для одного человека.
- **Монорепозиторий на все серверы.** Отвергнут на уровне организации
проектов: серверы — pets, изолированные репозитории с локальной
историей ценнее общей точки правды.
## Цена и риски
- Синхронизация держится на дисциплине. Забытый `roles-push` — канон
отстал; правка одной роли в двух репозиториях без push — конфликт,
который разрешается руками через дифф. Митигация — предупреждение в
lefthook и `roles-status`; при текущем темпе изменений (единицы в
год) этого достаточно.
- Канон существует в одном экземпляре на одной машине. Потеря машины —
потеря только канона, не ролей: они восстановимы из любой свежей
копии (`roles-push`). Опциональный git в `ansible-shared` закрывает
и это.
- Блок задач в `tasks.py` дублируется по репозиториям и сам может
дрейфовать. Принимаем: это ~30 строк, меняются реже ролей.
## Путь миграции
Если ролей станет десяток, серверов — больше трёх, или появится второй
пользователь, `ansible-shared` уже имеет структуру стандартного
galaxy-источника: повесить теги, перевести репозитории на
`requirements.yml` с git-URL, удалить rsync-задачи. Вендоринг копий
делает переход безопасным — в любой момент репозитории самодостаточны.
## План внедрения
1. Создать `~/projects/private/ansible-shared/roles/` из актуальных
ролей pet-project-server (`owner`, `eget`, `secrets`).
2. Добавить задачи `roles-pull` / `roles-push` / `roles-status` в
`tasks.py` pet-project-server; объявить подписку на все три роли.
3. То же в buckland; подписка только на `eget` (его копия сначала
выравнивается через `roles-pull` — дифф косметический, стиль
кавычек). Мёртвую `roles/owner` удалить.
4. Предупреждение о дрейфе в lefthook обоих репозиториев.
5. Новый сервер с самого начала строится на подписке.