79 lines
5.8 KiB
Markdown
79 lines
5.8 KiB
Markdown
---
|
||
title: sched-runtime-to-file-managed-migration
|
||
type: concept
|
||
status: live
|
||
tags: [books, sched, file-managed, durable-config, incident, live-sync]
|
||
related: "[[sched-ozon-creds-stub-contract]], [[snolla-smtp-mail-delivery-2026-08]], [[portainer-stack-management-books-vds]]"
|
||
updated: 2026-08-23
|
||
---
|
||
|
||
# Sched runtime → file-managed миграция (durable config)
|
||
|
||
## TL;DR
|
||
|
||
После инцидента 2026-08-23 (спин `ozonFbsPostingsSyncronization`, ~75 req/сек) оба тенанта books
|
||
(slovo + bookva) переведены на **file-managed** расписания sched: декларатив в
|
||
`deploy/sched-tasks.json` (git-tracked), демон подхватывает файл live-sync'ом. Runtime-путь
|
||
(таски в сторе, `fileManaged:false`) хрупкий — расписания живут в памяти демона и слетают
|
||
при pause/перезапуске, таски остаются сиротами без `data`.
|
||
|
||
**Ключевой факт миграции: live-sync НЕ конвертирует runtime-таски** (sync создаёт только
|
||
новые из файла, существующие runtime НЕ перезаписывает). Перевод = явный `DELETE` runtime-
|
||
тасок и расписаний через admin API, после чего sync доставляет file-managed версии.
|
||
|
||
## Почему runtime хрупкий (контекст инцидента)
|
||
|
||
- Расписания runtime-тасок держатся **в памяти демона** → при `pause`/`restart` слетают.
|
||
- Таски остаются сиротами: `fileManaged:false`, `schedule:null`, `data:undefined`.
|
||
- `deploy/sched-tasks.json` был **gitignored** (прод-файл на хосте) — конфиг не версионировался,
|
||
восстановление = ручные миграции из `agendaJobs`.
|
||
- Runtime-таска могла нести **чужой URL runner'а** (cross-tenant leak: `books-task-runner` в
|
||
bookva-конфиге) — файл чинит (bookva → `bookva-task-runner`).
|
||
|
||
## Миграционный рецепт (по тенанту)
|
||
|
||
1. **Файл** — из git-репо в прод-расположение:
|
||
- slovo: `/opt/books/sched/tasks.json` (bind-директория `/opt/books/sched:/app/config:ro`).
|
||
- bookva: volume `/var/lib/docker/volumes/bookva-sched-config/_data/tasks.json` (→ `/app/config`).
|
||
- Заливка **in-place**: `cp /tmp/new /path/tasks.json` (не mv — сохранить inode/perms).
|
||
Бэкап: `cp tasks.json tasks.json.bak-<tag>`.
|
||
- Перед заливкой: diff против live — допускаются только целевые изменения (books собирает
|
||
файл байт-идентичным live + новые таски).
|
||
2. **Live-sync** — демон подхватывает файл ≤60с, рестарт НЕ нужен. Верифицировать через
|
||
`GET /api/tasks` (новые таски появляются как `fileManaged:true`).
|
||
3. **DELETE runtime** (sync их не убирает — только API, runs-история сохраняется):
|
||
- `DELETE /api/tasks/:name` ×N — имена URL-encode (`%20`).
|
||
- `DELETE /api/schedules/:id` ×N — **не каскадятся**, удалять отдельно. Все → 204.
|
||
4. **Verify**:
|
||
- `fileManaged:true` у всех целевых, `disabled:false`, `paused:false`.
|
||
- Дублей 0: `[].taskName | group_by(.) | map(select(length>1))` пуст.
|
||
- `data` на расписании (не на таске): slovo `{idSeller:2}` / bookva `{idSeller:1}`,
|
||
отчёт — `{recipients:[...], idSeller}`.
|
||
- Ручной раунд: `POST /api/tasks/:name/run` → `succeeded {sellersProcessed:N}`.
|
||
|
||
## Gotchas
|
||
|
||
| Гоча | Деталь |
|
||
|---|---|
|
||
| id-паттерн | file-managed schedule `id` = taskName; runtime = UUID |
|
||
| ghost-таски | `fileManaged:true` сироты (выпилены из файла) висят `disabled:true` — удалять `DELETE /api/tasks/:name` |
|
||
| task-runner config | `/opt/books/task-runner/config/default.json` кэшируется при первом send (lazy init `sendReportsByEmail.js`) → после правки **рестарт обязателен** |
|
||
| SMTP | `from` == auth-user обязателен (Yandex: 550 not owned / 525 disabled). Креды `e-16513832@yandex.ru` (app-pass, `pass show snolla-smtp/full-env`). См. [[snolla-smtp-mail-delivery-2026-08]] |
|
||
| имена тасок | «ozon fbs postings syncronization» — пробелы, в DELETE URL-encode |
|
||
| шаблон vs прод | конвертер из шаблона может давать лишние trigger-only таски — в деплой-файл не включать без причины |
|
||
|
||
## Текущее состояние (2026-08-23, #991 closed)
|
||
|
||
- slovo: 25 тасок, file-managed, отчёт `{idSeller:2, recipients:[3]}` (recipients восстановлены,
|
||
commit `854e381`).
|
||
- bookva: 23 таски, file-managed, `data {idSeller:1}` (commit `6d4f163` bookva-overlay).
|
||
- Ghost `system-cleanup-task-runs` удалён на обоих тенантах.
|
||
- Доступ: sched admin API — books-sched `172.20.0.20:3031`, bookva-sched `172.20.0.12:3031`,
|
||
Bearer `SCHED_ADMIN_KEY` (env контейнера), через SSH books-vds. Роуты — `packages/admin-api/src/admin-api.ts` (репо sched).
|
||
|
||
## Related
|
||
|
||
- [[sched-ozon-creds-stub-contract]] — почему мисматч-пары кредов намеренные (тот же инцидент).
|
||
- [[snolla-smtp-mail-delivery-2026-08]] — SMTP-уроки (525/554/550), перенос на e-16513832@yandex.ru.
|
||
- [[portainer-stack-management-books-vds]] — управление стеками books VDS.
|