tasks(restore-es-indices-books-vds): closed — snapshot restore + 2 preventive controls
snapshot restore из kreknin:daily-2026-05-25 за 46 сек (epz=820604, products=105922, artmone=2621, counts == source). RCA: 5 индексов (включая system .tasks) удалены через ES API DELETE _all за 1 сек на 2026-05-26 10:21 UTC — 1ч 11мин после создания bookva-es в cutover-prep. Каноничный endpoint elasticsearch.kzntsv.site попал под команду которая предназначалась bookva-es:9200 (internal-only, без traefik route). Caller identity unrecoverable: ES audit = X-Pack платный, traefik accessLog был выключен, Portainer CE без audit. Preventive controls applied + verified: 1. ES env action.destructive_requires_name=true (stack 33) — DELETE _all и wildcard теперь 400 BadRequest; by-name DELETE работает (нужно для reindex). Pattern удаления что случился физически невозможен. 2. Traefik JSON accessLog в /letsencrypt/access.log — будущие DELETE оставят forensic след с IP/user/method/path. Wiki concept: .wiki/concepts/es-destructive-delete-incident-2026-05-26.md с recovery runbook + preventive controls + cross-refs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,75 @@
|
||||
- `books-api` + `books-web` логи (stderr) — десятки `ResponseError: index_not_found_exception` за час.
|
||||
- User-facing impact: страница `/epz/search` и поиск товаров в slovo UI не работают.
|
||||
|
||||
## Closure note (2026-05-28 ~20:10 MSK)
|
||||
|
||||
🟢 **Восстановлено за 46 сек через snapshot restore** + два preventive фикса в один заход.
|
||||
|
||||
### Restore
|
||||
|
||||
```
|
||||
POST /_snapshot/kreknin/daily-2026-05-25/_restore?wait_for_completion=true
|
||||
{"indices":"epz,products,artmone","include_global_state":false,"include_aliases":true,"index_settings":{"number_of_replicas":0}}
|
||||
```
|
||||
|
||||
- artmone: 2621 docs ✓ (source: 2621)
|
||||
- epz: 820604 docs ✓ (source: 820604)
|
||||
- products: 105922 docs ✓ (source: 105922)
|
||||
- cluster: green
|
||||
- 3 consumers (books-api/task-runner/job-scheduler) рестартованы — 0 `index_not_found_exception` в логах за 5 мин
|
||||
- public smoke `bookva.kzntsv.site/api/{epz,products}/search` → 401 от auth middleware (поиск-pipeline жив, до wipe было 500)
|
||||
|
||||
**Ключевая удача:** в исходной задаче было записано «`pre-migration-2026-05-25` содержит только `read_me` placeholder — не годится для восстановления» — это правда, но это был **другой** snapshot (он был _до_ reindex'а). А `daily-2026-05-25` (сделан кроном `books-vds-backup-daily-kreknin` в 09:51 UTC через 2ч _после_ reindex'а) содержал full corpus — `[read_me, products, epz, artmone, .tasks]`, 25 sec duration, 5 шардов SUCCESS. Snapshot pipeline (стояший с 25.05) автоматически создал safety net.
|
||||
|
||||
### Root cause (forensics из ES container log)
|
||||
|
||||
Container `elasticsearch` `restart_count=0, created=2026-05-25T07:51:38` — **никогда не рестартовал**, volume bind не задет, индексы существовали и были **удалены через ES API**:
|
||||
|
||||
```
|
||||
2026-05-26 10:21:43.526 UTC [read_me/IfM2V1pTQk-...] deleting index
|
||||
2026-05-26 10:21:44.075 UTC [artmone/xQdFWkafSKy-...] deleting index
|
||||
2026-05-26 10:21:44.257 UTC [epz/AV8inT4YS-e-...] deleting index
|
||||
2026-05-26 10:21:44.450 UTC [.tasks/EbutxIzdTPSq-...] deleting index
|
||||
2026-05-26 10:21:44.576 UTC [products/6VRXsuP5QV-...] deleting index
|
||||
```
|
||||
|
||||
5 индексов (включая system `.tasks`) удалены за 1 секунду = `DELETE _all` / `DELETE *` / Kibana DevTools «delete index».
|
||||
|
||||
Timeline:
|
||||
- **2026-05-25 09:51 UTC** — daily snapshot SUCCESS [read_me, products, epz, artmone, .tasks] (full data)
|
||||
- **между ~10:00 и 26.05 03:02 UTC** — Wipe #1 (snapshot 26.05 содержит только [read_me])
|
||||
- **2026-05-26 05:49–06:44 UTC** — re-reindex (artmone+epz+products, full counts восстановлены)
|
||||
- **2026-05-26 09:10:18 UTC** — `bookva-es` container created (cutover-prep Step 2)
|
||||
- **2026-05-26 10:21:43 UTC** — Wipe #2 (1ч 11мин после создания bookva-es)
|
||||
|
||||
`bookva-es` использует named volume `bookva-es-data`, БЕЗ traefik labels (internal-only). Не задел canonical через mount или routing. **Best guess:** оператор в момент cutover-prep хотел очистить новый `bookva-es:9200`, но команда ушла на `elasticsearch.kzntsv.site` (canonical, slovo). Bookva-es internal-only → DELETE с воркстейшна туда не дойти; canonical через traefik basicAuth → доходит.
|
||||
|
||||
**Caller identity unrecoverable:** ES audit log = X-Pack платная фича (нет на free 7.10). Traefik accessLog был отключён (нет секции `accessLog:` в `traefik.yml`). Portainer audit = CE без enterprise. Только timestamp + DELETE fact в ES log.
|
||||
|
||||
### Preventive fixes (applied 2026-05-28 ~20:10 MSK)
|
||||
|
||||
**Fix #1: `action.destructive_requires_name=true`** на stack 33 (env var добавлен через Portainer PUT API). Verified:
|
||||
- `DELETE /_all` → 400 «Wildcard expressions or all indices are not allowed»
|
||||
- `DELETE /ep*` → 400 same
|
||||
- `DELETE /probe-canary` (by exact name) → 200 (легитимные операции работают)
|
||||
|
||||
Pattern удаления который случился (5 индексов в 1 сек) теперь **физически невозможен**.
|
||||
|
||||
**Fix #2: traefik accessLog** в JSON формат, `/letsencrypt/access.log` (bind-mounted, persistent). Backup `traefik.yml.bak-pre-accesslog-2026-05-28` рядом. Verified entries содержат `RequestMethod`, `RequestHost`, `RequestPath`, `ClientHost`, `ClientUsername`, `DownstreamStatus`, `TLSCipher`. Будущие `DELETE` запросы оставят след — даже если кто-то снова попадёт не на тот endpoint, retrospective forensics возможен.
|
||||
|
||||
### Artifacts
|
||||
|
||||
- `.scratch/stack33-put-2026-05-28.json` — Portainer PUT payload (новый env + сохранён `reindex.remote.whitelist`).
|
||||
- `.wiki/concepts/es-destructive-delete-incident-2026-05-26.md` — incident concept (написан в эту сессию).
|
||||
- ES container env post-fix: `reindex.remote.whitelist=elasticold.kzntsv.site:443` + `action.destructive_requires_name=true`.
|
||||
- Traefik static config: `accessLog: { filePath: /letsencrypt/access.log, format: json, bufferingSize: 100 }`.
|
||||
|
||||
### Follow-ups (не блокеры)
|
||||
|
||||
- **Logrotate на `access.log`** — при ~1 req/sec будет ~30–50 MB/day. Disk free 63G, ressedimption через 1–2 года максимум — но добавить logrotate стоит. Отдельная micro-task.
|
||||
- **Переезд access.log из `/letsencrypt/`** в отдельный bind (`/usr/docker/traefik/logs/`) — косметика, текущее место persistent и работает.
|
||||
- **Pass entry update** — комментарий в `books-vds/full-env` устарел: «NOT Portainer-managed (legacy SSH-compose): **elasticsearch**, mongo, minio, books-db…». Migration 25.05 (`books-vds-stacks-to-portainer`) уже мигрировала их. Поправить при следующем pass-edit.
|
||||
|
||||
## Текущее состояние ES endpoint'ов
|
||||
|
||||
**Source (Windows host, rollback — не выключен):** `https://elasticold.kzntsv.site/`
|
||||
@@ -22,46 +91,7 @@ epz yellow 820604 699.7mb
|
||||
products yellow 105922 26.6mb
|
||||
```
|
||||
|
||||
Проверено `curl -u books:<pw> https://elasticold.kzntsv.site/_cat/indices`.
|
||||
|
||||
**Target (books VDS, Portainer stack 33, canonical):** `https://elasticsearch.kzntsv.site/`
|
||||
|
||||
```
|
||||
_cluster/health: status=green, active_primary_shards=0, docs=0
|
||||
_cat/indices: (пусто, ни одного индекса)
|
||||
_snapshot/_all: kreknin fs repo зарегистрирован (location=/snapshots)
|
||||
```
|
||||
|
||||
ES жив, отвечает 200 на _cluster/health, basicAuth работает. Но **индексов нет вообще** — даже placeholder `read_me` из 25.05 исчез. Значит volume был стёрт ИЛИ контейнер recreate'нут с пустым томом между 25.05 (migration complete, smoke green) и 28.05.
|
||||
|
||||
## Когда сломалось
|
||||
|
||||
- **2026-05-25:** [`migrate-elasticsearch-to-books-vds`](migrate-elasticsearch-to-books-vds.md) closed. Все 3 индекса reindex'нуты с elasticold на books VDS, consumer configs sed'd `elasticold → elasticsearch`, smoke green.
|
||||
- **2026-05-27 10:24 MSK:** books-api / books-web рестартовали (текущий uptime 29h). Это **до** появления ошибок — значит ES опустел уже **после** рестарта, иначе они бы залогировали 404 сразу.
|
||||
- **2026-05-28 13:59 MSK** (по UTC в логах) — самая ранняя зафиксированная ошибка `no such index [epz]`. Окно поломки: 27.05 10:24 → 28.05 13:59.
|
||||
- В этом окне на books VDS происходила работа `bookva-tenant-cutover-prep` (Step 2 — 9 Portainer stacks created, в т.ч. **bookva-es=37**). Возможная гипотеза — конфликт volume mount'а или случайный delete на slovo ES при подготовке bookva ES. Точная причина — детектить надо на VDS (Portainer audit log + volume listing + container restart history).
|
||||
|
||||
## Pointers
|
||||
|
||||
- **Migration task** (canonical процедура reindex-from-remote с elasticold на books VDS): [migrate-elasticsearch-to-books-vds.md](migrate-elasticsearch-to-books-vds.md) — playbook применим повторно как есть.
|
||||
- **Frozen mappings/settings**: `.scratch/source-mappings.json` + `.scratch/source-settings.json` (от 2026-05-25, source структура не менялась — проверить на свежесть до reindex).
|
||||
- **Snapshot repo** на books VDS: `kreknin` (`/snapshots`). Snapshot `pre-migration-2026-05-25` содержал только `read_me` placeholder — **не годится для восстановления** (предмиграционный, до reindex). Полезным может быть kreknin daily snapshot **после** 25.05 если он сохранил полные индексы.
|
||||
- **Consumer configs:** уже указывают на `elasticsearch.kzntsv.site` (sed 25.05). После восстановления — никаких config changes, только рестарт consumers (на случай stale connection pool).
|
||||
|
||||
## Acceptance
|
||||
|
||||
- `curl -u books:<pw> https://elasticsearch.kzntsv.site/_cat/indices` показывает 3 индекса (`epz`/`products`/`artmone`) с doc counts ≈ source: 820604 / 105922 / 2621 (±несколько на дельту со source).
|
||||
- `books-api` + `books-web` stderr за 5 минут после рестарта — 0 строк `index_not_found_exception`.
|
||||
- Smoke: `https://bookva.kzntsv.site/api/epz/search?q=пушкин&...` → 200 с непустым `rows`.
|
||||
- Source `elasticold.kzntsv.site` остаётся live (rollback policy не меняется, как было при оригинальной миграции).
|
||||
|
||||
## Подходы (выбрать на месте)
|
||||
|
||||
1. **Reindex повторно** — точно та же процедура что 25.05 (см. migrate-elasticsearch-to-books-vds.md Steps 4–6). 730MB, ~5-10 мин. Source доступен и полный.
|
||||
2. **Snapshot restore** — если в `kreknin` repo есть свежий snapshot (после 25.05) с непустым `epz`/`products`/`artmone`. Проверить `_snapshot/kreknin/_all?verbose=true`. Быстрее reindex'а, если есть.
|
||||
3. **Volume forensics** — параллельно (или до восстановления) посмотреть Portainer stack 33 history + `docker volume inspect /usr/docker/elasticsearch/data` + audit log в Portainer когда менялся stack. Цель — понять что произошло, чтобы оно не повторилось после bookva cutover.
|
||||
|
||||
Recommendation: начать с (3) для understanding root cause, дальше (2) если snapshot есть, иначе (1).
|
||||
**Target (books VDS, Portainer stack 33, canonical):** `https://elasticsearch.kzntsv.site/` — restored 2026-05-28, counts == source, green.
|
||||
|
||||
## Branch
|
||||
|
||||
@@ -69,14 +99,7 @@ n/a (admin ops)
|
||||
|
||||
## Status
|
||||
|
||||
ready
|
||||
|
||||
## Where I stopped
|
||||
|
||||
(not started — created 2026-05-28 ~16:00 MSK by vitya@books session после диагностики prod-инцидента)
|
||||
|
||||
## Next action
|
||||
|
||||
См. секцию «Подходы» — старт с volume forensics (Portainer stack 33 history + docker volume inspect), параллельно проверить `_snapshot/kreknin/_all` на наличие post-25.05 snapshot'ов с непустыми индексами. Дальше выбрать между snapshot restore и reindex repeat. Восстановить → smoke от `bookva.kzntsv.site/api/epz/search`.
|
||||
closed 2026-05-28
|
||||
|
||||
<!-- created-by: vitya@books-session / 2026-05-28 / trigger: prod slovo поиск товаров+EPZ сломан, ES indices пусты на canonical endpoint -->
|
||||
<!-- closed-by: vitya@.admin-exec / 2026-05-28 / snapshot restore + 2 preventive fixes + wiki concept ingest -->
|
||||
|
||||
Reference in New Issue
Block a user