Эксплуатация geoscan (планировщик БВП)¶
Этот раздел — инструкции по установке, запуску, развёртыванию, эксплуатации и диагностике сервиса планирования полётных заданий (всё, что вне каталога simulate/). Для жюри и заказчика здесь — как за десять минут проверить демо-контур (https://aerozveno.ff): поды, health-ручки, доступность API и статики; это не отчёт о полной готовности продукта. Известные продуктовые ограничения (планирование, UI, трассировка) — в перечне ограничений; итог приёмки — в 96-status/00-final-status.md (архив вердикта — release/docs/task5/96-acceptance/00-verdict.md в git). Для разработчика — как поднять контур локально и сверить поведение с кодом. Для оператора — пошаговые команды, мониторинг и типовые поломки.
Соседние разделы документации: бизнес · сценарии · UI (черновик) · архитектура · домен · API (черновик). Симулятор — отдельный продукт.
Термины (ПАФС, ЛАФС, галс, ЛЗП, GSD) — глоссарий.
Что разворачивается¶
| Контур | Где | Домен / доступ | Манифесты |
|---|---|---|---|
| Демо хакатона (полный UI) | k3s n1 | https://aerozveno.ff (tailnet + TLS ff-ca) |
k3s/aerozveno/ |
| Бэкенд без отдельного фронта | k3s n2 (по умолчанию в CLAUDE.md) |
https://geoscan.ff |
k3s/geoscan/ |
| Офлайн на машине разработчика | Docker Compose | http://127.0.0.1:3000 |
docker-compose.yml, дубликат src/backend/docker-compose.yml |
На aerozveno.ff ingress делит трафик: API и ops-ручки → gateway, остальное → nginx со статикой (k3s/aerozveno/60-ingress.yaml:26-50). Подробная шпаргалка по k3s — k3s-aerozveno.md. История выката UI — ../../95-deploy/frontend-on-aerozveno.md.
Процессы бэкенда (одинаковая логика везде):
| Процесс | Назначение | Порты |
|---|---|---|
gateway |
REST /api, Swagger, очередь планов, SSE прогресса |
HTTP 3000 |
planner |
Расчёт галсов, gRPC | ops 3001, gRPC 5001 |
validator |
Проверки плана, gRPC | ops 3002, gRPC 5002 |
postgres |
PostGIS, схема gateway |
5432 |
Инструменты (версии с рабочей станции, 2026-09-17)¶
Проверка в каталоге клона:
git branch --show-current # ожидаемая ветка разработки документации: docs2/gs-operate
python3 --version # Python 3.12.3
uv --version # uv 0.12.5
node --version # v24.5.0 (в package.json фронта: engines node >=20.19)
docker --version # Docker 29.2.1
kubectl version --client # Client v1.35.5
Для k3s нужен kubeconfig с контекстом n1 (стенд aerozveno). На машине без tailnet и без ff-ca/ff-ca.crt внешние URL .ff недоступны — это ожидаемо (k3s/aerozveno/60-ingress.yaml:8-9).
Установка из чистого клона (без Docker)¶
1. Клонирование¶
git clone https://gitea.ff/gpb/geoscan.git
cd geoscan
git checkout docs2/gs-operate # или ветка с нужным релизом
Доступ к gitea.ff — только из tailnet; токен хранится в creds-store (gitea), в документ не копируется.
2. Python: planner и validator¶
Требования из src/backend/README.md:9-16:
cd src/backend
uv sync --all-packages
uv run pytest -q --maxfail=1 # полный прогон: сотни тестов, несколько минут
Точечная проверка ops-контракта (как в k3s-пробах):
cd src/backend
uv run pytest tests/integration/test_deploy_contract_b6.py::test_b6_ops_http_matches_k3s_probes -q
Фактический вывод в этой среде:
. [100%]
1 passed in 6.87s
Примеры CLI:
uv run python -m planner run --scenario scenarios/s01-simple.json --objective makespan --out /tmp/plan.json
uv run python -m validator check --scenario scenarios/s01-simple.json --plan /tmp/plan.json
3. Gateway (Node 22+)¶
cd src/backend/gateway
npm ci
npm run build && npm test
Фактический вывод npm test (после npm ci, 2026-09-17):
Test Files 7 passed (7)
Tests 11 passed (11)
Переменные — src/backend/gateway/.env.example и src/backend/.env.example. Минимум для локального процесса: SERVICE_API_KEY, BUILD_COMMIT, HTTP_PORT, DATABASE_URL, адреса gRPC (PLANNER_GRPC_ADDR, VALIDATOR_GRPC_ADDR). Без Postgres можно GATEWAY_PLANS_STORAGE=memory (интеграционные тесты в src/backend/tests/integration/test_gateway_data.py).
Запуск после npm run build:
node dist/main.js
4. Фронтенд (опционально, против локального gateway)¶
cd src/frontend
npm ci
npm run dev # четыре dev-сервера: shell + три remote, src/frontend/package.json:12
Для production-бандла под один origin (как на aerozveno.ff):
npm run build:site
Запуск через Docker Compose¶
Два синхронизированных файла: корневой docker-compose.yml:1-3 и src/backend/docker-compose.yml:1-4. Имя проекта geoscan, сервисы: postgres, planner, validator, gateway (порт 3000 наружу).
cd /path/to/geoscan
docker compose config --quiet && echo "compose config OK"
Проверено: compose config OK.
Поднять весь контур с ожиданием healthcheck:
docker compose up -d --wait
curl -s http://127.0.0.1:3000/healthz
curl -s http://127.0.0.1:3000/readyz
Что произошло на станции при проверке (2026-09-17):
docker compose up -d --waitсобрал/запустил postgres, planner, validator; gateway не стартовал —Bind for 0.0.0.0:3000 failed: port is already allocated(на хосте уже слушал другой контейнер gateway).- На занятом порту 3000 отвечал уже работающий gateway:
{"status":"ok"}
{"service":"gateway","version":"0.1.0","commit":"7173c9af","bootTime":"2026-09-16T14:51:45.385Z","uptimeSec":74313,"timestamp":"2026-09-17T11:30:18.888Z"}
Обход: освободить порт (docker ps → остановить контейнер на 3000) или в override задать другой host-port, например "3001:3000".
Healthcheck в compose (docker-compose.yml:114-123): gateway — GET /readyz; planner — GET /healthz на 3001 (docker-compose.yml:46-55). В src/backend/docker-compose.yml у validator проверяется HTTP /readyz на 3002 (src/backend/docker-compose.yml:76-81), в корневом docker-compose.yml validator проверяет только TCP 5002 (docker-compose.yml:74-83) — файлы должны обновляться синхронно, но сейчас они расходятся.
Образы в compose по умолчанию тег 7173c9af (docker-compose.yml:5-6); при смене кода нужен docker compose build и обновление x-build-commit.
Развёртывание на k3s (aerozveno, n1)¶
Ниже — сжатая схема; команды и откат — в k3s-aerozveno.md.
Процедура выката (оператор)¶
flowchart TD
A[Секреты geoscan-secrets и geoscan-s3 в ns aerozveno] --> B[Сборка образов aerozveno-* с тегом 8-sha]
B --> C[docker push в cr.yandex]
C --> D[Обновить теги в k3s/aerozveno/40-deployment-*.yaml]
D --> E{Менялась только статика?}
E -->|да| F[npm run build:site + publish-frontend.py]
F --> G[rollout restart deploy/geoscan-frontend]
E -->|нет| H[kubectl apply -k k3s/aerozveno/ --server-side]
H --> I[rollout status: geoscan, planner, validator, frontend]
I --> J[DNS aerozveno.ff в ff-coredns на n2]
J --> K[curl /healthz и /readyz с ff-ca]
K --> L[Панели Grafana /d/aerozveno]
Вывод: без секретов и без образов в реестре поды уйдут в ImagePullBackOff или CreateContainerConfigError; без DNS имя не резолвится; без tailnet ingress отдаст 403 — это штатно.
Сборка и выкатка во времени¶
sequenceDiagram
participant Dev as Разработчик
participant Git as git / TAG=8-sha
participant CR as Yandex CR
participant K8s as kubectl n1
participant Pod as Pods aerozveno
participant User as Браузер tailnet
Dev->>Git: git rev-parse --short=8 HEAD
Dev->>CR: docker build + push aerozveno-gateway/planner/validator
Dev->>K8s: apply -k k3s/aerozveno/
K8s->>Pod: init migrate (gateway image)
K8s->>Pod: rollout gateway / planner / validator
opt Новый UI
Dev->>CR: publish-frontend.py → MinIO
K8s->>Pod: mc mirror → nginx
end
User->>Pod: HTTPS aerozveno.ff /api/plans
Pod-->>User: SSE progress + JSON plan
Эксплуатация: health, status, metrics¶
Публичные URL на aerozveno.ff¶
Ingress пробрасывает те же пути, что и у gateway (k3s/aerozveno/60-ingress.yaml:30-45).
| Путь | Назначение | Реализация |
|---|---|---|
GET /healthz |
Процесс жив | src/backend/gateway/src/ops/ops.controller.ts:28-31 → {"status":"ok"} |
GET /readyz |
Готов принимать трафик | ops.controller.ts:34-44 — 200 или 503 с errorCode: UPSTREAM_UNAVAILABLE |
GET /status |
Версия, commit, uptime | ops.controller.ts:47-55, тело из buildStatusBody в src/backend/shared/src/ops-http-contract.ts:10-24 |
GET /metrics |
Prometheus (gateway) | ops.controller.ts:58-79, метрики geoscan_* в src/backend/gateway/src/ops/metrics.service.ts:24-56 |
GET /api/docs |
Swagger UI | NestJS, не в ops-контроллере |
Readiness gateway проверяет gRPC planner/validator и Postgres (src/backend/gateway/src/ops/readiness.service.ts:27-64). Пока planner не поднят, /readyz вернёт 503 — под не попадёт в Service endpoints.
Planner / validator (из кластера, ClusterIP): ops-HTTP на 3001/3002 — src/backend/planner/planner/rpc/ops.py:45-73 (те же /healthz, /readyz, /status, /metrics). В Deployment aerozveno у validator пробы сейчас tcpSocket на gRPC 5002, не HTTP (k3s/aerozveno/40-deployment-validator.yaml:49-58) — ops-порт 3002 в поде есть, но kubelet HTTP-готовность по нему не спрашивает.
Состояния сервиса и задания планирования¶
stateDiagram-v2
[*] --> ProcessUp: контейнер стартовал
ProcessUp --> LivenessOK: GET /healthz 200
LivenessOK --> NotReady: postgres или gRPC не готовы
NotReady --> Ready: GET /readyz 200
Ready --> NotReady: обрыв БД / gRPC
Ready --> Serving: в endpoints Ingress
Serving --> PlanQueued: POST /api/plans
PlanQueued --> PlanRunning: воркер gateway
PlanRunning --> PlanCompleted: feasible/optimal
PlanRunning --> PlanFailed: ошибка / таймаут
PlanCompleted --> [*]
PlanFailed --> [*]
Статусы задания в коде: queued | running | completed | failed (src/backend/gateway/src/plans/plans.repository.ts:6). Метрика geoscan_plan_jobs обновляется при отдаче /metrics (ops.controller.ts:60-75).
Проверка живого стенда (выполнено)¶
curl --cacert ff-ca/ff-ca.crt https://aerozveno.ff/healthz
curl --cacert ff-ca/ff-ca.crt https://aerozveno.ff/readyz
curl --cacert ff-ca/ff-ca.crt https://aerozveno.ff/status
Ответы:
{"status":"ok"}
{"status":"ok"}
{"service":"gateway","version":"0.1.0","commit":"7173c9af","bootTime":"2026-09-16T16:16:33.650Z","uptimeSec":68553,"timestamp":"2026-09-17T11:19:06.767Z"}
Начало /metrics (тот же день):
# HELP process_cpu_user_seconds_total Total user CPU time spent in seconds.
# TYPE process_cpu_user_seconds_total counter
process_cpu_user_seconds_total{service="gateway"} 612.3591600000013
...
Сквозной сценарий в браузере и headless: src/frontend/tools/e2e-aerozveno.mjs (описан в JOURNAL.md и docs/task5/95-deploy/frontend-on-aerozveno.md).
Мониторинг¶
Общая инфраструктура — docs/05-monitoring.md (Grafana https://grafana.ff, Prometheus https://prometheus.ff, пароль admin — creds monitoring).
Цели scrape для aerozveno¶
Зеркало конфигурации: k3s/monitoring/prometheus-aerozveno-scrape.yaml:7-13
| component | target | Примечание |
|---|---|---|
| gateway | geoscan.aerozveno.svc.cluster.local:80 |
/metrics на gateway |
| planner | planner.aerozveno.svc.cluster.local:3001 |
geoscan_plan_duration_seconds и др. |
| frontend | geoscan-frontend.aerozveno.svc.cluster.local:9113 |
nginx-prometheus-exporter |
| validator | — | В job aerozveno нет static_configs на validator (prometheus-aerozveno-scrape.yaml:7-13 — только gateway, planner, frontend). При этом процесс validator поднимает ops-HTTP с /metrics (src/backend/validator/validator/rpc/ops.py:45-73, запуск в server.py:86). Комментарий в scrape-файле про «ops-http не поднимает» (prometheus-aerozveno-scrape.yaml:6) устарел относительно кода; для метрик validator нужен отдельный target или правка scrape. |
Дашборд Grafana: https://grafana.ff/d/aerozveno (JSON — k3s/monitoring/grafana/dashboards/aerozveno.json). Панель «Браузер → API» смотрит на geoscan_http_requests_total — по ней видно, ходит ли UI в gateway.
Алерты¶
Правила в репозитории: k3s/monitoring/aerozveno.rules.yml. Alertmanager в контуре нет — смотреть в UI Prometheus /alerts и в Grafana.
| Alert | Смысл |
|---|---|
AerozvenoTargetDown |
up{job="aerozveno"}==0 3m |
AerozvenoHttp5xx |
доля 5xx > 5% за 5m |
AerozvenoPlanFailures |
планы не в статусе feasible/optimal 15m |
AerozvenoPlanSlow |
p95 geoscan_plan_duration_seconds > 60s |
AerozvenoFrontendDown |
nginx/статика недоступны — останется только API |
Раскатка изменений мониторинга — rsync на n1 и kubectl apply -k (docs/05-monitoring.md:41-45).
Диагностика¶
Источники реальных инцидентов: JOURNAL.md (грабли деплоя UI, SSE, S3, сценарии) и заметки в docs/task5/95-deploy/frontend-on-aerozveno.md. Файл docs/09-agent-pipeline-postmortem.md в этой ветке отсутствует — ориентируйтесь на журнал и merge-log docs/task5/98-fixes/00-merge-log.md.
Дерево «симптом → что делать»¶
flowchart TD
S1{Симптом} --> S2[403 на aerozveno.ff]
S2 --> S2a[Не tailnet / не тот source IP]
S2a --> S2b[Подключить Tailscale; whitelist 100.64.0.0/10 в ingress]
S1 --> S3[502 / 503 на /api]
S3 --> S3a{kubectl get pods -n aerozveno}
S3a -->|ImagePullBackOff| S3b[Проверить cr.yandex и секрет pull; см. docs/04-yandex-cr.md]
S3a -->|CrashLoop gateway| S3c[kubectl logs deploy/geoscan; миграции init]
S3a -->|readyz 503| S3d[planner/validator/postgres: logs + endpoints]
S1 --> S4[UI пустой / 404 на /]
S4 --> S4a[pod geoscan-frontend: init fetch-bundle]
S4a --> S4b[Бакет MinIO пуст → publish-frontend.py]
S4b --> S4c[rollout restart deploy/geoscan-frontend]
S1 --> S5[Прогресс расчёта не двигается]
S5 --> S5a[SSE: ingress proxy-buffering off — k3s/aerozveno/60-ingress.yaml:12-13]
S1 --> S6[POST /api/scenarios 422]
S6 --> S6a[Схема scenario.schema.json: turnaround_time_min, energy_reserve]
S6a --> S6b[JOURNAL.md: черновики s01-draft-*]
S1 --> S7[Локально compose не поднимает gateway]
S7 --> S7a[Порт 3000 занят — docker ps / сменить mapping]
S1 --> S8[Prometheus target down]
S8 --> S8a[Проверить pod и /metrics изнутри кластера]
Типовые команды¶
kubectl --context n1 -n aerozveno get pods -o wide
kubectl --context n1 -n aerozveno logs deploy/geoscan --tail=100
kubectl --context n1 -n aerozveno describe pod -l app=geoscan
kubectl --context n1 -n aerozveno get endpoints geoscan
Postgres в ns: StatefulSet geoscan-postgres (k3s/aerozveno/30-postgres.yaml). При недоступной БД gateway остаётся на /healthz 200, но /readyz — 503 (тест test_readyz_503_when_postgres_down в src/backend/tests/integration/test_gateway_data.py:385-402).
Откат¶
| Что откатываем | Команда |
|---|---|
| Gateway / planner / validator | kubectl --context n1 -n aerozveno rollout undo deploy/<имя> |
| Конкретная ревизия | rollout undo deploy/geoscan --to-revision=N |
| Только фронт | rollout undo deploy/geoscan-frontend или повторная выгрузка старого dist-site |
| Полное удаление ns | kubectl --context n1 delete -k k3s/aerozveno/ (данные PVC — отдельное решение) |
Имя образа в манифестах должно совпадать с откатываемой ревизией ReplicaSet; надёжнее вернуть предыдущий 8-sha тег в yaml и снова apply -k. Тег :latest в репозитории не используется (docs/02-deploy.md:106-107).
После отката кода проверьте:
curl --cacert ff-ca/ff-ca.crt https://aerozveno.ff/readyz
curl --cacert ff-ca/ff-ca.crt https://aerozveno.ff/status
Связанные материалы в репозитории¶
| Тема | Путь |
|---|---|
| Деплой общий | docs/02-deploy.md |
| Yandex CR | docs/04-yandex-cr.md |
| Чеклист нового сервиса | docs/07-new-service-checklist.md |
| Системные требования SR-DEP | docs/task5/91-system/09-deployment.md |
| Конфиг и секреты | docs/task5/91-system/08-config-and-secrets.md |
| Бэкенд README | src/backend/README.md |
Ограничения и честные пробелы¶
Эксплуатация vs продукт. Живой стенд и зелёные /healthz не означают, что все сценарии ТЗ закрыты: см. 03-limitations.md и 00-final-status.md.
airspaceкак отдельный процесс в манифестах не развёрнут (заглушка в архитектуре,docs/task5/91-system/09-deployment.md:40-43).- Validator в Prometheus job
aerozvenoне скрейпится, хотя ops/metricsв коде есть (см. таблицу scrape выше). scripts/deploy.shрассчитан на Deployment с именем, совпадающим с аргументом (geoscanна n2), не на стекaerozveno.- Сборка образа gateway на станции при документировании один раз упала с ошибкой Docker BuildKit; перед релизом сборку нужно подтвердить локально.
- Файл
docs/09-agent-pipeline-postmortem.mdв worktree не найден — диагностика опирается наJOURNAL.mdи deploy-заметки.