Перейти к содержанию

Эксплуатация 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-заметки.