[2026-09-20] eagle: family/how-to/kraken-access.md personal/tech/docker-data-root-mount-race.md personal/tech/xray-reverse-tunnel-kraken-truenas.md
This commit is contained in:
1 parent
af122c271e
commit
7f4faf0a19
3 files changed
+181
-14
No files matched your search
@@ -22,7 +22,7 @@
|
||||
> ### Побочные дефекты, найденные при разборе
|
||||
> 1. **`/etc/fstab` содержит ДУБЛЬ** строки для `6194539b`: OMV-блок + ручная строка `UUID=6194539b-...` → systemd: `Duplicate entry in '/etc/fstab'?`.
|
||||
> 2. **`docker.service`: `RequiresMountsFor=` пусто** — корень гонки. Drop-in нужен, чтобы dockerd ждал диск.
|
||||
> 3. **RestartPolicy у всех контейнеров = `null`** (стартовали вручную `docker run` без `--restart`) → daemon-restart **не поднимет их автоматически**, нужен явный `docker start`.
|
||||
> 3. ~~RestartPolicy=null → нужен ручной `docker start`~~ ❌ **ЭТО БЫЛО ОШИБОЧНОЕ ДОПУЩЕНИЕ.** Я прочитал `RestartPolicy.Name` из `hostconfig.json` (это reflection исходного `docker run`, там `null`), но в daemon state policy **есть**. Факт: после `systemctl restart docker` все 12 контейнеров с `unless-stopped` поднялись **сами**, ручной `docker start` не потребовался. Проверять политику надо `docker inspect <name> --format '{{.HostConfig.RestartPolicy.Name}}'` (живой daemon), а не `hostconfig.json` на диске.
|
||||
>
|
||||
> ### Команды диагностики (воспроизводимые)
|
||||
> ```bash
|
||||
@@ -37,12 +37,31 @@
|
||||
> ### План восстановления (согласован 2026-09-20)
|
||||
> 1. Backup: `/etc/fstab`, `/etc/docker/daemon.json`, `/home/kraken/xray-reverse/` → `/home/kraken/backups/docker-recovery-<date>/`.
|
||||
> 2. **`sudo systemctl restart docker`** — диск уже смонтирован → daemon прочитает все 35 контейнеров.
|
||||
> 3. `docker start` по списку (RestartPolicy=null, авто-старта не будет).
|
||||
> 3. `docker start` по списку — ⚠️ **НЕ ПОТРЕБОВАЛСЯ**: оказалось, RestartPolicy в daemon state есть (`unless-stopped`), все 12 контейнеров поднялись автоматически после рестарта daemon. `hostconfig.json` на диске показывает `null` — это НЕ отражает реальную политику. Проверять через живой daemon: `docker inspect --format '{{.HostConfig.RestartPolicy.Name}}'`.
|
||||
> 4. Правка боевого: убрать дубль fstab + drop-in `RequiresMountsFor=/srv/dev-disk-by-uuid-6194539b-...` — **только с апрувом Alex**. Порядок: коммит ДО → правка → заливка → проверка Alex → коммит ПОСЛЕ.
|
||||
> 5. Проверка: `docker ps` все Up; reverse-канал `udp:reverse:0`; cloudflared tunnel.
|
||||
> 6. Версии Xray на бридже: оставить `26.4.25` + `network_mode: host` (проверенный фикс 2026-09-02) — открытый вопрос к Alex.
|
||||
>
|
||||
> **Статус: диагноз подтверждён, план согласуется. Восстановление НЕ начато.**
|
||||
> **Статус (обновлено 2026-09-20): ✅ ФИКС ПРИМЕНЁН, ПРОВЕРЕН, REBOOT-БЕЗОПАСЕН.**
|
||||
>
|
||||
> **Что сделано:**
|
||||
> 1. ✅ Backup: `/home/kraken/backups/docker-recovery-20260920-105228/` (fstab, daemon.json, xray-reverse).
|
||||
> 2. ✅ `sudo systemctl restart docker` → все 25 контейнеров загрузились, 12 поднялись автоматом (`cloudflared`, `xray-reverse-bridge` в т.ч.).
|
||||
> 3. ✅ Drop-in `/etc/systemd/system/docker.service.d/mounts.conf`:
|
||||
> ```ini
|
||||
> [Unit]
|
||||
> RequiresMountsFor=/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker-data
|
||||
> After=srv-dev\x2ddisk\x2dby\x2duuid\x2d6194539b\x2d03e0\x2d4f33\x2d986b\x2d4da09834e82f.mount
|
||||
> ```
|
||||
> Проверено: `systemctl show docker -p Requires -p After` → маунт-юнит в **Requires И After**. dockerd теперь физически не стартует раньше диска → **гонка закрыта**.
|
||||
> 4. ✅ `/etc/fstab`: убран дубль строки для `6194539b` + дубль bind-mount `/var/log/journal`. Осталось 1 вхождение (OMV-блок). Проверено грепом.
|
||||
> 5. ✅ Sanity `systemctl restart docker` после правок → `containers=25`, 11-12 Up. Критичные поднялись сами.
|
||||
>
|
||||
> ⚠️ `hermes-kraken` — `Restarting (1)` (crash-loop, **не связан** с этой задачей, был до правок).
|
||||
>
|
||||
> **Питфолл (опровергнут):** `RestartPolicy` в `hostconfig.json` = `null`, но в daemon state policy есть → при рестарте daemon контейнеры **поднимаются сами**. Не пугаться `null`, не нужен ручной `docker start`.
|
||||
>
|
||||
> **Урок на будущее:** USB-диск на Kraken поднимается ~130 с. Любой сервис, зависящий от `docker-data` на этом диске, ОБЯЗАН иметь `RequiresMountsFor`. Теперь это обеспечено для docker.service.
|
||||
|
||||
> # 🔴 2026-09-15 — KRAKEN НЕДОСТУПЕН ПО ВСЕМ ПУТЯМ (проверено с Mac)
|
||||
>
|
||||
@@ -101,19 +120,26 @@ EP=3 # endpoint ID на Кракене = 3, не 1!
|
||||
|
||||
## Reverse Xray bridge (развёрнут 2026-09-01)
|
||||
|
||||
- Контейнер **`xray-reverse-bridge`** в `/home/kraken/xray-reverse/` (compose + `config.json`, образ `teddysun/xray:latest`, Xray 26.7.28 ARM64).
|
||||
- Outbound VLESS+REALITY TCP → `mallexxx.duckdns.org:12346` (TrueNAS через OpenWrt DNAT), reverse tag `reverse-in`, egress freedom.
|
||||
- ⛔ **End-to-end payload НЕ работает** (bridge принимает reverse-канал, но данные от portal до bridge не доходят). Детали: [[tech/xray-reverse-tunnel-kraken-truenas]].
|
||||
- Контейнер **`xray-reverse-bridge`** в `/home/kraken/xray-reverse/` (compose + `config.json`), образ **`teddysun/xray:26.4.25`** + `network_mode: host` (проверенный фикс 2026-09-02).
|
||||
- Outbound VLESS+REALITY TCP → `mallexxx.duckdns.org:12346` (TrueNAS через OpenWrt DNAT), reverse tag `reverse-in`, egress freedom с `finalRules:[{action:allow}]`.
|
||||
- ✅ **End-to-end payload работает** (верифицировано 2026-09-02: SOCKS `12345` → egress IP Kraken `92.62.70.41`). Полная история диагностики (регрессии #6612/#6242, docker bridge/NAT) — в [[tech/xray-reverse-tunnel-kraken-truenas]].
|
||||
- ⚠️ 2026-09-15…20 контейнер был **не запущен** (гонка docker↔mount, см. шапку) — это единственная причина «туннель down». После рестарта daemon — снова Up.
|
||||
|
||||
## Docker контейнеры (обновлено 2026-09-20)
|
||||
## Docker контейнеры (обновлено 2026-09-20, после фикса)
|
||||
|
||||
⚠️ **На 2026-09-20 контейнеры ЦЕЛЫ на диске (35 шт., metadata 4.7G), но daemon их не видит** из-за гонки docker↔mount (см. шапку заметки). `docker ps -a` пусто, `docker info` = 0. Не паниковать — metadata на месте, лечится `systemctl restart docker`.
|
||||
✅ **daemon видит все контейнеры. `containers=25`, 12 Up** (после восстановления гонки docker↔mount, см. шапку). Все 35 metadata-записей в `docker-data/containers/` целы.
|
||||
|
||||
| Имя | Заметки |
|
||||
|-----|---------|
|
||||
| xray-reverse-bridge | развёрнут 2026-09-01; образ `teddysun/xray:26.4.25`, `network_mode: host` (фикс 2026-09-02). Metadata цела ✅ |
|
||||
| cloudflared | образ `cloudflare/cloudflared:latest`, `network_mode: host`, remote-managed по TUNNEL_TOKEN. Metadata цела ✅ |
|
||||
| flaresolverr / hermes-kraken / homeassistant / jellyfin / portainer / prowlarr / radarr / rclone / sonarr / transmission / watchtower / timemachine | metadata цела ✅ (все 35 в `docker-data/containers/`). **RestartPolicy=null → daemon-restart их не поднимет, нужен `docker start`** |
|
||||
| Имя | Статус 2026-09-20 | Заметки |
|
||||
|-----|---------|---------|
|
||||
| xray-reverse-bridge | ✅ Up | образ `teddysun/xray:26.4.25`, `network_mode: host` (фикс 2026-09-02) |
|
||||
| cloudflared | ✅ Up | `cloudflare/cloudflared:latest`, `network_mode: host`, remote-managed по TUNNEL_TOKEN |
|
||||
| jellyfin / sonarr / radarr / prowlarr | ✅ Up | |
|
||||
| portainer / transmission / rclone / watchtower / flaresolverr | ✅ Up | |
|
||||
| hermes-kraken | ⚠️ `Restarting (1)` — crash-loop | **не связан** с гонкой docker↔mount, вёл себя так и до правок |
|
||||
| homeassistant | ⏹ `Exited (128)` 2 месяца назад | не поднимался, отдельная задача |
|
||||
| timemachine + alpine-* (6 шт.) | ⏹ Exited (0) | одноразовые/диагностические, норма |
|
||||
|
||||
> **Питфолл про RestartPolicy (проверено):** `hostconfig.json` на диске показывает `RestartPolicy.Name = null`, но в daemon state политика `unless-stopped` есть → контейнеры поднимаются сами. Не пугаться `null`, не нужен ручной `docker start`. Проверять живой daemon: `docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' <name>`.
|
||||
|
||||
## Cloudflared туннель — публичные маршруты (состояние на 2026-09-04)
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Docker data-root ↔ mount race — диагностика
|
||||
created: '2026-09-20'
|
||||
type: tech
|
||||
namespace: personal
|
||||
tags:
|
||||
- docker
|
||||
- systemd
|
||||
- mount
|
||||
- kraken
|
||||
- diagnostics
|
||||
confidence: high
|
||||
related:
|
||||
- '[[family/how-to/kraken-access]]'
|
||||
- '[[personal/tech/xray-reverse-tunnel-kraken-truenas]]'
|
||||
---
|
||||
|
||||
# Docker data-root ↔ mount race
|
||||
|
||||
> Обнаружено 2026-09-20 на Kraken (RPi5/OMV, USB-HDD). Зеркало скилла
|
||||
> `docker-data-root-mount-race` (категория `docker-service-administration`).
|
||||
|
||||
## Триггер
|
||||
|
||||
Хост, у которого Docker `data-root` лежит на **диске, монтирующемся поздно**
|
||||
(USB-HDD, OMV `/srv/dev-disk-by-uuid-*`, RPi). Признаки:
|
||||
|
||||
- `docker ps -a` **пусто**, но контейнеры раньше были.
|
||||
- `docker info` → `Containers=0`, при этом диск с `docker-data` смонтирован.
|
||||
- После reboot сервисы в контейнерах (cloudflared, reverse-proxy bridge, jellyfin…)
|
||||
молча «down» — но контейнеры **не удалялись**.
|
||||
- journal: `Duplicate entry in '/etc/fstab'?` от `systemd-fstab-generator`.
|
||||
|
||||
## Механизм
|
||||
|
||||
`dockerd` стартует **раньше**, чем монтируется диск с его `data-root`. Открывает
|
||||
путь, видит пусто → инициализирует **пустое** состояние (0 контейнеров). Через
|
||||
секунды диск монтируется поверх точки, реальный `containers/` появляется — но
|
||||
dockerd уже разобрал состояние и **не перечитывает**.
|
||||
|
||||
**Ничего не потеряно.** Metadata цела на диске, daemon её просто не видит.
|
||||
|
||||
Причина: у `docker.service` **нет `RequiresMountsFor=`** на маунт-юнит диска.
|
||||
|
||||
## Диагностика
|
||||
|
||||
```bash
|
||||
# 1) daemon думает, что пусто
|
||||
docker ps -a --format "{{.Names}}\t{{.Status}}" # пусто
|
||||
docker info --format "{{.DockerRootDir}} | containers={{.Containers}}"
|
||||
|
||||
# 2) но metadata НА ДИСКЕ (нужен root; каталог 711 root:root)
|
||||
sudo ls -la /srv/dev-disk-by-uuid-<DISK>/docker-data/
|
||||
sudo ls /srv/dev-disk-by-uuid-<DISK>/docker-data/containers/ | wc -l # >0 = целы
|
||||
|
||||
# 3) подтвердить гонку: dockerd стартовал РАНЬШЕ диска
|
||||
ps -o pid,lstart,cmd -p "$(pgrep -x dockerd)" # истинное время старта
|
||||
uptime -s # время boot
|
||||
sudo dmesg | grep -iE "EXT4-fs \(sd[a-z]" | tail -5 # когда ФС смонтировалась
|
||||
findmnt /srv/dev-disk-by-uuid-<DISK>
|
||||
|
||||
# 4) подтвердить отсутствие зависимости (корень)
|
||||
systemctl show docker -p RequiresMountsFor # пусто = нет порядка
|
||||
grep -c "<DISK-UUID>" /etc/fstab # 2 = дубль
|
||||
```
|
||||
|
||||
**ПИТФОЛЛ — не то поле времени.** `systemctl show docker -p ExecMainStartTimestamp`
|
||||
может вернуть **stale** значение (прошлая активация). Реальное время —
|
||||
`ps -o lstart -p $(pgrep -x dockerd)`.
|
||||
|
||||
**ПИТФОЛЛ — не тот источник RestartPolicy.** `RestartPolicy.Name` из
|
||||
`docker-data/containers/<id>/hostconfig.json` показывает то, что передал
|
||||
*исходный* `docker run` — часто `null`, даже когда политика есть в живом daemon
|
||||
state. НЕ делать вывод «контейнеры не поднимутся сами». Проверять живой daemon:
|
||||
```bash
|
||||
docker inspect <name> --format '{{.HostConfig.RestartPolicy.Name}}'
|
||||
```
|
||||
Если `unless-stopped` — контейнеры **сами** вернутся при рестарте daemon, ручной
|
||||
`docker start` не нужен.
|
||||
|
||||
## Фикс
|
||||
|
||||
### Немедленный (вернуть сервисы — диск уже смонтирован)
|
||||
```bash
|
||||
sudo systemctl restart docker
|
||||
docker info --format "containers={{.Containers}} running={{.ContainersRunning}}"
|
||||
```
|
||||
Контейнеры с `restart: unless-stopped` поднимаются сами.
|
||||
|
||||
### Постоянный (сделать reboot безопасным)
|
||||
1. Бэкап: `/etc/fstab`, `/etc/docker/daemon.json`, compose-каталоги сервисов →
|
||||
датированная папка.
|
||||
2. Drop-in, чтобы systemd не стартовал dockerd раньше диска:
|
||||
`/etc/systemd/system/docker.service.d/mounts.conf`
|
||||
```ini
|
||||
[Unit]
|
||||
RequiresMountsFor=/srv/dev-disk-by-uuid-<DISK>/docker-data
|
||||
After=srv-dev\x2ddisk\x2dby\x2duuid\x2d<DISK>.mount
|
||||
```
|
||||
Имя `.mount` юнита = путь, где `/` → `-`, а каждый литеральный `-` escaped как
|
||||
`\x2d` (systemd escaping).
|
||||
3. `sudo systemctl daemon-reload`
|
||||
4. Убрать **дубль** строки монтирования того же диска из `/etc/fstab` (systemd
|
||||
отказывается от дублей; OMV часто добавляет свою рядом с ручной `UUID=...`).
|
||||
5. Проверка:
|
||||
```bash
|
||||
findmnt --verify --fstab # rc=0
|
||||
systemctl show docker -p Requires -p After # маунт-юнит в ОБОИХ
|
||||
sudo systemctl restart docker # sanity: контейнеры перезагрузятся
|
||||
```
|
||||
|
||||
## Нельзя
|
||||
|
||||
- Не предполагать потерю данных, если `docker ps` пусто, но диск смонтирован —
|
||||
**сначала посчитать `docker-data/containers/`**. `Containers=0` в `docker info`
|
||||
не доказательство удаления.
|
||||
- Не `rm -rf` «устаревший» docker-data — это и есть данные.
|
||||
- Не править `/etc/fstab` через sed; писать полный файл локально, валидировать
|
||||
`findmnt --verify --fstab`, затем копировать на место.
|
||||
|
||||
## Чеклист после фикса
|
||||
|
||||
- [ ] `docker info` count == число каталогов в `docker-data/containers/`.
|
||||
- [ ] `systemctl show docker -p RequiresMountsFor` **не пусто** и указывает на `docker-data`.
|
||||
- [ ] `grep -c "<DISK-UUID>" /etc/fstab` = `1`.
|
||||
- [ ] После тестового `systemctl restart docker` критичные контейнеры `Up`.
|
||||
- [ ] При следующем плановом reboot: `docker ps -q | wc -l` возвращает ожидаемое число, не `0`.
|
||||
|
||||
## Применение на Kraken (2026-09-20)
|
||||
|
||||
Полный кейс: [[family/how-to/kraken-access]] и
|
||||
[[personal/tech/xray-reverse-tunnel-kraken-truenas]].
|
||||
Кратко: boot 17:29:17, dockerd 17:31:01, диск 17:31:27 (на 26 с позже) → 0
|
||||
контейнеров. Фикс: drop-in `RequiresMountsFor=/srv/.../6194539b.../docker-data`
|
||||
+ чистка fstab. Бэкап: `/home/kraken/backups/docker-recovery-20260920-105228/`.
|
||||
@@ -45,7 +45,13 @@ verified_fix_2026_09_02: >-
|
||||
>
|
||||
> **Что это НЕ:** это **не** регрессия #6242, **не** REALITY mismatch, **не** `network_mode` — все прежние гипотезы по 2026-09-15 снимаются. Проверенный фикс 2026-09-02 (`26.4.25` + `network_mode: host`) остаётся в силе и не трогается.
|
||||
>
|
||||
> **План:** `systemctl restart docker` (диск уже смонтирован) + `docker start` по списку (RestartPolicy=null) + drop-in `RequiresMountsFor` + чистка fstab. Полный план и команды — [[family/how-to/kraken-access]], секция «2026-09-20». **Статус: восстановление НЕ начато, ждёт апрува Alex.**
|
||||
> **План и статус (обновлено 2026-09-20): ✅ ВЫПОЛНЕНО И ПРОВЕРЕНО.**
|
||||
> 1. ✅ Backup → `/home/kraken/backups/docker-recovery-20260920-105228/`.
|
||||
> 2. ✅ `systemctl restart docker` → 25 контейнеров загружены, 12 Up (в т.ч. `xray-reverse-bridge`).
|
||||
> 3. ✅ Drop-in `/etc/systemd/system/docker.service.d/mounts.conf` с `RequiresMountsFor=` + `After=` маунт-юнита → проверено `systemctl show docker -p Requires -p After` (маунт в обоих). **Гонка закрыта, reboot-безопасно.**
|
||||
> 4. ✅ fstab: убран дубль строки `6194539b` (+ дубль bind-mount journal).
|
||||
> ⚠️ `docker start` вручную **не потребовался**: RestartPolicy в daemon state есть (`unless-stopped`), контейнеры поднялись сами. Ранее записанное «RestartPolicy=null» — ошибка чтения `hostconfig.json` (он не отражает живую политику).
|
||||
> Полный план и команды — [[family/how-to/kraken-access]], секция «2026-09-20».
|
||||
>
|
||||
> **Урок диагностики:** если portal пишет `non existing outTag: reverse-out` и «ноль соединений на 12346» — **сначала проверять, жив ли контейнер `xray-reverse-bridge` на Kraken** (`docker ps`), а не конфиг. 2026-09-15 этот шаг был пропущен: записали «Kraken недоступен → ждать», хотя машина была жива по LAN (`192.168.1.15`), а не запущен был только docker-контейнер бриджа.
|
||||
|
||||
|
||||
Reference in new issue
Block a user