wiki(books-vds): disk-full incident 2026-06-04 + Bookva->slovo job scoping

Provider Rusonyx alert (96% used). Reclaimed ~64G: truncated runaway
task-runner json-logs (38G) + removed orphan BuildKit builders/volumes
(26G). Added logrotate copytruncate guard.

Root cause: books-task-runner (slovo) error-looped on Ozon seller Bookva
(50542) whose key was intentionally swapped to slovo's as an access
revocation. Spam came from unscoped legacy seller-iterating agenda jobs.
Fixed: idSeller:2 on 5 jobs (deploy-durable, non-_fromConfig). Verified
6448 errors/min -> 0.

Group-V (channel-driven) confirmed non-issue: Bookva channel is type=ym,
no active Bookva-ozon channel exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 12:28:30 +03:00
parent adecf36ffd
commit b0559fdbed

View File

@@ -26,11 +26,37 @@ updated: 2026-05-25 (ES migration from Windows host)
## Disk
```
/dev/vda1 122G 59G used (49%) 63G free
/dev/vda1 122G 53G used (44%) 69G free # 2026-06-04, после disk-full remediation
```
Single root partition, нет отдельной `/data`. Docker hub data под `/var/lib/docker/`.
**Disk-full incident 2026-06-04** (provider Rusonyx monitoring alert, 4.95% free / 96% used). Culprits:
- **38 GB unrotated container logs** — `books-task-runner` (32.8 GB) + `bookva-task-runner`-история (5.6 GB). `books-task-runner` (тенант **slovo**) в tight error-loop с ~2026-05-31 (~9 GB/day, no log rotation). Truncated live (`:> json.log`, no restart).
- **~26 GB orphaned BuildKit cache** — 11 running `buildx_buildkit_builder-*` daemon-контейнеров, которых нет в `docker buildx ls` (orphan builders), держали 9× `*_state` volumes. Removed (`docker rm -f` + `docker volume rm`).
- Result: 96% → 44%.
**Durable guard added 2026-06-04:** `/etc/logrotate.d/docker-containers` (copytruncate, size 200M, rotate 3, compress) + hourly `/etc/cron.d/docker-logrotate`. Caps любой runaway container-log без docker restart.
**Root cause залипшего лога (НЕ «протухший ключ» — правка-самообман, см. ниже):**
- На books VDS два зеркальных стека: **books-*** (тенант slovo, `books-db`) и **bookva-*** (тенант bookva, `bookva-db`), один и тот же образ. Ozon-вызовы шлёт `*-task-runner` (не scheduler — он лишь триггерит agenda-джобу, HTTP делает task-runner). Только `books-task-runner` сыпал ошибки; `bookva-task-runner` — 0.
- В таблице `sellers` (обе БД) два Ozon-продавца: **Slovo (client_id 94191)** и **Bookva (client_id 50542)**. В `books-db` у строки Bookva в `api_key` **намеренно вписан ключ Slovo** (`9683…` вместо живого `ddc2…`) — это **сознательная ревокация** доступа books→Ozon-аккаунт Bookva (сделано в прошлой сессии). Ozon на `Client-Id:50542`+чужой ключ → `code 5 Invalid Api-Key`. **НЕ чинить этот ключ** — это и есть защита. (Живой ключ Bookva — в `bookva-db` и в конфиге, для тенанта bookva.)
- Спам шёл оттого, что часть books-agenda-джоб **не заскоуплены** на slovo: при пустом `data.idSeller` таски итерируют ВСЕХ продавцов (`data.idSeller ? filter : allSellers`), включая Bookva с битым ключом. Раньше заскоупили только `ozon stocks syncronization` (`idSeller:2`).
**Фикс 2026-06-04:** проставлен `data.idSeller=2, otherSellers:[]` в `books-job-scheduler-mongo` (agendaDb.agendaJobs) пяти джобам: `ozon fbs postings syncronization`, `generate old prices`, `ozon fbs products syncronization`, `create ozon products in incorrect state report`, `put on sale products`. Залипшие overdue-прогоны оборваны: unlock (`lockedAt:null`) + `docker restart books-task-runner`. Результат: Invalid-Api-Key 6448/мин → **0**, джобы отработали по slovo и перепланировались.
**Durability фикса:** правка в mongo переживает деплой. Эти джобы — легаси (`data._fromConfig` отсутствует), reconciler (`lib/reconciler.js`, `RECONCILER_ENABLED=true`) трогает только `_fromConfig:true`-джобы; в `tasks.json` (запечён в образ) у них `schedule:null` → reconciler их пропускает. Эмпирика: `stocks-sync idSeller:2` (только в mongo) пережил деплой 31.05.
**Полнота скоупа (сверено 2026-06-04).** На books-стороне сосуществуют ДВА поколения джоб:
- **Legacy** (без `_fromConfig`, расписание в persisted mongo): seller-перебирающие — `seller.findAll` при пустом `data.idSeller`ВСЕ продавцы. Ровно 7 таких: `ozonFbsPostingsSyncronization`, `ozonFbsProductsSyncronization`, `ozonStocksSyncronization`, `generateOldPrices`, `createOzonProductsInIncorrectStateReport`, `putOnSaleProducts`, `syncStocksWithWarehouse`. **Все заскоуплены `idSeller:2`** (5 сегодня + stocks + warehouse ранее). Других seller-перебирающих нет.
- **Reconciler-gen** (`_fromConfig:true`, из `tasks.json`): фильтруются по **`salesChannels:[2]`** (ozon-канал Slovo), не по idSeller — `load products to ozon`, `ozonProductSalesChannelSync`, `unarchiveAutoArchivedProducts`, `addFixedPriceProductsToAction`, `fbsPicking*` и т.д. Уже корректно указывают только на канал Slovo.
**Группа ВНЕ дыра (изначальная гипотеза снята).** `sales_channels` в books-db: канал 2 = Slovo **type=ozon**; канал 3 = Bookva **type=ym** (Яндекс.Маркет, не Ozon). Активного Bookva-**ozon**-канала нет → channel-driven Ozon-джобы физически не могут залезть в Bookva. Bookva-`ym` использует отдельный `ym_api_key`, к инциденту не относится.
Follow-ups:
1. **Каноничный фикс — в репо books**: legacy seller-перебирающие джобы стоит либо мигрировать в `tasks.json` со скоупом, либо вывести из эксплуатации (их функции, возможно, уже покрыты reconciler-gen). Риск mongo-only правки: если будущий релиз даст legacy-джобе реальный `schedule` в `tasks.json` без скоупа — reconciler пере-сеет её (data без idSeller) → доступ к Bookva вернётся.
2. wiki-drift — `bookva-*` stack (bookva-api/db/es/minio/mongo/scheduler/task-runner/web) + books-task-runner/scheduler не в stack-inventory ниже; нужен re-inventory.
## Стек (2026-05-25 inventory)
### Portainer-managed stacks (endpoint 1)