docs: troubleshooting guide + production notes

- docs/troubleshooting.md — 13 секций с реальными grабельками которые мы
  прошли: cudaIpcOpenEventHandle invalid device context (pid namespace),
  s6-overlay vs pid share, scale_cuda missing (cuda-llvm + stdbit.h glibc 2.36),
  libcuframes not found install paths, ffbuild/ missing source, GMP no working
  compiler (long-long reliability), zlib.net deprecated URL, RTSP/RTP UDP
  docker NAT, gitea actions Node version
- docs/architecture.md — Appendix A "Production deployment notes" с реальными
  observations после 24h+ run: что подтвердилось, что доработали, что не учли
- docs/requirements.md — production deployment matrix + Docker namespace
  requirements таблица (cross-container CUDA IPC требует 5 условий)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 00:37:13 +01:00
parent 022a198c33
commit fbe1d18c39
3 changed files with 440 additions and 1 deletions
+81
View File
@@ -423,3 +423,84 @@ mosaic + RTSP-server. После v1 cuframes:
3. После Phase 0 — review результатов, корректировка дизайна (если CUDA IPC
повёл себя не как ожидали)
4. Phase 1+ по плану
---
# Appendix A — Production deployment notes (post-v0.1.0)
Реальные наблюдения после первого production deployment (Frigate + cctv-processor
на RTX 5090, 24h+ uptime). Обновляется по мере накопления опыта.
## Что подтвердилось из изначального дизайна
- **CUDA IPC handshake через cudaIpcEventHandle_t работает стабильно** — нет
ни одного torn frame за 24+ часов на 2 consumer'ах.
- **EXTERNAL ownership** (publisher передаёт свои pre-allocated CUDA pointers)
необходим для FFmpeg-based publisher — иначе нужен extra cudaMemcpy из FFmpeg's
hwframe pool в library-managed pool.
- **Unix socket handshake** ОК — простой, debug'абельный (`socat` для inspect).
- **POSIX shm для header + atomic seq counters** — race-free на reader side.
## Что пришлось доработать в v0.1.0 vs initial design
- **CMake install rules** изначально не предусмотрены. Downstream проекты
делали `cmake --install` → пустой prefix. Fix: `install(TARGETS ...)` +
`install(DIRECTORY include/cuframes ...)`. Лессон — install rules должны
быть в day 1.
- **Variable HINTS в find_library**: пользователи делают install в разные
prefix'ы. HINTS для downstream `find_library(cuframes)` должны включать
`$PREFIX/lib`, `$PREFIX/lib64`, и `build-dir/libcuframes/` для local-dev.
## Что не учли в дизайне (открытые grабли — см. troubleshooting.md)
### Cross-container CUDA IPC требует **shared pid + ipc namespace**
`cudaIpcOpenEventHandle` validates IPC peer через `/proc/<pid>/...`. Если
consumer container не в same PID namespace что publisher — fail с
`invalid device context`.
Это **incompatible** с s6-overlay-based containers (linuxserver.io stack,
Frigate), требующими PID 1 для self. Workaround: только `ipc:` shared,
accept race window (works на Frigate в практике потому что подключается
первым после publisher restart). **Real fix planned v0.2**: socket-based
context validation вместо `/proc` reliance.
### Publisher-side resize нужен для consumers без cuda-llvm
Большинство downstream FFmpeg builds — без `--enable-cuda-llvm` (на платформах
с glibc < 2.38 эта опция не собирается, нужен `stdbit.h`). Без cuda-llvm нет
`scale_cuda` filter. Consumer вынужден CPU-resize либо отключать hwaccel.
**Fix planned v0.2**: publisher принимает `--scale=WxH` и делает GPU resize
до publish. Consumer получает уже scaled frames, scale_cuda не нужен.
### Encoded packet sharing — отсутствует в v0.1
cuframes v0.1 раздаёт **только decoded** NV12. Для `record` use case
(`-c:v copy` mux без decode) consumer всё ещё открывает свой RTSP — лимит
камеры на concurrent streams (4-5 у Dahua) hit'ится.
**v0.2 spec**: parallel encoded-packets ring + `cuframes_packets://`
demuxer. См. [issue #2](https://git.goldix.org/gx/cuframes/issues/2).
## Production setup (gold path)
```
┌─► Frigate (FFmpeg cuframes:// demuxer) → detect
Camera RTSP ─► publisher ──┤
(1× NVDEC) └─► cctv-processor (CuframesSource C++ API) → motion+RTSP-encode→TV
```
| Метрика | Without cuframes (baseline) | С cuframes v0.1 |
|---|---|---|
| NVDEC operations на parking-камеру | 2 (Frigate detect + cctv detect) | **1** (publisher) |
| VRAM extra cost | 0 (каждый своё) | ~3 MB (ring 6×460KB sub-stream) |
| RTSP camera load | 2 streams | **1** stream |
| Uptime (verified) | n/a | 24h+ без drops |
## См. также
- [docs/troubleshooting.md](troubleshooting.md) — конкретные грабли + fixes
- [BENCHMARKS.md](../BENCHMARKS.md) — измерения
- [docs/integrations/frigate.md](integrations/frigate.md) — guide для Frigate
- [ROADMAP.md](../ROADMAP.md) — v0.2/v0.3/v1.0
+33 -1
View File
@@ -181,4 +181,36 @@ Phase 0 PoC (2026-05-14):
- **Docker:** 29.1.3 с nvidia-container-runtime
- **Container:** Ubuntu 24.04 + GCC 13 + Clang + CMake 3.28 + Ninja
Дополнительный target matrix будет в CI после Phase 4.
## Production deployment matrix (v0.1.0)
Что подтверждено в 24h+ production run:
| Слой | Версия | Comments |
|---|---|---|
| NVIDIA driver | 555+ | минимум для CUDA 12 user runtime |
| CUDA toolkit (build) | 12.4 (Debian 12 / Ubuntu 22.04) либо 13.0 (Ubuntu 24.04) | toolkit для builder image, не runtime |
| GPU | RTX 5090 (sm_120) | проверено; раньше — sm_75 минимум |
| Builder OS | Ubuntu 22.04 (glibc 2.35) | forward-compat с Debian 12 runtime |
| Runtime OS (Frigate) | Debian 12 (glibc 2.36) | base image Frigate `stable-tensorrt` |
| Runtime OS (cctv-backend) | Ubuntu 22.04 либо Debian 12 | matched с builder |
| Docker | 29.1.x | для buildx |
| docker buildx | v0.34.0+ | `apt install docker-buildx-plugin` либо manual install из GH releases |
| nvidia-container-toolkit | 1.14+ | для `runtime: nvidia` |
## Docker namespace requirements (cross-container CUDA IPC)
Для consumer'а который подключается к publisher'у в **другом** container'е:
| Что нужно | Как настроить |
|---|---|
| `/dev/shm` shared (header + ring metadata) | `ipc: container:<publisher>` либо `ipc: shareable` у publisher + same у consumer |
| `/proc` visibility (CUDA IPC peer validation) | `pid: container:<publisher>` |
| `/run/cuframes/*.sock` доступен | volume mount `cuframes_sock:/run/cuframes:ro` |
| GPU access | `runtime: nvidia` + `NVIDIA_VISIBLE_DEVICES=all` |
| Socket file permissions | `user: root` либо chmod в publisher |
**Все 5** должны быть выполнены. Подробности — [docs/troubleshooting.md](troubleshooting.md).
**Special case: s6-overlay containers (Frigate, linuxserver.io stack)**: `pid:` share **невозможен** — s6-overlay требует PID 1. Workaround: только `ipc:` + race window connect. См. troubleshooting.
Дополнительный target matrix будет в CI после Phase 4 (см. [ROADMAP.md](../ROADMAP.md)).
+326
View File
@@ -0,0 +1,326 @@
# Troubleshooting
Реальные грабли которые мы прошли при первой production deployment'е cuframes
(Frigate + custom C++ processor + custom Python). Документировано чтобы вы их
не повторяли.
## Содержание
- [Runtime / CUDA IPC](#runtime--cuda-ipc)
- [`cudaIpcOpenEventHandle: invalid device context`](#cudaipcopeneventhandle-invalid-device-context)
- [Subscriber timeout (`cuframes_subscriber_create: timeout`)](#subscriber-timeout)
- [Permission denied на socket](#permission-denied-на-socket)
- [Frigate-specific](#frigate-specific)
- [`s6-overlay-suexec: fatal: can only run as pid 1`](#s6-overlay-suexec-fatal-can-only-run-as-pid-1)
- [`No such filter: 'scale_cuda'`](#no-such-filter-scale_cuda)
- [Missing dynamic .so после ffmpeg replace](#missing-dynamic-so-после-ffmpeg-replace)
- [Build / FFmpeg patch](#build--ffmpeg-patch)
- [`libcuframes not found` при configure](#libcuframes-not-found-при-configure)
- [`ffbuild/library.mak: No such file`](#ffbuildlibrarymak-no-such-file)
- [`could not find a working compiler` (GMP)](#could-not-find-a-working-compiler-gmp)
- [`zlib: download failed` в crosstool-NG](#zlib-download-failed-в-crosstool-ng)
- [`stdbit.h: No such file` при `--enable-cuda-llvm`](#stdbith-no-such-file-при---enable-cuda-llvm)
- [Docker / IPC](#docker--ipc)
- [Cross-container CUDA IPC: ipc + pid namespace share](#cross-container-cuda-ipc-ipc--pid-namespace-share)
- [Buildx container driver не видит host images](#buildx-container-driver-не-видит-host-images)
- [Networking / RTSP](#networking--rtsp)
- [RTSP/RTP UDP не доходит до клиента (docker NAT)](#rtsprtp-udp-не-доходит-до-клиента-docker-nat)
- [`Nonmatching transport in server reply`](#nonmatching-transport-in-server-reply)
- [Gitea Actions / CI](#gitea-actions--ci)
- [`node: executable file not found`](#node-executable-file-not-found)
- [`SyntaxError: Unexpected token '{'` (Node 12)](#syntaxerror-unexpected-token--node-12)
---
## Runtime / CUDA IPC
### `cudaIpcOpenEventHandle: invalid device context`
**Симптом**: subscriber сразу после `cuframes_subscriber_create` падает с этой ошибкой.
**Причина**: CUDA driver проверяет IPC peer через `/proc/<pid>/...`. Если процесс publisher'а **не виден** в PID namespace consumer'а — context считается невалидным.
**Fix**: shared PID namespace.
Docker:
```yaml
consumer:
ipc: "container:<publisher>" # shared /dev/shm
pid: "container:<publisher>" # ← вот это критично, без него fail
```
Host process: запуск consumer'а на host'е (либо publisher'а на host'е тоже) — same default namespace.
**Caveat**: если consumer image использует s6-overlay (Frigate, linuxserver.io
images) — `pid: container:` несовместим (см. [соответствующую секцию](#s6-overlay-suexec-fatal-can-only-run-as-pid-1)).
### Subscriber timeout
**Симптом**: `cuframes_subscriber_create: timeout` без других ошибок.
**Причины** (в порядке вероятности):
1. `/run/cuframes/<key>.sock` не виден consumer'у — забыли volume-mount
2. `/run/cuframes` смонтирован, но publisher ещё не успел создать socket — увеличить `connect_timeout_ms`
3. Publisher запущен, socket есть, но **permission denied** — см. ниже
### Permission denied на socket
**Симптом**: socket виден через `ls -la /run/cuframes/`, owner `root`. Consumer process — non-root user → не может `connect()`.
**Fix**:
- Запустить consumer как root: `user: root` в compose
- Либо изменить permissions socket после создания (publisher delegation) — TBD в v0.2
---
## Frigate-specific
### `s6-overlay-suexec: fatal: can only run as pid 1`
**Симптом**: container Frigate'а в restart loop, в logs только эта ошибка.
**Причина**: `pid: container:<publisher>` сделал Frigate not-PID-1 в shared namespace. s6-overlay v3 strictly требует PID 1 для proper signal handling/zombie reaping.
**Fix**: убрать `pid: container:` для Frigate. Только `ipc: container:` shared.
**Trade-off**: без shared pid некоторые edge cases CUDA IPC ломаются (см. [соответствующую секцию](#cudaipcopeneventhandle-invalid-device-context)). Frigate **на практике** работает потому что подключается до того как CUDA driver проверяет peer (race window race), но если publisher restart'нётся посередине — Frigate'у не удастся пере-подключиться без перезапуска.
**Real fix** (planned v0.2): encoded packet sharing — Frigate detect получает кадры через decoded path (work-around), record получает encoded через socket-based protocol который **не** требует cudaIpcOpenEventHandle.
### `No such filter: 'scale_cuda'`
**Симптом**: Frigate ffmpeg subprocess падает с этой ошибкой в `AVFilterGraph`.
**Причина**: наш patched FFmpeg собран без `--enable-cuda-llvm` (см. [stdbit.h grабля](#stdbith-no-such-file-при---enable-cuda-llvm)). Без cuda-llvm в FFmpeg нет CUDA filters (scale_cuda, overlay_cuda).
**Fix**: в Frigate config.yml явно отключи hwaccel cuda:
```yaml
ffmpeg:
hwaccel_args: [] # CPU scale вместо scale_cuda
```
Cost: 5-10% CPU per FHD25 камера. **Real fix** (v0.2): publisher-side resize в cuframes сам.
### Missing dynamic .so после ffmpeg replace
**Симптом**: после `docker cp` patched ffmpeg в Frigate container — `ldd ffmpeg`
показывает `libharfbuzz.so.0 => not found`, `libfribidi.so.0 => not found`, …
~20 missing .so.
**Причина**: Frigate's bundled ffmpeg **статически слинкован** (NickM-27/FFmpeg-Builds
делает full static build). Все 30+ deps встроены в один binary. Frigate runtime
image **не имеет** этих .so packages installed (ему не надо — bundled ffmpeg
self-contained).
Наш custom ffmpeg — **dynamic linked** (apt deps). Нужны .so на target.
**Fix**: либо
- `apt install` missing libs в Frigate (additive image modification):
```bash
apt install libharfbuzz0b libfribidi0 librist4 libsrt1.5-openssl libssh-4 \
libvpx7 libwebpmux3 libwebp7 libdav1d6 libaom3 libmp3lame0 \
libsvtav1enc1 libtheora0 libvorbis0a libvorbisenc2 \
libx264-164 libx265-199 libopus0
```
- Либо строить наш ffmpeg static (sources from NickM-27 pipeline) — complex
(см. [zlib download / GMP compiler граблю](#zlib-download-failed-в-crosstool-ng))
Best practice: создать `Dockerfile.frigate` overlay поверх Frigate image,
который добавляет deps и копирует ffmpeg. Запечь в image, не in-place patch.
---
## Build / FFmpeg patch
### `libcuframes not found` при configure
**Симптом**: FFmpeg configure (с `--enable-libcuframes`) fails с этой ошибкой
из `enabled libcuframes && require libcuframes ...`. config.log показывает
`fatal error: cuframes/cuframes.h: No such file or directory`.
**Причины**:
1. **CMake install rules отсутствовали** в libcuframes (early commits до 601806a).
`cmake --install` создавал пустой prefix. Fix: обновить cuframes до ≥ 601806a.
2. **Wrong HINTS в find_library**: твой проект ищет в `${CUFRAMES_ROOT}/build/...`
но install layout кладёт в `${CUFRAMES_ROOT}/lib`. Добавь оба пути в HINTS.
3. **`rm -f libcuframes.so*`** удалил .so но **.a** file называется
`libcuframes_static.a` (не `libcuframes.a`) → linker не находит `-lcuframes`.
Fix: либо не удаляй .so, либо переименуй .a при install.
### `ffbuild/library.mak: No such file`
**Симптом**: configure FFmpeg success, но `make` падает сразу:
`Makefile:123: ffbuild/library.mak: No such file or directory`.
**Причина**: вы сделали ваш fork FFmpeg через snapshot (не git clone), и **случайно
исключили `ffbuild/`** в rsync. Это **source files** FFmpeg, не build artifacts.
**Fix**: убедись что `ffbuild/` есть в твоём FFmpeg checkout (`ls ffbuild/library.mak`).
Если делаешь snapshot через rsync — не используй `--exclude=ffbuild`.
### `could not find a working compiler` (GMP)
**Симптом**: crosstool-NG build падает на `Installing GMP for host` с
`configure: error: could not find a working compiler`. config.log показывает
`no, long long reliability test 1`.
**Причина**: GMP 6.2.1 имеет known issue с GCC 11+ (Ubuntu 22.04 default).
Проверка long-long reliability fail'ит false-positive.
**Fix**: pin GMP к 6.3.0 в `ct-ng-config`:
```
CT_GMP_V_6_3=y
# CT_GMP_V_6_2 is not set
CT_GMP_VERSION="6.3.0"
```
И убедись что crosstool-NG version (commit) поддерживает 6.3.0 (≥ master 2024-09).
### `zlib: download failed` в crosstool-NG
**Симптом**: crosstool-NG step `Retrieving 'zlib-1.2.12'` fail'ит.
**Причина**: zlib.net убрали старые versions с дефолтного location — теперь они
только в `/fossils/` subdirectory. Crosstool-NG hardcoded URL не работает.
**Fix**: pre-fetch tarball + положить в local cache:
```bash
wget https://zlib.net/fossils/zlib-1.2.12.tar.gz -O preload/zlib-1.2.12.tar.gz
```
В Dockerfile перед `ct-ng build`:
```dockerfile
COPY preload/*.tar.gz /root/src/
```
`CT_LOCAL_TARBALLS_DIR=${HOME}/src` — crosstool-NG найдёт в cache и не пойдёт
download.
### `stdbit.h: No such file` при `--enable-cuda-llvm`
**Симптом**: FFmpeg configure с `--enable-cuda-llvm` fail'ит:
`fatal error: stdbit.h: No such file or directory`. ERROR: cuda_llvm requested
but not found.
**Причина**: `stdbit.h` — C23 standard header. Доступен в glibc ≥ 2.38.
- Ubuntu 22.04 = glibc 2.35 — **нет**
- Debian 12 = glibc 2.36 — **нет**
- Ubuntu 24.04 = glibc 2.39 — есть
- Debian 13 (trixie) = glibc 2.38+ — есть
**Fix options**:
1. Build на newer base (Ubuntu 24.04+). Но runtime target (Frigate Debian 12)
не запустит binary с glibc-2.38 symbols (backwards-incompatible).
2. Убрать `--enable-cuda-llvm`. Потеря: CUDA filters (`scale_cuda`, `overlay_cuda`,
`hwupload_cuda`). Decode/encode через NVDEC/NVENC всё равно работают.
3. Дождаться когда Frigate base обновится до newer Debian — вне твоего контроля.
**На практике**: убираем cuda-llvm, в Frigate config `hwaccel_args: []`.
См. [scale_cuda секцию](#no-such-filter-scale_cuda).
---
## Docker / IPC
### Cross-container CUDA IPC: ipc + pid namespace share
| Что нужно | Compose option |
|---|---|
| /dev/shm shared (для cuframes header + SHM ring) | `ipc: container:<publisher>` (либо `ipc: shareable` у publisher + same у consumer) |
| /proc visibility (для CUDA IPC peer validation) | `pid: container:<publisher>` |
| `/run/cuframes/*.sock` доступен | volume mount: `cuframes_sock:/run/cuframes:ro` |
| GPU access | `runtime: nvidia` |
| Socket permissions | `user: root` (либо chmod socket в publisher) |
**Все 5** должны быть выполнены. Один пропуск — fail при subscriber_create или
cudaIpcOpenEventHandle.
### Buildx container driver не видит host images
**Симптом**: при использовании custom buildx builder (`docker buildx create
--driver docker-container ...`) с `FROM local-image:tag` — error `failed to
authorize: 403 Forbidden` (buildkit пытается pull с registry).
**Причина**: container driver buildx изолирован, не имеет доступа к host's
local docker daemon images. Pull через registry.
**Fix**: либо
- Не использовать custom builder — `docker buildx use default` (использует host
daemon). Минус: теряем `--cache-to/--cache-from type=local`.
- Либо push local image в **registry** (local или gitea), и buildx pull'ит оттуда.
---
## Networking / RTSP
### RTSP/RTP UDP не доходит до клиента (docker NAT)
**Симптом**: RTSP server в docker контейнере с `ports: "554:8555"`. Клиент (TV, VLC)
делает RTSP SETUP successfully (TCP control работает), но video frames не приходят.
**Причина**: RTP идёт **UDP**, sourced из docker network namespace. SNAT MASQUERADE
для outbound работает, но RTP destination port (которое клиент опубликовал в SETUP)
**не маппится обратно** через docker bridge — клиент видит UDP packets от чужого
source IP (docker network 172.x), не от 192.168.88.23 как expected.
**Fix**: `network_mode: host` для RTSP-server контейнера. Тогда server listens
**напрямую** на host interfaces, RTP packets идут без NAT.
Trade-offs:
- Все ports app'а listen на host network (нет port mapping). Проверь port collisions.
- DB env vars (postgres:5432 в docker network DNS) надо менять на host paths
(`localhost:5433` если postgres exposed на host port 5433).
### `Nonmatching transport in server reply`
**Симптом**: `ffprobe -rtsp_transport tcp -i rtsp://...` falls с этим сообщением.
**Причина**: RTSP server возвращает SDP с UDP-only transport. Client ожидает TCP
interleaved.
**Fix**: использовать UDP transport: `-rtsp_transport udp` (либо default behavior).
Если TV не поддерживает UDP — нужен RTSP server который умеет RTP-over-TCP
interleaved (cctv-processor v0.1 не умеет).
---
## Gitea Actions / CI
### `node: executable file not found`
**Симптом**: первый JS action (например `actions/checkout@v4`) fail'ит:
`OCI runtime exec failed: exec: "node": executable file not found in $PATH`.
**Причина**: гитея act_runner запускает JS actions через `node`, но твой
custom container (например `nvidia/cuda:...`) не имеет node installed.
**Fix**: pre-install node в первом `run:` step (до actions/checkout):
```yaml
steps:
- name: Bootstrap node
run: apt-get update && apt-get install -y nodejs git ca-certificates
- name: Checkout
uses: actions/checkout@v4
```
Либо использовать container с node pre-installed (`docker.gitea.com/runner-images:ubuntu-22.04`).
### `SyntaxError: Unexpected token '{'` (Node 12)
**Симптом**: после `apt install nodejs` в Ubuntu 22.04 — actions/checkout@v4 fail'ит:
`SyntaxError: Unexpected token '{' at static {...}`.
**Причина**: Ubuntu 22.04 apt'овский `nodejs` = Node **12**. `actions/checkout@v4`
скомпилирован для Node 20+ (static class blocks — ES2022).
**Fix**: install Node 20 from NodeSource:
```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs
```
В Ubuntu 24.04 apt уже даёт Node 20 — там goes автоматически.