Compare commits
3 Commits
068f7b2691
...
63708fe659
| Author | SHA1 | Date | |
|---|---|---|---|
| 63708fe659 | |||
| 48a5cf397e | |||
| e9b0a9edbc |
@@ -1,72 +1,56 @@
|
|||||||
---
|
---
|
||||||
_last_updated_: 2026-05-28T14:15:00+03:00
|
_last_updated_: 2026-05-28T20:30:00+03:00
|
||||||
session_id: 2026-05-28-vds-kzntsv-network-stack-mismatch
|
session_id: 2026-05-28-restore-es-indices-books-vds-closure
|
||||||
---
|
---
|
||||||
|
|
||||||
# Next session handoff
|
# Next session handoff
|
||||||
|
|
||||||
## Контекст сессии
|
_Сессия: восстановили 3 ES индекса на canonical (books VDS stack 33) через
|
||||||
|
snapshot restore + добавили 2 preventive control'а против повтора. Таска
|
||||||
vds-kzntsv (89.253.255.94) был недоступен по сети ~05:45–08:30 MSK 28 мая 2026 (~2.5ч активного outage). С 08:43 жил на эфемерной статике (через VNC) до окончательного fix хостером в **13:52 MSK** — они переписали конфиг через свой `start/ipadd` procedure, мы предварительно masked netplan + systemd-networkd через SSH.
|
🟢 closed. Wiki concept + closure note записаны._
|
||||||
|
|
||||||
**Revised RCA** (после resolution): наш сетевой стек был mixed (netplan+networkd поверх provider's expected ifupdown). 8 дней работало потому что networkd сам получал DHCP. Когда что-то на стороне Rusonyx разорвало DHCP-binding (28.05 утром) — их auto-recovery `start/ipadd` не смогла применить static config, потому что networkd «держал» eth0. Fix = mask netplan/networkd, оставить только ifupdown.
|
|
||||||
|
|
||||||
## LIVE сейчас
|
|
||||||
|
|
||||||
- **vds-kzntsv** работает на чистом ifupdown stack: `/etc/network/interfaces.d/ifcfg-eth0` (provider-managed), netmask **/18** (`255.255.192.0`), gw `89.253.192.1`. `netplan` + `systemd-networkd*` masked.
|
|
||||||
- **24 docker контейнера up** включая весь стек (gitea, registry, verdaccio, postgres, mariadb, mongo, redis, owncloud/oCIS, modulair-rag×4, mssql, board-viewer, traefik, portainer, ntfy, vds-ops-mcp, vds-docker-proxy-ro). **1 unhealthy** — `board-viewer-build` (предсуществующий, не связан с инцидентом).
|
|
||||||
- **Диск 79%** (118G/158G) — после quick GC сегодня утром (build cache 10.6G + dangling 0.3G + container logs ~3G truncated). Registry GC ~20G storage **не сделан** — pending.
|
|
||||||
- **Wiki, memory, STATUS** — обновлены с финальным RCA. Commit за сессию pending (см. ниже).
|
|
||||||
|
|
||||||
## Что делать на следующей сессии (по приоритету)
|
|
||||||
|
|
||||||
### 1. Cleanup netplan-artifacts (опционально, низкий приоритет)
|
|
||||||
|
|
||||||
После resolution на сервере остались netplan конфиги (не активны, netplan masked, но лежат):
|
|
||||||
|
|
||||||
- `/etc/netplan/01-eth0.yaml` — мы создали 28.05 утром при попытке fix через netplan
|
|
||||||
- `/etc/netplan/50-cloud-init.yaml` — от cloud-init
|
|
||||||
- `/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg` — мы создали (с опечаткой `disbaled` в одной из попыток, потом исправили)
|
|
||||||
|
|
||||||
Можно удалить или оставить — они не влияют (netplan masked). **Если** соберёмся когда-нибудь снять mask с netplan — следует тогда clean up. Сейчас не трогаем.
|
|
||||||
|
|
||||||
### 2. Registry GC (~20G storage reclaim — pending)
|
|
||||||
|
|
||||||
Из task #2 в TaskList. Окно read-only=true на 5-15 мин. **Делать только в low-traffic окно** — registry push/pull временно недоступен.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# на VDS
|
|
||||||
sudo docker exec registry registry garbage-collect --delete-untagged=true /etc/docker/registry/config.yml
|
|
||||||
# проверка размера до/после
|
|
||||||
sudo du -sh /opt/stacks/registry
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. board-viewer-build unhealthy — разбор
|
|
||||||
|
|
||||||
Предсуществующий, не связан с инцидентом. Если беспокоит — `sudo docker logs board-viewer-build | tail -50` + проверить healthcheck в compose. Иначе можно `--no-healthcheck` если он не нужен в running container.
|
|
||||||
|
|
||||||
### 4. SLA / RCA от Rusonyx — follow up (опционально)
|
|
||||||
|
|
||||||
В первом нашем тикете запрашивали технический RCA + SLA-компенсацию. User просил это не муссировать. На усмотрение — можно через несколько дней спросить «приходил ли формальный RCA», но force'ить не нужно.
|
|
||||||
|
|
||||||
## Спроси user'а
|
|
||||||
|
|
||||||
- **Registry GC** — окно ~5-15 мин в read-only=true, делать ночью или сейчас?
|
|
||||||
- **Cleanup netplan artifacts** — оставить как есть или зачистить `/etc/netplan/*`? (Low-priority)
|
|
||||||
- **Push wiki + memory + tasks** — push'нуть commit'нутые изменения в origin? (Per project-discipline нужен per-session grant, пока не давал)
|
|
||||||
|
|
||||||
## Не делать (preemptive guards)
|
|
||||||
|
|
||||||
- **НЕ unmask netplan/systemd-networkd** на vds-kzntsv. Anti-pattern для Rusonyx (см. concept).
|
|
||||||
- **НЕ редактировать `/etc/network/interfaces` или `/etc/network/interfaces.d/ifcfg-eth0`** — provider's start/ipadd procedure перезапишет при любых их manipulations с VM (resize, network reset).
|
|
||||||
- **НЕ reboot VDS без необходимости** — конфиг теперь правильный, но любой их network-action может что-то поменять, и проверить новое состояние придётся через VNC.
|
|
||||||
- **НЕ делать registry GC без `read-only=true` mode** — rogue push может corrupt'ить blobs.
|
|
||||||
|
|
||||||
## Memory updates за сессию
|
|
||||||
|
|
||||||
- **UPDATE** `vds-kzntsv-rusonyx-network-recovery.md` — переписан с revised RCA: provider stack = ifupdown, netmask /18, canonical bootstrap-step `mask netplan/networkd`. Anti-pattern: netplan на Rusonyx.
|
|
||||||
- **Уточнение паттерна:** при сетевых проблемах на Rusonyx VDS первым делом проверять что стек = ifupdown (не netplan). `systemctl is-enabled netplan systemd-networkd networking` — должно быть `masked masked enabled`.
|
|
||||||
|
|
||||||
## Recent commits
|
## Recent commits
|
||||||
|
|
||||||
- (pending — этот сеанс ещё не commit'ил; wiki + memory + STATUS + NEXT_SESSION готовы к одному commit'у).
|
- `48a5cf39` tasks(restore-es-indices-books-vds): closed — snapshot restore + 2 preventive controls
|
||||||
|
- `e9b0a9ed` docs(tasks): restore-elasticsearch-indices-books-vds — заведение таски на prod outage
|
||||||
|
- `068f7b26` docs(iis-migration): owner DNS-flip instructions (labtools, tandemmebel)
|
||||||
|
- `11554d45` wiki(ingest): vds-kzntsv network-stack mismatch RCA + ifupdown anti-pattern
|
||||||
|
- `dacc39a1` tasks(modulair-rag-vds-redeploy): follow-ups #1-3 done
|
||||||
|
|
||||||
|
## Что закрыли в эту сессию
|
||||||
|
|
||||||
|
- **Restore (46 сек):** `POST /_snapshot/kreknin/daily-2026-05-25/_restore` с `indices=epz,products,artmone`. Counts == source (820604/105922/2621), green.
|
||||||
|
- **RCA:** 5 индексов удалены через ES API `DELETE _all` 2026-05-26 10:21 UTC, **1ч 11мин после создания bookva-es**. Best guess — оператор в cutover-prep попал на canonical вместо internal-only bookva-es:9200. Caller identity unrecoverable (audit/accessLog отсутствовали).
|
||||||
|
- **Control #1:** `action.destructive_requires_name=true` env var на stack 33 — wildcard/`_all` DELETE теперь 400. By-name работает. Verified.
|
||||||
|
- **Control #2:** Traefik JSON accessLog в `/letsencrypt/access.log` — будущие DELETE с IP/user/method/path в логе. Restart traefik сделан, log файл генерится.
|
||||||
|
- **Wiki:** `concepts/es-destructive-delete-incident-2026-05-26.md` (recovery runbook + preventive controls + cross-refs). Index + log обновлены.
|
||||||
|
|
||||||
|
## Open треки
|
||||||
|
|
||||||
|
| Трек | Готовность | Entry-point |
|
||||||
|
|---|---|---|
|
||||||
|
| `books-bookva-user-whitelist-gathering` | ⚪ ready — gathering от учредителя Bookva | `.tasks/STATUS.md` line ~221 |
|
||||||
|
| `stateful-split-volume-copy` | ⚪ ready — maintenance window 5–10 мин | `.tasks/STATUS.md` line ~97 |
|
||||||
|
| `infra-inventory` | ⚪ ready — two-tier (public + admin) | `.tasks/STATUS.md` line ~150 |
|
||||||
|
| `books-stateful-split-execution` | 🔵 blocked — deps в `victor/books` | `.tasks/STATUS.md` line ~250 |
|
||||||
|
| `books-vds-bookva-bootstrap` | 🔵 blocked — Bookva billing + provision | `.tasks/STATUS.md` line ~161 |
|
||||||
|
| `books-dns-cutover-bookva` | 🔵 blocked — после `books-vds-bookva-bootstrap` | `.tasks/STATUS.md` line ~191 |
|
||||||
|
|
||||||
|
Активных 🔴 / 🟡 нет.
|
||||||
|
|
||||||
|
## Спроси user'а
|
||||||
|
|
||||||
|
- **Logrotate для `access.log`** (~30–50 MB/day, disk free 63G): отдельная micro-task сейчас, или отложить до первого ощутимого роста? Записан как follow-up в task closure + wiki concept, но не оформлен.
|
||||||
|
- **`pass books-vds/full-env` comment outdated** — в нём ещё написано «NOT Portainer-managed: elasticsearch, mongo, minio, books-db…». На самом деле migration 25.05 (`books-vds-stacks-to-portainer`) их мигрировала. Поправить при следующем pass-edit. Сейчас?
|
||||||
|
- **`stack33-put-2026-05-28.json` в `.scratch/`** — он gitignore'нут (transient drop). Если хочется keep как audit artefact — нужно отдельно архивировать.
|
||||||
|
|
||||||
|
## Не делать (preemptive guards)
|
||||||
|
|
||||||
|
- **Не снимать `action.destructive_requires_name=true`** на canonical ES. Если ОЧЕНЬ нужен wildcard DELETE — сначала остановиться и подумать на каком endpoint.
|
||||||
|
- **Не использовать `ssh + docker compose up -d`** на VDS для управления Portainer stacks — anti-pattern из CLAUDE.md. Только Portainer API / UI (как делали PUT stack 33 в эту сессию).
|
||||||
|
- **Никогда не делиться `elasticsearch.kzntsv.site`** между slovo и bookva. Bookva-es сейчас internal-only через docker network — это правильно. Если когда-нибудь нужен external — отдельный hostname.
|
||||||
|
- **branch ahead by 1 commit от origin/master** — push без explicit user grant (project-discipline Rule 4). Сейчас в working tree 2 unpushed commit'а: `e9b0a9ed` + `48a5cf39`.
|
||||||
|
|
||||||
|
## Memory updates за сессию
|
||||||
|
|
||||||
|
(нет на этом раунде — ничего не сохраняли в `~/.claude/projects/.../memory/`)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
# Admin Task Board
|
# Admin Task Board
|
||||||
|
_Updated: 2026-05-28 (вечер) — 🟢 `restore-elasticsearch-indices-books-vds` **closed** в ту же сессию через snapshot restore (46 сек) из `daily-2026-05-25` (полные данные, ~2ч после оригинального reindex'а). RCA: 5 индексов (включая system `.tasks`) удалены через ES API `DELETE _all` за 1 сек на 2026-05-26 10:21 UTC, **1ч 11мин после создания bookva-es** — оператор в cutover-prep попал на canonical вместо internal-only bookva-es. Caller identity не восстановим (audit log = X-Pack платный, traefik accessLog был выключен, Portainer audit = enterprise). 2 preventive фикса applied + verified: (1) ES env `action.destructive_requires_name=true` — `DELETE _all` / wildcard теперь 400; (2) traefik JSON accessLog в `/letsencrypt/access.log` — будущие DELETE оставят forensic след. См. таску + `.wiki/concepts/es-destructive-delete-incident-2026-05-26.md`._
|
||||||
|
_Updated: 2026-05-28 — **prod incident**: books-app slovo поиск товаров + EPZ сломаны, ES `elasticsearch.kzntsv.site` (books VDS stack 33) пуст (0 индексов из 3 ожидаемых; source `elasticold.kzntsv.site` rollback — все 928k docs на месте). Заведена ⚪ `restore-elasticsearch-indices-books-vds`. Окно поломки: 27.05 10:24 → 28.05 13:59, в этом окне шла работа bookva-cutover-prep (bookva-es stack 37 создавался) — возможный конфликт. См. таску для playbook'а._
|
||||||
_Updated: 2026-05-28 — **vds-kzntsv network-stack mismatch RESOLVED** в 13:52 MSK. Сначала ~2.5ч активного outage (05:45-08:30) + ~5.5ч на эфемерной статике до окончательного fix хостером. Revised RCA: наш netplan+networkd поверх provider's expected ifupdown stack ломал их auto-recovery когда DHCP-binding разорвался на их стороне. Fix: `systemctl mask netplan systemd-networkd` (на running system, без stop — IP и SSH сохранились), Rusonyx ребутнули + положили чистый `/etc/network/interfaces.d/ifcfg-eth0` с /18 netmask через свой `start/ipadd` procedure. Все 24 docker контейнера up. Disk after GC: 79% (132G→118G/158G). Anti-pattern закреплён: НЕ использовать netplan на Rusonyx VDS. Wiki updated с revised RCA + permanent-fix runbook + 2 quirks (#9 /18 layout, #10 ifupdown vs netplan)._
|
_Updated: 2026-05-28 — **vds-kzntsv network-stack mismatch RESOLVED** в 13:52 MSK. Сначала ~2.5ч активного outage (05:45-08:30) + ~5.5ч на эфемерной статике до окончательного fix хостером. Revised RCA: наш netplan+networkd поверх provider's expected ifupdown stack ломал их auto-recovery когда DHCP-binding разорвался на их стороне. Fix: `systemctl mask netplan systemd-networkd` (на running system, без stop — IP и SSH сохранились), Rusonyx ребутнули + положили чистый `/etc/network/interfaces.d/ifcfg-eth0` с /18 netmask через свой `start/ipadd` procedure. Все 24 docker контейнера up. Disk after GC: 79% (132G→118G/158G). Anti-pattern закреплён: НЕ использовать netplan на Rusonyx VDS. Wiki updated с revised RCA + permanent-fix runbook + 2 quirks (#9 /18 layout, #10 ifupdown vs netplan)._
|
||||||
_Updated: 2026-05-27 — `modulair-rag-vds-redeploy` 🟢 **closed** в ту же сессию: 4-контейнерный стек развёрнут на VDS (Portainer stack 15), acceptance 6/6. Образы пересобраны на самом VDS (push 3.36GB через traefik с дома падал 499); env/entrypoint/minio-host скорректированы под VDS-реальность. 3 follow-up'а переданы в modulair-rag handoff._
|
_Updated: 2026-05-27 — `modulair-rag-vds-redeploy` 🟢 **closed** в ту же сессию: 4-контейнерный стек развёрнут на VDS (Portainer stack 15), acceptance 6/6. Образы пересобраны на самом VDS (push 3.36GB через traefik с дома падал 499); env/entrypoint/minio-host скорректированы под VDS-реальность. 3 follow-up'а переданы в modulair-rag handoff._
|
||||||
_Updated: 2026-05-27 — заведена `modulair-rag-vds-redeploy` ⚪ (handoff из modulair-rag session). NAS-loss redeploy 4 контейнеров на VDS. Блокер MinIO снят — verified up на VDS 2026-05-27. postgres `proxy`-network reachability подтверждён (`postgres:5432` резолвится из pipeline/mcp). Спека: modulair-rag concept `nas-loss-vds-redeploy-context` + compose.yml as-is._
|
_Updated: 2026-05-27 — заведена `modulair-rag-vds-redeploy` ⚪ (handoff из modulair-rag session). NAS-loss redeploy 4 контейнеров на VDS. Блокер MinIO снят — verified up на VDS 2026-05-27. postgres `proxy`-network reachability подтверждён (`postgres:5432` резолвится из pipeline/mcp). Спека: modulair-rag concept `nas-loss-vds-redeploy-context` + compose.yml as-is._
|
||||||
@@ -18,6 +20,13 @@ _Updated: 2026-05-26 — заведена `bookva-tenant-cutover-prep` ⚪ (7-st
|
|||||||
|
|
||||||
_Updated: 2026-05-25 (`iis-migration-to-ruvds` 🟢 closed Phase 1 per user decision — 9/24 hostnames live на RUVDS, source IIS оставлен running. `migrate-elasticsearch-to-books-vds` 🟢 closed ранее сегодня. 16 IIS hostnames + LE renewal pipeline + decommission — descoped в Closure note, не отдельные tracker tasks.)_
|
_Updated: 2026-05-25 (`iis-migration-to-ruvds` 🟢 closed Phase 1 per user decision — 9/24 hostnames live на RUVDS, source IIS оставлен running. `migrate-elasticsearch-to-books-vds` 🟢 closed ранее сегодня. 16 IIS hostnames + LE renewal pipeline + decommission — descoped в Closure note, не отдельные tracker tasks.)_
|
||||||
|
|
||||||
|
## 🟢 [restore-elasticsearch-indices-books-vds] — closed 2026-05-28 — snapshot restore из `kreknin:daily-2026-05-25` за 46 сек (epz=820604, products=105922, artmone=2621, counts == source). Forensics: 5 индексов удалены через ES API `DELETE _all` 26.05 10:21 UTC, через 1ч 11мин после создания `bookva-es` — оператор в cutover-prep попал на canonical вместо internal-only bookva. Caller identity unrecoverable (audit log = X-Pack платный, traefik accessLog был выключен). 2 preventive фикса applied: ES env `action.destructive_requires_name=true` + traefik JSON accessLog в `/letsencrypt/access.log`. См. [restore-elasticsearch-indices-books-vds.md](restore-elasticsearch-indices-books-vds.md) § Closure + `.wiki/concepts/es-destructive-delete-incident-2026-05-26.md`.
|
||||||
|
**Branch:** n/a
|
||||||
|
<!-- created-by: vitya@books-session / 2026-05-28 / trigger: prod 404 index_not_found_exception на /api/epz/search + /api/products/search -->
|
||||||
|
<!-- closed-by: vitya@.admin-exec / 2026-05-28 / snapshot restore + 2 preventive fixes verified -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🟢 [modulair-rag-vds-redeploy] — closed 2026-05-27 — 4-контейнерный стек развёрнут на VDS, acceptance 6/6
|
## 🟢 [modulair-rag-vds-redeploy] — closed 2026-05-27 — 4-контейнерный стек развёрнут на VDS, acceptance 6/6
|
||||||
|
|
||||||
NAS помер → 4 контейнера пропали → fresh redeploy на VDS (89.253.255.94). Portainer stack id=15. **Acceptance 6/6 green:** 4 контейнера up без restart-loop; lightrag Uvicorn :9621; `https://modulair-mcp.kzntsv.site`→200; pipeline+tier1 0% CPU; pipeline-лог без postgres/minio ошибок.
|
NAS помер → 4 контейнера пропали → fresh redeploy на VDS (89.253.255.94). Portainer stack id=15. **Acceptance 6/6 green:** 4 контейнера up без restart-loop; lightrag Uvicorn :9621; `https://modulair-mcp.kzntsv.site`→200; pipeline+tier1 0% CPU; pipeline-лог без postgres/minio ошибок.
|
||||||
|
|||||||
105
.tasks/restore-elasticsearch-indices-books-vds.md
Normal file
105
.tasks/restore-elasticsearch-indices-books-vds.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# restore-elasticsearch-indices-books-vds
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Восстановить 3 ES индекса (`epz`, `products`, `artmone`) на canonical endpoint `elasticsearch.kzntsv.site` (books VDS, Portainer stack 33). Сейчас target ES пустой → весь поиск в books-app (slovo) сломан (404 `index_not_found_exception` для `epz` и `products`).
|
||||||
|
|
||||||
|
## Симптомы (зафиксировано 2026-05-28 ~15:50 MSK)
|
||||||
|
|
||||||
|
- `bookva.kzntsv.site/api/products/search?q=...` → 500 `no such index [products]`.
|
||||||
|
- `bookva.kzntsv.site/api/epz/search?q=...` → 500 `no such index [epz]`.
|
||||||
|
- `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/`
|
||||||
|
|
||||||
|
```
|
||||||
|
index health docs.count store.size
|
||||||
|
artmone yellow 2621 1.4mb
|
||||||
|
epz yellow 820604 699.7mb
|
||||||
|
products yellow 105922 26.6mb
|
||||||
|
```
|
||||||
|
|
||||||
|
**Target (books VDS, Portainer stack 33, canonical):** `https://elasticsearch.kzntsv.site/` — restored 2026-05-28, counts == source, green.
|
||||||
|
|
||||||
|
## Branch
|
||||||
|
|
||||||
|
n/a (admin ops)
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
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 -->
|
||||||
124
.wiki/concepts/es-destructive-delete-incident-2026-05-26.md
Normal file
124
.wiki/concepts/es-destructive-delete-incident-2026-05-26.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
title: ES destructive delete incident — canonical endpoint vs internal-only bookva-es
|
||||||
|
type: concept
|
||||||
|
tags: [elasticsearch, incident, books-vds, tenant-cutover, operator-error]
|
||||||
|
related: [[../entities/books-vds]], [[../sources/books-vds-backup-daily-kreknin-2026-05-25]]
|
||||||
|
updated: 2026-05-28
|
||||||
|
---
|
||||||
|
|
||||||
|
# ES destructive delete incident — canonical endpoint vs internal-only bookva-es
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
В период `bookva-tenant-cutover-prep` (2026-05-26) **дважды** были удалены все user-индексы (`epz`, `products`, `artmone`, плюс system `.tasks` и placeholder `read_me`) на canonical ES `elasticsearch.kzntsv.site` (books VDS stack 33). Удаление выполнено через ES REST API одной командой типа `DELETE _all` / `DELETE *` (5 индексов за <1 секунды). Контейнер не рестартовал, bind-volume не задет — данные стёрты на уровне cluster state.
|
||||||
|
|
||||||
|
**Caller identity не восстановим** — ES audit log = X-Pack платная фича (отсутствует на free 7.10), traefik accessLog был выключен, Portainer audit log = enterprise.
|
||||||
|
|
||||||
|
## Hypothesis (timeline-based, не доказан)
|
||||||
|
|
||||||
|
Оператор в момент cutover-prep хотел очистить **новый bookva-es** перед заполнением, но команда ушла на canonical `elasticsearch.kzntsv.site` вместо `bookva-es:9200`.
|
||||||
|
|
||||||
|
```
|
||||||
|
2026-05-25 09:51 UTC daily snapshot SUCCESS [read_me, products, epz, artmone, .tasks] ← данные ЕСТЬ
|
||||||
|
...
|
||||||
|
2026-05-26 03:02 UTC daily snapshot success [read_me] only ← Wipe #1 случился в окне
|
||||||
|
2026-05-26 05:49–06:44 UTC re-reindex artmone+epz+products восстанавливают данные
|
||||||
|
2026-05-26 09:10:18 UTC bookva-es container created (cutover-prep Step 2)
|
||||||
|
2026-05-26 10:21:43–44 UTC Wipe #2: 5 индексов удалены за 1 сек ← 1ч 11мин после bookva-es
|
||||||
|
```
|
||||||
|
|
||||||
|
`bookva-es` использует named volume `bookva-es-data`, БЕЗ traefik labels (internal-only через docker network `proxy`). Не задел canonical через volume mount или routing. `DELETE` с воркстейшна на `bookva-es:9200` физически невозможен (порт не expose'нут наружу); на `elasticsearch.kzntsv.site` — возможен через traefik basicAuth.
|
||||||
|
|
||||||
|
## Forensics — что есть и чего нет
|
||||||
|
|
||||||
|
| Источник | Доступность | Содержание |
|
||||||
|
|---|---|---|
|
||||||
|
| ES container log `docker logs elasticsearch` | ✅ есть | `o.e.c.m.MetadataDeleteIndexService` пишет факт + index UUID + timestamp. Caller отсутствует |
|
||||||
|
| ES audit log | ❌ нет | X-Pack Security платная фича. Free 7.10 audit отключён |
|
||||||
|
| Traefik accessLog | ❌ был выключен | Файл `traefik.yml` не содержал секции `accessLog:`. Запросы (включая DELETE) не пишутся |
|
||||||
|
| Portainer audit | ❌ нет | Portainer CE без enterprise = audit log не включён |
|
||||||
|
| Snapshot diff `_snapshot/kreknin/_all` | ✅ есть | Daily snapshots показывают какой день имеет какие индексы → окно wipe'а |
|
||||||
|
| Shell history `~/.bash_history` root | ⚠️ ограниченно | Только если delete был сделан с самого VDS. Если с воркстейшна / Kibana DevTools — пусто |
|
||||||
|
|
||||||
|
## Recovery procedure
|
||||||
|
|
||||||
|
Если индексы исчезли — **snapshot restore** из `kreknin` repo. Daily snapshot pipeline (`books-vds-backup-daily-kreknin`, cron 06:00 MSK) автоматически снапшотит непустые индексы. До wipe'а snapshot контейнерит full data → restore = ~46 сек для ~800MB (I/O bound, файлы уже на диске).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -i ~/.ssh/id_ed25519_books_ops root@89.253.255.133 'bash -s' <<'EOF'
|
||||||
|
PW=$(python -c "import json; print(json.load(open('/opt/books/api/config/default.json'))['elasticsearch']['auth']['password'])")
|
||||||
|
|
||||||
|
# 1. Найти последний snapshot С полными данными
|
||||||
|
curl -sk -u "books:$PW" 'https://elasticsearch.kzntsv.site/_snapshot/kreknin/_all' \
|
||||||
|
| python -c "import json,sys; [print(s['snapshot'], s['indices']) for s in json.load(sys.stdin)['snapshots']]"
|
||||||
|
|
||||||
|
# 2. Restore (synchronous, не overwrites open indices — current state должен быть пустой)
|
||||||
|
curl -sk -u "books:$PW" -H 'Content-Type: application/json' -X POST \
|
||||||
|
'https://elasticsearch.kzntsv.site/_snapshot/kreknin/daily-<YYYY-MM-DD>/_restore?wait_for_completion=true' \
|
||||||
|
-d '{"indices":"epz,products,artmone","include_global_state":false,"include_aliases":true,"index_settings":{"number_of_replicas":0}}'
|
||||||
|
|
||||||
|
# 3. Verify counts == source
|
||||||
|
curl -sk -u "books:$PW" 'https://elasticsearch.kzntsv.site/_cat/indices?v'
|
||||||
|
|
||||||
|
# 4. Restart consumers (reset connection pool)
|
||||||
|
docker restart books-api books-task-runner books-job-scheduler
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Если все snapshot'ы посткоммитные (содержат только placeholder), fallback на reindex-from-remote из `elasticold.kzntsv.site` (см. [[../../tasks/migrate-elasticsearch-to-books-vds]] § Playbook). Source ES не выключен per migration policy.
|
||||||
|
|
||||||
|
## Preventive controls (applied 2026-05-28)
|
||||||
|
|
||||||
|
### Control #1 — ES `action.destructive_requires_name=true`
|
||||||
|
|
||||||
|
Env var на stack 33 (Portainer-managed). После применения wildcard/_all delete возвращают **400 Bad Request**:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -sk -u books:<pw> -X DELETE 'https://elasticsearch.kzntsv.site/_all'
|
||||||
|
{"error":{"type":"illegal_argument_exception","reason":"Wildcard expressions or all indices are not allowed"},"status":400}
|
||||||
|
|
||||||
|
$ curl -sk -u books:<pw> -X DELETE 'https://elasticsearch.kzntsv.site/ep*'
|
||||||
|
{"error":{"type":"illegal_argument_exception","reason":"Wildcard expressions or all indices are not allowed"},"status":400}
|
||||||
|
|
||||||
|
$ curl -sk -u books:<pw> -X DELETE 'https://elasticsearch.kzntsv.site/epz'
|
||||||
|
{"acknowledged":true} # legit by-name delete still works
|
||||||
|
```
|
||||||
|
|
||||||
|
Класс ошибки «DELETE wildcard на неправильный endpoint» теперь блокируется ES до того, как достанется до данных. Reindex по одному индексу остаётся легитимным (нужно для миграции). Полностью **физически невозможно** удалить >1 индекса одной командой.
|
||||||
|
|
||||||
|
Apply: добавить `action.destructive_requires_name: "true"` в `environment:` блок stack 33 compose, PUT через Portainer API endpoint `/api/stacks/33?endpointId=1`. ES container recreate ~10 сек, consumers переподключатся через retry.
|
||||||
|
|
||||||
|
### Control #2 — Traefik accessLog (JSON)
|
||||||
|
|
||||||
|
Без этого никакая ретроспективная диагностика невозможна. Static config в `/usr/docker/traefik/data/traefik.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
accessLog:
|
||||||
|
filePath: /letsencrypt/access.log
|
||||||
|
format: json
|
||||||
|
bufferingSize: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
`/letsencrypt/` уже bind-mounted (`./letsencrypt:/letsencrypt` в `/usr/docker/traefik/docker-compose.yml`), файл persistent. Restart container — изменения подхватились (accessLog = static config, не dynamic).
|
||||||
|
|
||||||
|
Sample entry:
|
||||||
|
```json
|
||||||
|
{"ClientHost":"...", "ClientUsername":"books", "RequestMethod":"DELETE", "RequestHost":"elasticsearch.kzntsv.site",
|
||||||
|
"RequestPath":"/_all", "RouterName":"elasticsearch@docker", "DownstreamStatus":400, ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
Будущие подозрительные запросы видны с IP, username basicAuth, methodом, путём, response status. Логирование = ~30–50 MB/day при типичной нагрузке (~1 req/sec).
|
||||||
|
|
||||||
|
## Follow-ups
|
||||||
|
|
||||||
|
- **Logrotate** на `/usr/docker/traefik/letsencrypt/access.log` — пока без него файл будет расти; не критично (disk free 63G, ressedimption ~1–2 года), но micro-task на logrotate cron имеет смысл.
|
||||||
|
- **bookva-es traefik route** — если когда-нибудь bookva-es потребует внешнего доступа, обязательно отдельный hostname (`bookva-es.kzntsv.site`) с отдельной basicAuth. **Никогда** не делиться `elasticsearch.kzntsv.site` между slovo/bookva.
|
||||||
|
- **Snapshot retention review** — сейчас 7 daily snapshots. Если бы инцидент проявился через 8 дней, daily-25.05 был бы вытеснен. Для критичных корпусов имеет смысл добавить weekly/monthly tier. Отдельная мини-задача.
|
||||||
|
- **Логирование callers без accessLog ретроспективно невозможно** — это main learning. Любой новый production endpoint должен иметь access log с момента создания.
|
||||||
|
|
||||||
|
## Cross-refs
|
||||||
|
|
||||||
|
- [[../entities/books-vds]] § Стек, ES — stack 33 inventory.
|
||||||
|
- [[../sources/books-vds-backup-daily-kreknin-2026-05-25]] — backup pipeline который сделал spasibo daily-25.05 snapshot.
|
||||||
|
- [[../../tasks/restore-elasticsearch-indices-books-vds]] — incident response trace (этой сессии).
|
||||||
|
- [[../../tasks/migrate-elasticsearch-to-books-vds]] — original migration playbook (применим для reindex-from-remote fallback).
|
||||||
@@ -24,6 +24,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
|
|||||||
- [compose-bcrypt-escape-trap](concepts/compose-bcrypt-escape-trap.md) — Docker Compose ест `$` в bcrypt hashes
|
- [compose-bcrypt-escape-trap](concepts/compose-bcrypt-escape-trap.md) — Docker Compose ест `$` в bcrypt hashes
|
||||||
- [db-tls-self-signed-via-traefik-raw-tcp](concepts/db-tls-self-signed-via-traefik-raw-tcp.md) — DB TLS через traefik raw TCP с self-signed certs
|
- [db-tls-self-signed-via-traefik-raw-tcp](concepts/db-tls-self-signed-via-traefik-raw-tcp.md) — DB TLS через traefik raw TCP с self-signed certs
|
||||||
- [docker-host-loopback-detect](concepts/docker-host-loopback-detect.md) — Docker host-loopback detection — как доказать что host.docker.internal не петля
|
- [docker-host-loopback-detect](concepts/docker-host-loopback-detect.md) — Docker host-loopback detection — как доказать что host.docker.internal не петля
|
||||||
|
- [es-destructive-delete-incident-2026-05-26](concepts/es-destructive-delete-incident-2026-05-26.md) — ES destructive delete на canonical endpoint в момент bookva cutover-prep — RCA + recovery runbook + preventive controls (`action.destructive_requires_name=true` + traefik accessLog)
|
||||||
- [future-resilient-architecture-goals](concepts/future-resilient-architecture-goals.md) — fault-tolerance roadmap placeholder (расширяется через `[resilience-roadmap-design]`)
|
- [future-resilient-architecture-goals](concepts/future-resilient-architecture-goals.md) — fault-tolerance roadmap placeholder (расширяется через `[resilience-roadmap-design]`)
|
||||||
- [hyper-backup-structure-and-recovery](concepts/hyper-backup-structure-and-recovery.md) — Hyper Backup — структура репо и стратегия восстановления
|
- [hyper-backup-structure-and-recovery](concepts/hyper-backup-structure-and-recovery.md) — Hyper Backup — структура репо и стратегия восстановления
|
||||||
- [iis-migration-2026-05-19-postmortem](concepts/iis-migration-2026-05-19-postmortem.md) — post-mortem миграции CMS на нативный IIS, 2026-05-19
|
- [iis-migration-2026-05-19-postmortem](concepts/iis-migration-2026-05-19-postmortem.md) — post-mortem миграции CMS на нативный IIS, 2026-05-19
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Append-only log of wiki operations (ingests, promotions, lints, migrations).
|
Append-only log of wiki operations (ingests, promotions, lints, migrations).
|
||||||
|
|
||||||
|
## [2026-05-28] ingest | concepts/es-destructive-delete-incident-2026-05-26 — RCA для удаления 3 user-индексов на canonical ES `elasticsearch.kzntsv.site` (books VDS stack 33) во время cutover-prep. Caller identity unrecoverable (audit log = X-Pack платный, traefik accessLog был выключен). 2 preventive controls applied + verified: ES env `action.destructive_requires_name=true` + traefik JSON accessLog. Источник: `.tasks/restore-elasticsearch-indices-books-vds.md` § Closure note.
|
||||||
|
|
||||||
## 2026-05-21
|
## 2026-05-21
|
||||||
|
|
||||||
- bootstrap: empty wiki skeleton (CLAUDE.md, index.md, log.md, overview.md, raw/README.md) committed
|
- bootstrap: empty wiki skeleton (CLAUDE.md, index.md, log.md, overview.md, raw/README.md) committed
|
||||||
|
|||||||
Reference in New Issue
Block a user