Files
obsidian-vault/personal/tech/skill-docker-data-root-mount-race.md
T

104 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Skill mirror: `docker-data-root-mount-race`
> Зеркало скилла `docker-data-root-mount-race` (категория `docker-service-administration`) для поиска по vault.
> **Создано:** 2026-09-20. **Источник:** Kraken mount-race инцидент.
> **Связанные:** [[personal/tech/docker-data-root-mount-race]] (полный разбор), [[personal/tech/jellyfin-db-schema-mismatch]], [[family/plans/kraken-backup-to-truenas]]
## Когда применять
Хост, у которого Docker `data-root` лежит на **поздно монтирующемся диске** (USB HDD, OMV `/srv/dev-disk-by-uuid-*`, RPi + внешний диск):
- `docker ps -a` **пусто**, хотя контейнеры были.
- `docker info` → `Containers=0` при смонтированном диске.
- После reboot сервисы в контейнерах «молча down» (туннели, reverse-proxy, jellyfin) — но не удалены.
- Journal: `Duplicate entry in '/etc/fstab'?`
## Механизм
`dockerd` стартует **раньше**, чем монтируется диск с `data-root` → видит пустой каталог → загружает пустое состояние (0 контейнеров). Диск монтируется позже, но daemon **не перечитывает**. Данные целы.
Причина: `docker.service` без `RequiresMountsFor=`.
## Диагностика
```bash
docker ps -a --format "{{.Names}}\t{{.Status}}" # пусто
docker info --format "{{.DockerRootDir}} | containers={{.Containers}}"
sudo ls <data-root>/containers/ | wc -l # >0 = целы
ps -o pid,lstart,cmd -p "$(pgrep -x dockerd)" # РЕАЛЬНОЕ время старта
uptime -s; sudo dmesg | grep -iE "EXT4-fs \(sd[a-z]" | tail -5
systemctl show docker -p RequiresMountsFor # пусто = гонка возможна
grep -c "<DISK-UUID>" /etc/fstab # 2 = дубль
```
**Питфоллы диагностики:**
- `systemctl show docker -p ExecMainStartTimestamp` даёт **stale** время → только `ps -o lstart`.
- `RestartPolicy.Name` в `hostconfig.json` = `null`, но в live-состоянии политика есть. Проверять `docker inspect <name> --format '{{.HostConfig.RestartPolicy.Name}}'`. При `unless-stopped` контейнеры вернутся сами, ручной `docker start` не нужен.
## Фикс
**Немедленно:** `sudo systemctl restart docker` (диск уже смонтирован).
**Постоянно:** drop-in `/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
```
+ `systemctl daemon-reload`, + убрать дубль строки из `/etc/fstab`.
Проверка: `systemctl show docker -p Requires -p After` → маунт-юнит в **обоих**.
## Подтверждение реальным reboot
`systemctl restart docker` — **НЕ** тот же тест, что reboot (диск уже смонтирован). Проверка после реального выключения/включения 2026-09-20: все 12 контейнеров поднялись сами.
```bash
ssh kraken 'docker ps -q | wc -l' # ожидаем ~12, НЕ 0
```
## 🔴 «Сервис down после reboot» — ДВЕ разные причины, не путать
| Наблюдение | Причина | Где смотреть |
|---|---|---|
| `docker ps` **пусто** (0) | mount race | этот скилл |
| Контейнер **Up**, но 5xx / `unhealthy` | своя ошибка приложения | лог внутри контейнера |
Реальный пример путаницы: после reboot `jellyfin` был **Up, но 503** — из-за **несовпадения версий** (образ старше схемы БД), НЕ из-за монтирования. См. [[personal/tech/jellyfin-db-schema-mismatch]].
**Правило:** контейнер Up → mount race ни при чём, читать свой лог приложения.
**Питфолл — логи не в stdout.** `docker logs` может быть пуст, если приложение пишет в файл внутри volume (jellyfin: `/config/log/log_YYYYMMDD.log`). Читать файл; фильтровать старые ошибки по `StartedAt`.
**Питфолл — дрейф версий у `latest`-тегов.** `docker inspect image:latest` показывает digest, **откуда скачали**, а не текущий `latest`. Локальный `latest` может быть на месяцы старше. Проверять версию внутри образа: `docker run --rm --entrypoint sh <image>:latest -c '<bin> --version'`. Если версия БД выше версии сервера → **пересоздать контейнер на верном образе**, НЕ даунгрейдить данные.
## Смежная ловушка: смешанные владельцы под data-root
Бэкап/tar дерева сервисов на том же диске: часть каталогов `root:root`, часть `1000:1000` → обычный юзер получает `Permission denied` и **молча частичный архив**.
```bash
sudo ls -ldn <data-root>/../docker/*/ | awk '{print $3, $4, $NF}' # владельцы
```
Если `sudo -n true` проходит — архивировать под `sudo -n`, а на приёмнике ставить владельца: `rsync --chown=<user>:<group>`.
**Питфолл — долгий `chown -R`/tar умирает с SSH-сессией.** Запускать отсоединённо:
```bash
sudo systemd-run --unit=<name> --collect --property=Type=oneshot chown -R 1000:1000 <path>
```
**Питфолл — `Restarting (1)` + `mkdir: Permission denied`** на data-каталоге: владелец на хосте ≠ UID контейнера. `docker inspect <name> --format '{{.Config.User}}'` → `chown <uid>:<gid>`. Первые `mkdir`-ошибки могут быть **буферизованным остатком** от прошлого запуска — убедиться, что контейнер реально держится `Up`.
## Никогда
- Не считать `docker ps` пустым признаком потери данных — сначала проверить `containers/` count.
- Не `rm -rf` «устаревший» docker-data — это реальные данные.
- Не править `/etc/fstab` через sed — писать файл локально, `findmnt --verify --fstab`, затем копировать.
## Чеклист после фикса
- [ ] `docker info` count == число каталогов в `containers/`
- [ ] `RequiresMountsFor` непусто и указывает на `docker-data`
- [ ] `grep -c "<UUID>" /etc/fstab` == 1
- [ ] После `systemctl restart docker` критичные контейнеры `Up`
- [ ] После реального reboot: `docker ps -q | wc -l` == ожидаемое, не 0