gendesign/tradein-mvp/docker-compose.selectel.yml
bot-backend 05e82989a8
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
fix(ops): закрепить рабочий адрес api.telegram.org на новом хосте
Замер 2026-08-23 с Poincare: у api.telegram.org семь публикуемых адресов,
отвечает РОВНО ОДИН — 149.154.167.220 (302 за 0.11 с). Резолвер при этом
отдаёт 149.154.166.110, который мёртв. Скан всего 149.154.167.0/24
подтвердил: живой он один во всём блоке.

Это не блокировка Selectel и не наш файрвол: с Beget отвечают три адреса
из тех же семи, включая тот, что отдаёт DNS там. Telegram частично
фильтруется у ОБОИХ провайдеров, просто Beget попадает на живой адрес.
Через прокси ASocks Telegram не проходит ни с одного из четырёх узлов
(все 000) — узлы российские, путь закрыт.

Без закрепления tradein-tgbot на long-polling резолвит мёртвый адрес,
виснет и умирает МОЛЧА: restart поднимает его заново, он снова виснет,
ни краша, ни строки в логе.

Закреплены все три потребителя Telegram, а не только бот:
- tgbot и backend — extra_hosts в новом docker-compose.selectel.yml.
  backend тоже ходит в Telegram: пересылка алертов GlitchTip
  (api/v1/glitchtip.py) и support-чат (api/v1/support.py).
- хостовые скрипты ops/lib-backup.sh и ops/uptime-healthcheck.sh —
  шаг 11 в selectel-bootstrap.sh кладёт запись в /etc/hosts.
  Им compose не помогает, они идут из cron, не из контейнера.

Отдельный override-файл, а не правка docker-compose.prod.yml: на Beget
закрепление не нужно, и менять поведение действующего прода ради
будущего хоста нельзя. Файл просто не передаётся в -f.

Адрес вынесен в TELEGRAM_API_IP с текущим дефолтом — если он умрёт,
правка в одну переменную окружения без релиза. Шаг bootstrap проверяет
не факт записи, а что Bot API отвечает 401 на фиктивный токен, и при
другом коде печатает команду поиска нового живого адреса.

Проверено: docker compose config даёт ровно две вставки extra_hosts
(tradein-backend, tradein-tgbot) и ничего больше; TELEGRAM_API_IP
подставляется в обе.

Refs #3059, #3057
2026-08-23 23:32:35 +03:00

106 lines
9.1 KiB
YAML
Raw 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.

# Selectel-only override: закрепляет рабочий IP api.telegram.org для tgbot.
#
# Применяется ТОЛЬКО на новом выделенном сервере (Selectel Poincare,
# 188.246.224.93, переезд 30.08.2026), поверх обычного prod-стека:
#
# docker compose -p gendesign \
# -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.selectel.yml up -d
#
# На Beget этот файл НЕ подключается — там резолвер api.telegram.org отдаёт
# один из рабочих адресов и закрепление не нужно (см. замер ниже). Отдельный
# override, а не правка docker-compose.prod.yml, потому что:
# 1. Это host-specific обход, а не общее свойство сервиса — размазывать его
# по общему prod-файлу значит менять поведение действующего прода
# (Beget) ради проблемы хоста, который ещё не в проде.
# 2. Нулевой риск для Beget: файл просто не передаётся в -f, никакого
# shared-состояния между хостами через этот механизм нет.
# 3. Если Selectel впоследствии станет единственным прод-хостом (после
# 30.08) и обход всё ещё нужен — тогда его можно будет слить в
# docker-compose.prod.yml осознанно, а не молча "заодно".
#
# ── Почему это вообще нужно (замер 2026-08-23, с нового хоста) ─────────────
# У api.telegram.org семь публикуемых адресов. С Selectel отвечает РОВНО ОДИН:
#
# 149.154.167.220 -> 302 за 0.11 с (единственный живой)
# 149.154.166.110 -> 000 <- именно его отдаёт резолвер хоста!
# 149.154.167.99 -> 000
# 149.154.175.100 -> 000
# 149.154.171.5 -> 000
# 91.108.4.5 -> 000
# 91.108.56.130 -> 000
#
# Сканом всей подсети 149.154.167.0/24 с этого же хоста подтверждено, что
# 149.154.167.220 — единственный отвечающий адрес Telegram во всём блоке, не
# только среди этих семи. Это НЕ блокировка Selectel и не наш файрвол: с
# Beget отвечают три адреса из тех же семи (включая тот, что отдаёт DNS там),
# т.е. Telegram частично фильтруется у ОБОИХ провайдеров по-разному — Beget
# просто попадает на живой адрес через штатный резолвер, а Selectel нет.
#
# Через прокси (ASocks, все четыре узла) Telegram тоже не проходит — все
# запросы 000, узлы российские. Этот путь закрыт, обход только через IP.
#
# Проверено закрепление (прежде чем закреплять в compose):
# - на хосте через /etc/hosts: curl https://api.telegram.org/ -> 302, 0.11 с
# - настоящий Bot API (/bot<fake>/getMe) -> {"ok":false,"error_code":401} за
# 0.11 с (для сравнения — с Beget тот же ответ приходит за 0.25 с, т.е.
# закреплённый адрес не просто живой, а ещё и быстрее дефолтного пути)
# - В КОНТЕЙНЕРЕ через docker --add-host -> 302 за 0.125 с (подтверждает,
# что extra_hosts ниже — не отличается от прямой проверки на хосте)
# - стабильность: 5 запросов подряд -> 302 302 302 302 302, без единого сбоя
#
# ── Что ломается без этого обхода ───────────────────────────────────────────
# tradein-tgbot — long-polling воркер (см. docker-compose.prod.yml, сервис
# tgbot): он не слушает входящих соединений, а сам постоянно ходит наружу к
# api.telegram.org. Если резолвер отдаёт мёртвый адрес, httpx виснет на
# таймауте long-poll'а и процесс тихо умирает — restart: unless-stopped его
# поднимает заново, он снова резолвит тот же мёртвый адрес и снова виснет.
# Никакого явного краша или строки в логе, по которой это легко поймать —
# отсюда и требование закрепить адрес ДО переезда, а не разбираться постфактum.
#
# Вместе с ботом молча ложатся:
# - ops/lib-backup.sh — уведомления об успехе/провале бэкапов в Telegram
# - ops/uptime-healthcheck.sh — uptime-нотификации
# (обаХОСТ-скрипты, не контейнеры; extra_hosts на них не действует, этот
# файл их не чинит — см. TODO в конце файла).
#
# ── TELEGRAM_API_IP: почему переменная, а не жёсткий IP ─────────────────────
# Google/Cloudflare-класса анycast у Telegram нет: их адреса — обычные
# датацентровые IP, которые Telegram время от времени меняет. Если
# 149.154.167.220 однажды тоже станет мёртвым (или Selectel поменяет
# маршрутизацию и он перестанет быть единственным живым), правка — это
# одна строка в .env.runtime (TELEGRAM_API_IP=<новый адрес>) и
# `docker compose ... up -d --force-recreate --no-deps tgbot`, БЕЗ релиза и
# без правки этого файла. Дефолт ниже (:-149.154.167.220) — текущий
# подтверждённый живой адрес, применяется если переменная не задана.
# ── Почему ДВА сервиса, а не только бот ─────────────────────────────────────
# В api.telegram.org ходит не только tgbot. Внутри контейнера backend это
# делают ещё два пути:
# - app/api/v1/glitchtip.py — пересылка алертов GlitchTip в Telegram-тему
# - app/api/v1/support.py — support-чат
# Оба бьют httpx напрямую в api.telegram.org из того же контейнера, что и
# API. Ломаются они ЗАМЕТНЕЕ бота (не long-poll, а per-request: конкретный
# запрос отваливается по таймауту), но ломаются — и, что важнее, ломается
# пересылка алертов, то есть ещё один канал, которым мы узнали бы о беде.
# Закреплять адрес только боту значило бы починить треть и выглядеть готовым.
services:
tgbot:
extra_hosts:
- "api.telegram.org:${TELEGRAM_API_IP:-149.154.167.220}"
backend:
extra_hosts:
- "api.telegram.org:${TELEGRAM_API_IP:-149.154.167.220}"
# ── Хостовая половина — закрыта в ops/selectel-bootstrap.sh ─────────────────
# ops/lib-backup.sh и ops/uptime-healthcheck.sh исполняются НА ХОСТЕ (cron,
# не в docker), поэтому extra_hosts им не помогает. Их закрывает шаг 11
# bootstrap-скрипта: он кладёт ту же запись в системный /etc/hosts, идемпотентно
# и с той же переменной TELEGRAM_API_IP. Итого закреплены все три потребителя:
# tgbot, backend и хостовые скрипты.
#
# ⚠️ ОДНА ТОЧКА ОТКАЗА, ЗАФИКСИРОВАНА ОСОЗНАННО. 149.154.167.220 — единственный
# живой адрес во всём 149.154.167.0/24 с этого хоста. Если он умрёт, бот снова
# умрёт молча, а канал оповещения об этом сам идёт через Telegram — поломка
# скрывает сама себя. Поэтому закрепление НЕОБХОДИМО, НО НЕ ДОСТАТОЧНО: нужен
# независимый канал алертов. Проверено, что с нового хоста доступен
# smtp.beget.com:465 (порт 587 закрыт), плюс GlitchTip остаётся на Beget и
# доступен по имени. Отдельная задача, см. #3059.