Files
telemt-api/docs/GATEWAY_RUN.md
T
Denozordec 8bf4bb7f35
Publish telemt-api gateway Docker image / test (push) Successful in 24s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m6s
Enhance error handling in gateway and configuration
- Added `expose_upstream_errors` option to the configuration, allowing detailed error messages in JSON responses for 502 errors.
- Implemented `writeBadGatewayJSON` function to streamline JSON error responses, including upstream error details when enabled.
- Updated documentation to reflect changes in configuration and error handling behavior for improved diagnostics.
2026-03-31 14:30:15 +07:00

24 KiB
Raw Blame History

Запуск Telemt API Gateway (Docker)

Оглавление:

  1. Назначение
  2. Требования
  3. Минимальная конфигурация
  4. Переменные окружения
  5. Готовый образ из registry
  6. Локальная сборка образа
  7. Запуск через Docker CLI
  8. Запуск через Docker Compose
  9. Проверка
  10. Обновление и CI/CD
  11. Устранение неполадок
  12. Mihomo (external-controller) — при 400 на REST: отладка

Назначение

Шлюз — это один HTTP‑вход для нескольких экземпляров Telemt Control API:

  • Web UI (в образе Docker): статика панели на GET / (и клиентские маршруты SPA), агрегаты и прокси на /api/…. Тот же порт, что и у API (например 8080). Исходники UI — каталог web/, сборка встроена в Dockerfile (стадия Node + embed в Go).
  • Белый список IP (CIDR): кто может обращаться к шлюзу (кроме GET /health, см. ниже).
  • Маршрутизация по alias: клиент вызывает GET /api/{alias}/health, шлюз проксирует на {base_url}/v1/health у соответствующего сервера.
  • Метрики Prometheus: GET /metrics (под тем же правилом whitelist, что и API).
  • Доверенные прокси: если прямой TCP‑peer входит в trusted_proxies, для проверки whitelist берётся первый адрес из X-Forwarded-For или X-Real-IP.

Эндпоинты и контракт ответов бэкенда описаны в API.md.

Требования

  • Установленные Docker и при необходимости Docker Compose v2.
  • Для локальной сборки Docker-образа из репозитория: Docker сам подтянет Node на стадии сборки фронта и Go 1.22+ на стадии компиляции (см. Dockerfile).
  • Для go test ./... без Docker на машине нужен только Go 1.22+.

Минимальная конфигурация

Скопируйте config.example.yaml в свой config.yaml и отредактируйте.

Минимальный рабочий фрагмент для разработки (без проверки IP):

listen: ":8080"
allow_all: true
servers:
  - alias: main_srv
    base_url: http://127.0.0.1:9091

Минимальный фрагмент для продакшена (только перечисленные сети/хосты):

listen: ":8080"
allow_all: false
whitelist_cidrs:
  - "203.0.113.10/32"
  - "10.0.0.0/8"
servers:
  - alias: main_srv
    base_url: http://telemt-internal:9091

Правила:

  • При allow_all: false и пустом whitelist_cidrs доступ будет закрыт для всех (кроме GET /health).
  • В whitelist_cidrs и trusted_proxies допустимы CIDR (10.0.0.0/8) и одиночный IPv4/IPv6 без маски (87.103.241.8 эквивалентно 87.103.241.8/32).
  • GET /health на шлюзе не проверяется по whitelist — так проще настроить Docker HEALTHCHECK и оркестраторы.
  • Поле path_prefix по умолчанию равно /v1 (префикс Telemt Control API).

Опционально для бэкенда с включённым auth_header в Telemt задайте в конфиге имя переменной окружения, значение которой будет отправлено как заголовок Authorization на этот upstream:

servers:
  - alias: main_srv
    base_url: http://telemt:9091
    authorization_env: TELEMT_API_AUTH

Значение должно точно совпадать с настроенным в Telemt auth_header (см. API.md).

Mihomo (external-controller)

Опционально для каждого servers[] можно включить проксирование Mihomo (Clash Meta) API: в Web UI появится раздел «Mihomo» для выбранного alias.

  • Клиент (браузер) обращается к шлюзу: GET /api/{alias}/mihomo/proxies, WebSocket …/mihomo/traffic и т.д.
  • Шлюз проксирует на {mihomo_base_url}/proxies, …/traffic и т.д. с заголовком Authorization из переменной окружения (секрет не попадает в фронтенд).
  • Служебный ответ: GET /api/{alias}/mihomo/meta — JSON с полем controller_base (без учётных данных) для строки «Подключено к: …» в панели.

В config.yaml задаются только параметры шлюза (mihomo_base_url / mihomo_base_url_env, mihomo_authorization_env). Сервис контейнера Mihomo (build, CLASH_SECRET, сеть proxy-net и т.д.) описывается в вашем Docker Compose отдельно; связка URL и переменных для gateway — ниже и в docker-compose.yml.

Поля в конфиге:

Поле Описание
mihomo_base_url Базовый URL контроллера, например http://mihomo:9090 (порт контроллера по умолчанию в контейнере Mihomo — 9090).
mihomo_base_url_env Имя переменной окружения; если задано и значение непустое, URL контроллера берётся из os.Getenv при старте (удобно в Docker без хардкода IP). Если env пустой, используется mihomo_base_url.
mihomo_authorization_env Имя env: полное значение заголовка Authorization (например Bearer <secret>), как у authorization_env для Telemt. Должно совпадать с секретом на стороне Mihomo (secret / CLASH_SECRET в конфиге ядра).

У нескольких записей servers[] можно задать разные имена переменных (mihomo_authorization_env: TELEMT_MIHOMO_MTG и …_GT1), если секреты контроллеров на нодах различаются. Одно и то же имя env для всех нод — нормально, если везде один и тот же токен.

Локальный Mihomo (HTTP в Docker) работает, удалённый (HTTPS) даёт 502 через шлюз

Прямой curl с хоста к https://…:8443 может быть успешен, а шлюз при этом отдаёт 502 с mihomo upstream unreachable: исходящий запрос делает процесс шлюза (часто контейнер). Отличия от «рабочего» curl:

  • другая сеть/DNS из контейнера;
  • другой исходящий IP на стороне nginx Mihomo (whitelist allow);
  • ошибка TLS при проверке сертификата из окружения процесса.

Включите в config.yaml expose_upstream_errors: true, перезапустите шлюз и повторите запрос: в JSON появится error.detail с текстом ошибки (x509: …, dial tcp …, lookup … и т.д.). После диагностики флаг отключите.

Правила:

  • Если указан mihomo_base_url или mihomo_base_url_env, обязательно задайте mihomo_authorization_env и непустые значения в env при старте шлюза.
  • Контейнер gateway должен иметь сетевую связность с контроллером Mihomo (лучше одна пользовательская Docker-сеть; имя сервиса http://mihomo:9090 предпочтительнее статического IP). Публиковать порт 9090 на хост не обязательно: браузер ходит в шлюз, шлюз — в контейнер Mihomo по overlay-сети.
  • Пример environment для compose (секреты не в git — через .env):
services:
  gateway:
    environment:
      MIHOMO_CONTROLLER_URL: http://mihomo:9090
      TELEMT_MIHOMO_AUTH: Bearer ${CLASH_SECRET}

и в config.yaml для нужного сервера: mihomo_base_url_env: MIHOMO_CONTROLLER_URL, mihomo_authorization_env: TELEMT_MIHOMO_AUTH.

За reverse proxy (nginx) перед панелью убедитесь, что для WebSocket проксируются заголовки Upgrade и Connection. Панель Mihomo тянет трафик и память потоковым HTTP (не WS); при задержке или «залипании» графиков за nginx включите для upstream шлюза proxy_buffering off (или эквивалент), чтобы не буферизовать длинный ответ.

Отладка 400 на маршрутах Mihomo (/api/{alias}/mihomo/…)

Если напрямую к контроллеру всё ок (curl -H "Authorization: Bearer …" http://mihomo:9090/version → JSON), а через шлюз те же пути дают 400 и в логах шлюза duration_ms около 1 — проблема в сборке исходящего HTTP на шлюзе, а не в секрете Mihomo.

Что зафиксировано в коде и не стоит «упрощать» назад без причины:

Что Зачем
REST (GET /version, /proxies, /connections, POST к API контроллера и т.д.) Тот же путь, что и прокси к Telemt: http.NewRequest + RoundTrip (internal/proxy/forward.go, общая логика newAliasForward). Так совпадает с рабочим curl к upstream.
Не единый httputil.ReverseProxy на весь Mihomo Для обычного HTTP ReverseProxy нередко даёт 400 у строгих upstream при том, что прямой запрос работает.
WebSocket (/traffic, /memory, …) На upstream — отдельно httputil.ReverseProxy + Rewrite (internal/proxy/mihomo.go). Веб-панель использует нативный WebSocket к шлюзу для метрик и параллельно короткие HTTP GET (резерв); шлюз подставляет Authorization к Mihomo, токен в браузер не передаётся.
Заголовок Host Не пересылается с клиента; на upstream уходит authority из mihomo_base_url (как для Telemt и base_url).

Проверки конфигурации:

  • mihomo_authorization_env в YAML — это имя переменной; полная строка Authorization (например Bearer <secret>) задаётся в environment контейнера gateway, не в YAML.
  • После изменений в этом месте пересоберите образ / задеплойте актуальный бинарь — иначе будет старое поведение.

Переменные окружения

Переменная Описание
CONFIG_PATH Путь к YAML внутри контейнера. По умолчанию: /etc/telemt-gateway/config.yaml.
TELEMT_API_AUTH Пример: секрет для authorization_env в конфиге (имя может быть любым).
MIHOMO_CONTROLLER_URL Пример: URL для mihomo_base_url_env (если используете в конфиге).
TELEMT_MIHOMO_AUTH Пример: Bearer … для mihomo_authorization_env (если используете).

Готовый образ из registry

CI публикует образ в Container Registry Gitea. Для репозитория denozord/telemt-api на git.shts.su стабильный тег:

docker pull git.shts.su/denozord/telemt-api:latest

Если registry не публичный, сначала войдите (логин — пользователь Gitea, пароль — personal access token с правом read:package):

docker login git.shts.su

Другие полезные теги из того же workflow: имя ветки (с / заменённым на -) и sha-<12 символов коммита> — см. раздел Обновление и CI/CD.

Локальная сборка образа

Если нужно собрать образ самостоятельно из клона репозитория:

docker build -t telemt-api-gateway:local .

Сборка многостадийная: сначала npm ci + npm run build в каталоге web/ (в бандл вшивается пустой PUBLIC_TELEMT_GATEWAY_URL, запросы API с того же origin), затем компиляция Go со встраиванием web/build через embed. В командах docker run ниже вместо имени из registry подставьте telemt-api-gateway:local.

Запуск через Docker CLI

Пример для Linux (подставьте путь к config.yaml; ниже — файл из текущего каталога). Используется готовый образ:

docker run -d --name telemt-gateway \
  -p 8080:8080 \
  -v "$(pwd)/config.yaml:/etc/telemt-gateway/config.yaml:ro" \
  -e CONFIG_PATH=/etc/telemt-gateway/config.yaml \
  git.shts.su/denozord/telemt-api:latest

Проверка:

curl -sS -i http://127.0.0.1:8080/health
curl -sS -i http://127.0.0.1:8080/api/main_srv/health
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/

Второй запрос проксируется на {base_url}/v1/health для alias main_srv. Третий — HTML панели (в образах, собранных с Web UI; ожидайте 200).

Панель в браузере: http://127.0.0.1:8080/ (при allow_all: false ваш IP должен быть в whitelist_cidrs, иначе для / будет 403, как и для API).

Остановка и удаление:

docker stop telemt-gateway
docker rm telemt-gateway

Запуск через Docker Compose

В репозитории есть docker-compose.yml (один сервис gateway) и пример config.compose.yaml с allow_all: true и base_url: http://host.docker.internal:9091 (Telemt на хосте; на Docker Desktop для Linux это обычно работает из коробки). Образ по умолчанию скачивается из registry, локальная сборка не требуется. UI доступен на том же порту, что и шлюз: http://127.0.0.1:8080/.

docker compose pull
docker compose up -d
docker compose logs -f gateway
docker compose down

Чтобы пересобрать образ из исходников вместо pull, в docker-compose.yml временно замените блок image: на build: . (и нужный тег при необходимости).

На Linux без host.docker.internal сделайте одно из:

  • в docker-compose.yml для сервиса gateway добавьте extra_hosts: ["host.docker.internal:host-gateway"] (Docker Engine 20.10+);
  • либо замените в config.compose.yaml значение base_url на IP хоста в dockerbridge (часто 172.17.0.1) или на имя сервиса Telemt в той же сети compose.

Проверка

Сценарий Ожидание
GET /health 200, JSON {"status":"ok"}
GET / (образ с Web UI) 200, HTML панели
Разрешённый IP, корректный alias ответ бэкенда (например 200 для /v1/health)
IP не в whitelist 403, JSON с code: forbidden
Неизвестный alias 404, JSON с code: not_found
Бэкенд недоступен 502, JSON с code: bad_gateway
GET /metrics текст метрик Prometheus (при разрешённом IP)

Обновление и CI/CD

  • Образ: подтяните свежий тег (docker pull git.shts.su/denozord/telemt-api:latest или docker compose pull), пересоздайте контейнер (docker compose up -d или новый docker run с тем же volume конфига). Локальная пересборка нужна только если вы меняете Dockerfile/код и не пользуетесь CI. Образы, собранные до добавления стадии web/ в Dockerfile, могут отдавать на / только заглушку — нужен образ из актуального CI или локальный docker build.

  • Конфиг: отредактируйте файл на хосте и перезапустите контейнер (шлюз не перечитывает конфиг на лету).

  • Gitea Actions: workflow .gitea/workflows/docker.yaml сначала выполняет go mod tidy && go test ./..., затем собирает образ через Buildx по Dockerfile (стадии Node для web/ и Go) и пушит в Container Registry Gitea.

    • В репозитории должен быть secret ACTIONS_PAT — personal access token пользователя с правом write:package (и при необходимости read:package), как для обычного docker login к registry.
    • Логин в registry: пользователь gitea.actor (кто запустил workflow), пароль — этот PAT.
    • Хост registry задаётся в workflow в env.REGISTRY (по умолчанию git.shts.su); при другом инстансе Gitea измените значение в .gitea/workflows/docker.yaml.
    • Теги образа: latest, имя ветки/тега (с / заменённым на -), и sha-<первые 12 символов коммита>. Полный путь: {REGISTRY}/{gitea.repository}:<тег>.

Устранение неполадок

  • Nginx с location /api/ и proxy_pass http://…:9091/; (со слэшем в конце) на бэкенд уходит путь без префикса /api/ (например запрос к nginx GET /api/v1/users превращается в GET /v1/users на Telemt). Шлюз при base_url: https://gt2.example/api/ должен запрашивать именно /api/v1/… на стороне nginx. Если в base_url нет пути /api/ (только https://gt2.example), шлюз обратится к https://gt2.example/v1/… — часто это не попадает в location /api/, и nginx отдаёт чужой vhost / заглушку. Задавайте base_url с завершающим слэшем: https://gt2.example/api/.
  • Заголовок Host: шлюз выставляет Host равным хосту из base_url (как у обычного клиента к этому имени). Если после обновления образа проблема остаётся, с хоста шлюза проверьте: curl -sv -o /dev/null https://gt2…/api/v1/health и сравните с запросом через шлюз.
  • 400 на /api/{alias}/… при локальном base_url (например http://172.20.0.3:9091), хотя curl к :9091/v1/… даёт 200: частая причина — несовпадение заголовка Host: браузер шлёт Host: публичное_имя:8888, а при прямом curl к IP в Host попадает 172.20.0.3:9091. Строгий upstream (часто hyper/Rust) отвечает 400, если Host не совпадает с ожидаемым authority. Шлюз при проксировании не пересылает клиентский Host и выставляет authority из base_url (как серверные запросы агрегатора). Убедитесь также, что в YAML нет пробела/переноса в конце base_url (поля обрезаются TrimSpace), и в URL нет лишнего /api//… (путь под /api нормализуется).
  • 400 только на /api/{alias}/mihomo/…, при этом прямой curl к контроллеру Mihomo ок: см. Отладка 400 (Mihomo) в разделе Mihomo — REST к Mihomo идёт через тот же механизм, что и к Telemt; не путать с отдельным WebSocket-прокси.
  • Список пользователей через шлюз: запрос GET или HEAD на /api/{alias}/users шлюз перенаправляет на upstream GET/HEAD /v1/stats/users (как и агрегатор). Так совместимы сборки Telemt, где прямой GET /v1/users даёт ошибку (например 400), а /v1/stats/users работает. POST /api/{alias}/users (создание) и GET /api/{alias}/users/{username} по-прежнему идут на /v1/users и /v1/users/{username}. Явный путь /api/{alias}/stats/users не меняется. См. API.md.
  • docker pull: unauthorized / denied: выполните docker login git.shts.su с учётной записью Gitea и PAT с read:package.
  • 403 forbidden с хоста при allow_all: false: добавьте CIDR клиента в whitelist_cidrs. Запросы из контейнера к самому себе идут с 127.0.0.1 — при необходимости добавьте 127.0.0.1/32.
  • За reverse proxy: укажите CIDR прокси в trusted_proxies, иначе whitelist видит IP прокси, а не клиента.
  • 502 bad_gateway: проверьте base_url, DNS в Docker‑сети и то, что Telemt слушает API ([server.api].enabled=true и корректный listen).
  • Сборка Go без Docker: в корне репозитория выполните go mod tidy && go test ./....