Гоняно на 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
24 KiB
Проверка 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. Предварительные пакеты на стенд-хосте
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. Ограничения и что делать дальше
- CI-гейты (DoD) не автоматизированы: Gitea Actions runner для
embeddevio/awg-dashне зарегистрирован (см. отчёт в #17, run #1 остаётсяqueued). Все гейты в таблице §2 прогнаны вручную на стенд-хосте; после регистрации runner'а в #17 они переносятся в workflow без изменений команд. - Порт: на
192.168.1.147хост-порт 8080 занят (drawio), поэтому стенд публикуется как8180:8080(контейнерный порт приложения — 8080,AWG_DASH_PORTпереопределяет хост-порт). - Сборка образа — только после мержа #18; до этого образ
awg-dash:devсобирается из ветки #18 вручную. - Prod/staging: stub-оверрайд и
deploy/api_stub.py— DEV-ONLY. В staging/prod поднимается толькоdocker-compose.dev.yml(или прод-манифест) без stub-файла.
5. Откат / очистка
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, 404not_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— на вердикт не влияет.