Tududi: add task tracke application
This commit is contained in:
@@ -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. Новый сервер с самого начала строится на подписке.
|
||||
Reference in New Issue
Block a user