Files
awg-dash/deploy/VERIFICATION-21.md
embeddevio a3396b2be6 docs(#21): VERIFICATION-21 §7 — реальные прогоны раунда 3 (коды 0/1/3/4, make→2, env-алиас, DEV_SKIP_BUILD)
Гоняно на 192.168.1.147 на дереве, сверенном по git-blob-SHA: ci.sh 0/4/3/1,
make при коде 4 и 1 отдаёт 2 (контейнерные коды видны в логе), AWG_NS_EXEC_MODE
больше не перекрывается, DEV_SKIP_BUILD=1 даёт AC-1 (200 + healthy), test-deploy
51 passed / 98.86 %, manifests и shellcheck — 0.

Refs #21
Refs #56
2026-10-28 21:58:58 +03:00

284 lines
24 KiB
Markdown
Raw Permalink 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.
# Проверка dev/staging стенда (#21) — фактические прогоны на Docker-хосте
Дата: 2026-10-22 · Хост: `192.168.1.147` (Debian 13, Docker 29.4.1, compose v5.1.3, kernel 6.17)
Ветка: `feat/21-dev-stand`, HEAD `d63b9cea` · Ref: #21, #15, #17, #18, #31, #56
## 0. Стенд запускает именно то, что в ветке
Актуальные блобы для раунда 3 — в §7.3 (эта таблица фиксирует состояние раунда 2).
Каталог стенда `(/opt/awg-dash-dev/repo)` не является git-чекаутом (`git clone` по HTTP обрывается
на этом Gitea — `fetch-pack: invalid index-pack output`), поэтому файлы сверены побайтово
через git-blob-SHA с деревом HEAD:
| файл | git blob (HEAD == хост) |
|---|---|
| `Makefile` | `638ec381f3d1e57a8ab84f194e40ff6a0c51d8cf` |
| `deploy/docker-compose.dev.yml` | `6bed89a5ea2a197aa9f6fd6bcda6d8131303114d` |
| `deploy/docker-compose.stub.yml` | `936efb38dfff4ba8b942c9757acd4ba9c39c4064` |
| `deploy/docker-compose.test.yml` | `b628c129ae4de9b33875503b2c8273675b235a84` |
| `deploy/verify-dev-stand.sh` | `c4e13f11f9023700be531ebb312ad33d7beea4d8` |
| `deploy/api_stub.py` | `93663113f118676a7e896e0e9d32bd20a21f6a0e` |
| `tests/deploy/validate_compose.py` | `0fa8b3c02675a364f4f5b447220680a7818faa5a` |
| `tests/deploy/pytest.ini` | `7299b6b45487dd4f214f0444e2f0f5c237eb2fac` |
| `tests/deploy/.coveragerc` | `9b41183e29a155b5498c3722cf4639e9c69268c6` |
| `tests/deploy/test_api_stub.py` | `1d5cec3a72b02da6cb48a6ca433e7699b768f631` |
| `tests/deploy/test_validate_compose.py` | `fb045949848cb70a1880614201930c2d4ecd9d0a` |
| `tests/netns/lib.sh` | `806375299b7c3932d20b275d98b72f65e48940ec` |
| `tests/netns/run.sh` | `c6797bf9c42abe7c4f72cf5402af82e2192ee867` |
| `tests/netns/topology.sh` | `6c13a7cfd4d9368ee7d6cb2efca85d890b803e59` |
| `tests/netns/netns_common.py` | `9d3bfd94cd8e8b321af92274835b3f0ebc48c842` |
| `tests/netns/README.md` | `845863188ffb26c1aae7e475addc0c511e0d8f1f` |
| остальные файлы `tests/netns/` (5) | без изменений с раунда 1 |
## 1. Предварительные пакеты на стенд-хосте
```bash
apt-get install -y make python3-yaml python3-pytest python3-pytest-cov shellcheck
```
(`make` на хосте отсутствовал — без него гейты `make *` невоспроизводимы; пакеты добавлены
сверх существующего набора, ничего не удалялось и не переконфигурировалось.)
## 2. Гейты (реальные прогоны)
Лог целиком: `/opt/awg-dash-dev/verify-final-1792617226.log` (на хосте).
| гейт | команда | результат |
|---|---|---|
| манифесты | `make test-manifests` | exit 0, `validate-compose: ok (1 предупреждений)`¹ |
| статический анализ шелла | `make lint-shell` | exit 0 |
| unit-тесты манифестов | `python3 -m pytest tests/deploy -q --no-cov` | exit 0 |
| netns-харнесс (AC-3) | `make test-netns` | exit 0, `8 ok / 0 fail / 0 err`, отчёт `/tmp/awg-netns/report.json` |
| AC-1 (health 200) | `make dev-up` + `curl :8180/api/v1/health` | HTTP 200, контейнер `healthy` |
| AC-2 (состояние переживает рестарт) | `make dev-verify` | `[ ok ] AC-1`, `[ ok ] AC-2` ×2, exit 0 |
| логи | `make dev-logs` | стрим логов, есть `[api-stub] ... GET /api/v1/health 200` |
¹ предупреждение: `build.dockerfile deploy/Dockerfile отсутствует в дереве (ожидаемо до мержа #18)`.
Образ `awg-dash:dev` для стенда собран из Dockerfile PR #49 (ветка `feat/18-docker-runtime`), в дерево
этой ветки он не входит: `make build` вернёт внятную ошибку до мержа #18. Валидатор переключается в
режим ошибки флагом `make test-manifests MANIFEST_ARGS=--require-dockerfile` — этот режим нужно включить
в CI гейте после мержа #18.
### AC-1 — детали
```
$ make dev-up
# (авто-режим: backend/awg_api/app.py отсутствует (#31) → + deploy/docker-compose.stub.yml)
$ curl -s -w '\nHTTP=%{http_code}\n' http://127.0.0.1:8180/api/v1/health
{"issue": "#31", "note": "заглушка dev-стенда: реальный API появится в #31", "status": "ok",
"stub": true, "stub_env": "1", "uptime_s": 14.909}
HTTP=200
$ docker ps --filter name=awg-dash-dev
awg-dash-dev Up 15 seconds (healthy) 0.0.0.0:8180->8080/tcp, [::]:8180->8080/tcp
```
Почему с заглушкой: FastAPI-каркас (#31) ещё не реализован, сам образ HTTP не поднимает (entrypoint
пишет `awg_api/app.py ещё не реализован` и ждёт). Чтобы AC-1 #21 проверялся на живом compose-стенде
уже в Sprint 0, `make dev-up` добавляет оверрайд `docker-compose.stub.yml` **только пока
`backend/awg_api/app.py` отсутствует**; ответ явно помечен `"stub": true`. После мержа #31 оверрайд
не добавляется, `make dev-up` поднимает реальный API без правок Makefile.
### AC-2 — детали
```
$ make dev-verify
[verify][ ok ] AC-1: http://127.0.0.1:8180/api/v1/health -> 200 (api=stub)
[verify] AC-2: пишу маркеры в /config и /data и перезапускаю контейнер
[verify][ ok ] AC-2: /config и /data пережили restart и пересоздание контейнера
[verify][ ok ] AC-2: маркеры видны на хосте (/opt/awg-dash-dev/{config,data}) — это bind mount, а не слой контейнера
$ ls /opt/awg-dash-dev/config /opt/awg-dash-dev/data
/opt/awg-dash-dev/config/.ac2-marker (24 байта)
/opt/awg-dash-dev/data/.ac2-marker (24 байта)
```
Проверяется два сценария: `docker compose restart` (как в AC) и `up -d --force-recreate` (сильнее).
### AC-3 (netns) — детали
```
[netns] создаю netns: client=awgt21-client gw=awgt21-gw uplink=awgt21-uplink
[ ok ] стенд поднят: awgt21-client(10.213.0.2) -> awgt21-gw(10.213.0.1/10.214.0.1) -> awgt21-uplink(10.214.0.2)
[ ok ] policy routing: fwmark 51820 -> table 51820 (default via 10.214.0.2)
[ ok ] mock endpoint: udp/51820 в awgt21-uplink; детектор утечки: 198.51.100.9:9999/udp
Ran 8 tests in 5.447s — OK
test_01_namespaces_and_links ... test_08_killswitch_blocks_direct_egress — все ok
[ ok ] топология снесена, namespace'ы удалены (ip netns list пусто)
```
`report.json`: `{"suite": "tests/netns", "tests": 8, "passed": 8, "failures": 0, "errors": 0,
"skipped": 0, "returncode": 0, "duration_s": 5.447}`
## 3. Что чинилось в этой итерации
| проблема | причина | правка |
|---|---|---|
| `make dev-up` давал `unhealthy` и AC-1 не выполнялся | API (#31) не реализован | `Makefile`: авто-подключение stub-оверрайда, пока нет `backend/awg_api/app.py`; `dev-up-app` / `dev-up-stub` для явных режимов |
| `make dev-verify` печатал `[verify][fail] нет монтирования /config` при реально сохранённых томах и падал | `verify-dev-stand.sh` кэшировал id контейнера и делал `docker inspect` по нему после `--force-recreate` → `no such object` | перезапрос id (`cid()`) после пересоздания и явный `fatal_env` вместо ложного fail |
| `dev-up` падал без `deploy/Dockerfile` (`--build`) | Dockerfile живёт в #18, ветка не смёржена | `--build` форсируется только при наличии `deploy/Dockerfile` |
## 4. Ограничения и что делать дальше
1. **CI-гейты (DoD) не автоматизированы:** Gitea Actions runner для `embeddevio/awg-dash` не зарегистрирован
(см. отчёт в #17, run #1 остаётся `queued`). Все гейты в таблице §2 прогнаны **вручную** на стенд-хосте;
после регистрации runner'а в #17 они переносятся в workflow без изменений команд.
2. **Порт:** на `192.168.1.147` хост-порт 8080 занят (`drawio`), поэтому стенд публикуется как `8180:8080`
(контейнерный порт приложения — 8080, `AWG_DASH_PORT` переопределяет хост-порт).
3. **Сборка образа** — только после мержа #18; до этого образ `awg-dash:dev` собирается из ветки #18 вручную.
4. **Prod/staging:** stub-оверрайд и `deploy/api_stub.py` — DEV-ONLY. В staging/prod поднимается только
`docker-compose.dev.yml` (или прод-манифест) без stub-файла.
## 5. Откат / очистка
```bash
make dev-down # остановить стенд, каталоги /opt/awg-dash-dev/{config,data} сохраняются
make dev-reset # + удалить осиротевшие контейнеры/сети проекта
make test-netns # netns-харнесс сносит топологию сам; аварийно: bash tests/netns/topology.sh down
```
## 6. Раунд 2 (issue #56): netns в контейнере и покрытие тестов
Дата: 2026-10-28 · Хост: `192.168.1.147` · Ветка: `feat/21-dev-stand` · Ref: #21, #15, #17, #56
Ревью qa-engineer (раунд 1) нашло два блокера: `make test-netns-ci` падал в контейнере
(`mount of /sys failed: Operation not permitted`, exit 255 вместо 0/1/3/4) и гейт покрытия
`pytest tests/deploy` обходился флагом `--no-cov` (0.00 %, `deploy/api_stub.py` 0 %).
### 6.1 Причина падения netns в контейнере
Воспроизведено на образе `awg-dash:dev` (iproute2 6.11.0):
| команда в контейнере | результат |
|---|---|
| `ip netns add <ns>` | rc=0 |
| `ip netns exec <ns> true` | `mount of /sys failed: Operation not permitted`, rc=255 |
| `ip -n <ns> link set lo up` | `mount of /sys failed: Operation not permitted`, rc=255 |
| `nsenter --net=/var/run/netns/<ns> true` | rc=0 |
`ip netns exec` и `ip -n` перемонтируют `/sys` внутри namespace; в контейнере это падает.
Поэтому харнесс больше не использует эти формы: вход в namespace идёт через `ns_exec()` /
`ns_prefix()` (`tests/netns/lib.sh`) и `netns_common.ns_sh()`, которые один раз пробуют
`ip netns exec`, а при отказе переключаются на `nsenter --net=...` (`AWG_NS_EXEC_MODE=auto|ip|nsenter`).
Все `ip -n "$NS"` заменены на `ns_exec "$NS" ip ...`.
Коды возврата приведены к контракту `0/1/3/4` (`run.sh`, `topology.sh`): неожиданный код
утилиты (255) нормализуется в `4` («ошибка стенда»), нет прав — `3`, упавшие тесты — `1`.
**Контракт действует на уровне контейнера, а не через make.** GNU make возвращает `2`
на любой упавший рецепт, поэтому `make test-netns-ci` при коде контейнера `3` или `4`
сам отдаёт `2` (в выводе остаётся только `make: *** [Makefile:…] Error 4`). Для CI #17
добавлена точка входа без make — `bash tests/netns/ci.sh`: она запускает тот же контейнер
(`docker compose … run --rm -T netns`) и возвращает его код как есть. Проверено в §7.2.
### 6.2 Реальные прогоны
| прогон | команда | результат |
|---|---|---|
| стенд на хосте | `make test-netns` | exit 0, 8 ok / 0 fail, 5.46 s, `ip netns list` пуст, мок и sink не остались в процессах |
| стенд в контейнере (путь CI) | `make test-netns-ci` | exit 0, `[netns] вход в netns: nsenter`, backend `iptables`, 8 ok / 0 fail, 5.50 s, `[ ok ] топология снесена`, отчёт `/artifacts/netns-report.json` |
| нет прав | `docker run` без `--privileged` | exit **3**, `[blocked] не удалось создать netns — нет CAP_NET_ADMIN/SYS_ADMIN` |
| форс нерабочего режима | `AWG_NS_EXEC_MODE=ip` в контейнере | exit **4**, `mount of /sys failed` → `[warn] run.sh: непредвиденный код 255 — трактую как ошибку стенда (4)` |
| упавшие тесты | синтетический проваленный тест в контейнере | exit **1**, `[warn] netns-стенд: тесты/очистка упали (код 1)` |
| гейт покрытия | `make test-deploy` | exit 0, 51 passed, coverage **98.86 %** (`deploy/api_stub.py` 98 %, `tests/deploy/validate_compose.py` 99 %) |
| манифесты / shellcheck | `make test-manifests`, `make lint-shell` | exit 0 / exit 0 |
| стабильность в контейнере | 5 прогонов `make test-netns-ci` подряд | 5/5 exit 0, 8 ok каждый, ни одного «остался netns» |
| AC-1 после правок | `curl :8180/api/v1/health` | HTTP 200, `"stub": true`, контейнер `healthy` |
| AC-2 после правок | `make dev-verify VERIFY_ARGS="--no-up --skip-build"` | exit 0: маркеры `/config` и `/data` пережили restart и пересоздание, видны на хосте (bind mount) |
### 6.3 Что изменено для гейта покрытия
* `tests/deploy/pytest.ini` — отдельный профиль: `--cov=deploy --cov=tests/deploy
--cov-config=tests/deploy/.coveragerc --cov-fail-under=70`. Раньше `pytest tests/deploy`
наследовал addopts корневого `pyproject.toml` (`--cov=awg_agent --cov=awg_api`), поэтому
покрытие всегда было 0.00 % и RC=1 — гейт гоняли с `--no-cov` в обход порога.
* `tests/deploy/.coveragerc` — из покрытия исключены сами тестовые модули
(`tests/deploy/test_*.py`), иначе «покрытие» измеряет тестовый код.
* `tests/deploy/test_api_stub.py` — 9 тестов на живой HTTP-сервер заглушки: 200 и
`"stub": true` на `/api/v1/health`, алиас `/healthz`, игнор query-string, 404
`not_implemented` на прочие пути, HEAD, отказ на POST, разбор аргументов `main()`.
* `tests/deploy/test_validate_compose.py` — 39 негативных/граничных тестов валидатора:
каждая инварианта AC #21 ломается по отдельности (порт, тома, healthcheck, NET_ADMIN,
`/dev/net/tun`, `ip_forward`, privileged netns-сервис, DEV-ONLY-метка, `--require-dockerfile`).
* `deploy/api_stub.py` — `make_server()` выделен из `main()` (тесты поднимают сервер на порту 0).
* `make test-deploy` — гейт покрытия. Утверждение раунда 2 «`make test-netns-ci` прокидывает
`AWG_NS_EXEC_MODE`» было **неверным**: make-алиас `NETNS_EXEC_MODE ?= auto` перекрывал
переменную. Исправлено в §7.1 (проверено прогоном в §7.2).
* `topology.sh`: снос топологии удаляет namespace'ы безусловно и до 5 раз проверяет
результат — в контейнере `ip netns delete` иногда «терялся» и `run.sh` отдавал код 4
на зелёных тестах (флак 1 из 5 прогонов, после правки — 5/5 зелёных).
### 6.4 Что закрыто и что осталось
Закрыты оба блокера раунда 1 (`make test-netns-ci` работает в контейнере, покрытие
измеряется и ≥70 %). `CI green` по-прежнему недостижим: Gitea Actions runner не
зарегистрирован (#17), все прогоны в таблице 6.2 — ручные на `192.168.1.147`; команды
`make test-netns-ci` и `make test-deploy` — ровно те, что должны попасть в workflow #17.
Порт и stub-оверрайд (см. §4) без изменений: `privileged: true` и `cap_add SYS_MODULE`
из `deploy/docker-compose.test.yml` — только DEV/TEST, для staging/prod непригодны.
## 7. Раунд 3 (ревью PR #53, раунд 2): контракт кодов, env-алиас, сборка без сети
Дата: 2026-10-28 · Хост: `192.168.1.147` (Debian 13, Docker 29.4.1, compose v5.1.3, kernel 6.17)
Ветка: `feat/21-dev-stand` · Ref: #21, #15, #17, #56
Ревью qa-engineer (раунд 2, PR #53 `issuecomment-664`) дало три воспроизводимых замечания:
контракт `0/1/3/4` теряется на уровне make, документированный `AWG_NS_EXEC_MODE` перекрыт
make-алиасом, `make dev-up` падает при сборке без DNS. Все три закрыты правками ниже.
### 7.1 Что изменено
| замечание | правка |
|---|---|
| `make test-netns-ci` отдаёт `2` на любом провале → CI #17 не отличит 3/4/1 | новая точка входа `tests/netns/ci.sh`: `docker compose -f deploy/docker-compose.test.yml run --rm -T netns`, код контейнера возвращается как есть (`2` у неё = сломан вызов/нет docker/образа). `make test-netns-ci` делегирует в неё, в комментарии таргета и в §6.1 зафиксировано «make → 2» |
| `AWG_NS_EXEC_MODE=ip make test-netns-ci` игнорировался (`Makefile:19` `NETNS_EXEC_MODE ?= auto` + `:117`) | `NETNS_EXEC_MODE ?= $(if $(AWG_NS_EXEC_MODE),$(AWG_NS_EXEC_MODE),auto)` — документированное имя имеет приоритет, make-алиас остался для совместимости; README и `dev-stand.md` приведены к факту |
| `make dev-up` форсит `--build` и падает без DNS | `DEV_SKIP_BUILD=1` → `up -d --no-build`; `DEV_UP_BUILD` переопределяем (`?=`) и по-прежнему принимается из командной строки (`DEV_UP_BUILD=`); строка в `dev-stand.md` (быстрый старт + pitfalls + таблица переменных) |
### 7.2 Реальные прогоны (дерево сверено по git-blob-SHA, см. §7.3)
| прогон | команда | код | что видно в выводе |
|---|---|---|---|
| CI-точка входа, зелёный | `bash tests/netns/ci.sh` | **0** | `[ci] образ=awg-dash:dev режим netns=auto`, `вход в netns: nsenter`, `Ran 8 tests`, `8 ok`, `[ ok ] топология снесена`, `[ci][ ok ]` |
| make-путь, зелёный | `make test-netns-ci` | **0** | те же 8 ok через `ci.sh` |
| код 4 (форс нерабочего режима) | `AWG_NS_EXEC_MODE=ip bash tests/netns/ci.sh` | **4** | `[ci] … режим netns=ip`, `mount of /sys failed` → `[warn] код 255 → 4` |
| код 3 (нет прав на netns) | `AWG_NS_PRIVILEGED=0 bash tests/netns/ci.sh` | **3** | `[blocked] не удалось создать netns — нет CAP_NET_ADMIN/SYS_ADMIN`, `[ci][blocked]` |
| код 1 (упавшие тесты) | синтетический падающий тест в `tests/netns/` → `bash tests/netns/ci.sh` | **1** | `Ran 9 tests`, `FAILED (failures=1)`, `8 ok, 1 fail` (тест удалён после прогона) |
| контракт make при коде 4 | `AWG_NS_EXEC_MODE=ip make test-netns-ci` | **2** (контейнер 4) | `[ci] … режим netns=ip` — **env больше не перекрывается**; `make: *** [Makefile:133: test-netns-ci] Error 4` |
| контракт make при коде 1 | синтетический падающий тест → `make test-netns-ci` | **2** (контейнер 1) | `make: *** [Makefile:133: test-netns-ci] Error 1` |
| AC-1 | `make dev-up DEV_SKIP_BUILD=1` + `curl :8180/api/v1/health` | **0**, HTTP **200** | `up -d --no-build`, контейнер `Up (healthy)`, ответ `"stub": true` |
| AC-1 со сборкой (кеш тёплый) | `make dev-up` (без `DEV_SKIP_BUILD`) | **0** | `222` шагов `CACHED`, `Image awg-dash:dev Built`, контейнер пересоздан и `healthy` — на холодном кеше этот путь требует DNS, поэтому кноб нужен |
| AC-2 | `make dev-verify VERIFY_ARGS="--no-up --skip-build"` | **0** | маркеры `/config` и `/data` пережили `restart` и `--force-recreate`, видны на хосте (bind mount) |
| гейт покрытия | `make test-deploy` | **0** | `51 passed`, `Required test coverage of 70% reached. Total coverage: 98.86%` (`TOTAL 176 строк, 2 miss`) |
| манифесты | `make test-manifests` | **0** | `validate-compose: ok (1 предупреждений)` — предупреждение про `deploy/Dockerfile` (до мержа #18) |
| shellcheck | `make lint-shell` | **0** | проверен и новый `tests/netns/ci.sh` |
| уборка | `ip netns list`; `docker ps -a --filter name=awg-dash-test` | — | пусто, контейнеров после прогонов не осталось |
Логи прогонов на стенд-хосте: `/tmp/r3_a.log` … `/tmp/r3_m.log`
(`a` — зелёный `ci.sh`, `b` — код 4, `c` — код 3, `d` — make при коде 4, `e`/`f` — код 1 через `ci.sh`/make,
`g` — `make test-netns-ci`, `h` — `dev-up DEV_SKIP_BUILD=1` + health, `i` — AC-2, `j` — `test-deploy`,
`k` — `test-manifests`, `l` — `lint-shell`, `m` — `dev-up` со сборкой).
### 7.3 Блобы: файлы на стенд-хосте == дерево ветки
| файл | git blob |
|---|---|
| `Makefile` | `55c3ea5c98c7980c9bc3fcec8483d1140e670b2e` |
| `tests/netns/ci.sh` | `561ca3976ad3749fbde79b8d163e007bace145de` |
| `tests/netns/README.md` | `a4642f845f38fca1da0019af7e91d95be9e4d97c` |
| `deploy/dev-stand.md` | `60495f7cd8eba10b40dc9aef469bea210b50e8e1` |
| `deploy/VERIFICATION-21.md` | этот файл (блоб меняется этим же коммитом) |
| остальные файлы стенда | без изменений с раунда 2 (§0) |
### 7.4 Что осталось
* **CI green (DoD)** — за задачей #17: у репозитория появился runner (контейнер
`gitea-runner-awg-repo`, запуски runs 22–31), но workflow, покрывающего ветку #21, в `main`
ещё нет, поэтому все гейты выше прогнаны вручную. Команды для workflow:
`bash tests/netns/ci.sh`, `make test-deploy`, `make test-manifests`, `make lint-shell` —
они уже отдают нужные коды без make-обёртки.
* Merge в `main` и любой prod/staging-деплой — за TechLead (ADVISORY): автор ревью не мержит.
* `verify-dev-stand.sh` после `--force-recreate` печатает статус до перехода в `healthy`,
но AC-проверки при этом ждут health (`wait_health 20`) и прогон возвращает `0` — на вердикт
не влияет.