fix(ops): закрепить рабочий адрес api.telegram.org на новом хосте #3060

Merged
lekss361 merged 1 commit from fix/telegram-pin-selectel-override into main 2026-08-23 20:41:22 +00:00
Owner

Снимает блокер переезда из #3059. Окно 30.08.

Что обнаружено замером

У api.telegram.org семь публикуемых адресов. С Poincare отвечает ровно один:

IP с Beget с Poincare
149.154.167.220 302 (0.17 с) 302 (0.11 с)
149.154.166.110 ← отдаёт резолвер 302 (0.22 с) 000
149.154.167.99 302 (0.86 с) 000
149.154.175.100 / 149.154.171.5 / 91.108.4.5 / 91.108.56.130 000 000

Скан всего 149.154.167.0/24 подтвердил: живой адрес один во всём блоке.

Это не блокировка Selectel и не наш файрвол. С Beget отвечают три адреса из тех же семи, включая тот, что отдаёт DNS. Telegram частично фильтруется у обоих провайдеров, просто Beget попадает на живой адрес через штатный резолвер, а Selectel нет.

Через прокси ASocks Telegram не проходит ни с одного из четырёх узлов (все 000) — узлы российские. Этот путь закрыт, обход только через закрепление IP.

Чем это грозило

tradein-tgbot работает на long-polling: сам ходит наружу, входящих не слушает. Резолвит мёртвый адрес, виснет на таймауте, restart: unless-stopped поднимает заново, он снова резолвит тот же адрес и снова виснет. Ни краша, ни строки в логе.

Вместе с ботом молча ложится канал, которым мы узнали бы о любой другой поломке — уведомления о бэкапах и uptime-нотификации.

Закреплены все три потребителя, а не только бот

Разведка показала, что в Telegram ходит не один сервис. Починить бота и оставить остальных значило бы сделать треть и выглядеть готовым.

Потребитель Где Чем закрыт
tradein-tgbot контейнер extra_hosts в новом docker-compose.selectel.yml
tradein-backend контейнер там же — api/v1/glitchtip.py (пересылка алертов GlitchTip) и api/v1/support.py тоже бьют httpx в api.telegram.org
ops/lib-backup.sh, ops/uptime-healthcheck.sh хост, из cron шаг 11 в selectel-bootstrap.sh — запись в /etc/hosts; extra_hosts им не помогает

backend ломается заметнее бота — per-request, а не long-poll, — но ломается, и вместе с ним пересылка алертов.

Почему отдельный файл, а не правка docker-compose.prod.yml

На Beget закрепление не нужно: там резолвер отдаёт рабочий адрес. Менять поведение действующего прода ради хоста, который ещё не в проде, нельзя. Оверрайд просто не передаётся в -f — нулевой риск.

Применяется на новом хосте так:

docker compose -p gendesign \
  -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.selectel.yml up -d

Адрес — переменная, а не константа

TELEGRAM_API_IP с дефолтом 149.154.167.220. Если он умрёт, правка — одна переменная окружения и --force-recreate, без релиза.

Шаг bootstrap проверяет не факт записи в /etc/hosts, а что Bot API реально отвечает: 401 на заведомо фиктивный токен означает, что до API достучались. При другом коде печатает готовую команду поиска нового живого адреса по подсети.

Проверка

docker compose config в двух вариантах, diff:

@@ -31 (сервис backend, container_name: tradein-backend)
+    extra_hosts:
+      - api.telegram.org=149.154.167.220
@@ -205 (сервис tgbot, container_name: tradein-tgbot)
+    extra_hosts:
+      - api.telegram.org=149.154.167.220

Ровно две вставки, больше ничего не изменилось. С TELEGRAM_API_IP=1.2.3.4 подстановка срабатывает в обеих. bash -n на bootstrap чистый.

⚠️ Точка отказа названа осознанно

149.154.167.220 — единственный живой адрес во всём /24 с этого хоста. Запасного нет. Если он умрёт, бот снова умрёт молча, а канал оповещения об этом сам идёт через Telegram — поломка скрывает сама себя.

Поэтому закрепление необходимо, но не достаточно. Нужен независимый канал алертов. Кандидат проверен: с Poincare smtp.beget.com:465 открыт (587 закрыт), плюс GlitchTip остаётся на Beget и доступен по имени. Это отдельная задача, в этот PR не входит — но без неё риск остаётся.

Refs #3059, #3057, #2989

Снимает блокер переезда из #3059. Окно 30.08. ## Что обнаружено замером У `api.telegram.org` семь публикуемых адресов. **С Poincare отвечает ровно один:** | IP | с Beget | с Poincare | |---|---|---| | `149.154.167.220` | 302 (0.17 с) | **302 (0.11 с)** | | `149.154.166.110` ← отдаёт резолвер | 302 (0.22 с) | **000** | | `149.154.167.99` | 302 (0.86 с) | 000 | | `149.154.175.100` / `149.154.171.5` / `91.108.4.5` / `91.108.56.130` | 000 | 000 | Скан всего `149.154.167.0/24` подтвердил: живой адрес один во всём блоке. **Это не блокировка Selectel и не наш файрвол.** С Beget отвечают три адреса из тех же семи, включая тот, что отдаёт DNS. Telegram частично фильтруется у обоих провайдеров, просто Beget попадает на живой адрес через штатный резолвер, а Selectel нет. Через прокси ASocks Telegram **не проходит ни с одного из четырёх узлов** (все `000`) — узлы российские. Этот путь закрыт, обход только через закрепление IP. ## Чем это грозило `tradein-tgbot` работает на long-polling: сам ходит наружу, входящих не слушает. Резолвит мёртвый адрес, виснет на таймауте, `restart: unless-stopped` поднимает заново, он снова резолвит тот же адрес и снова виснет. **Ни краша, ни строки в логе.** Вместе с ботом молча ложится канал, которым мы узнали бы о любой другой поломке — уведомления о бэкапах и uptime-нотификации. ## Закреплены все три потребителя, а не только бот Разведка показала, что в Telegram ходит не один сервис. Починить бота и оставить остальных значило бы сделать треть и выглядеть готовым. | Потребитель | Где | Чем закрыт | |---|---|---| | `tradein-tgbot` | контейнер | `extra_hosts` в новом `docker-compose.selectel.yml` | | `tradein-backend` | контейнер | там же — `api/v1/glitchtip.py` (пересылка алертов GlitchTip) и `api/v1/support.py` тоже бьют httpx в `api.telegram.org` | | `ops/lib-backup.sh`, `ops/uptime-healthcheck.sh` | **хост**, из cron | шаг 11 в `selectel-bootstrap.sh` — запись в `/etc/hosts`; `extra_hosts` им не помогает | `backend` ломается заметнее бота — per-request, а не long-poll, — но ломается, и вместе с ним пересылка алертов. ## Почему отдельный файл, а не правка `docker-compose.prod.yml` На Beget закрепление не нужно: там резолвер отдаёт рабочий адрес. Менять поведение действующего прода ради хоста, который ещё не в проде, нельзя. Оверрайд просто не передаётся в `-f` — нулевой риск. Применяется на новом хосте так: ``` docker compose -p gendesign \ -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.selectel.yml up -d ``` ## Адрес — переменная, а не константа `TELEGRAM_API_IP` с дефолтом `149.154.167.220`. Если он умрёт, правка — одна переменная окружения и `--force-recreate`, **без релиза**. Шаг bootstrap проверяет не факт записи в `/etc/hosts`, а что Bot API реально отвечает: `401` на заведомо фиктивный токен означает, что до API достучались. При другом коде печатает готовую команду поиска нового живого адреса по подсети. ## Проверка `docker compose config` в двух вариантах, diff: ```diff @@ -31 (сервис backend, container_name: tradein-backend) + extra_hosts: + - api.telegram.org=149.154.167.220 @@ -205 (сервис tgbot, container_name: tradein-tgbot) + extra_hosts: + - api.telegram.org=149.154.167.220 ``` Ровно две вставки, больше ничего не изменилось. С `TELEGRAM_API_IP=1.2.3.4` подстановка срабатывает в обеих. `bash -n` на bootstrap чистый. ## ⚠️ Точка отказа названа осознанно `149.154.167.220` — единственный живой адрес во всём `/24` с этого хоста. Запасного нет. Если он умрёт, бот снова умрёт молча, **а канал оповещения об этом сам идёт через Telegram** — поломка скрывает сама себя. Поэтому закрепление **необходимо, но не достаточно**. Нужен независимый канал алертов. Кандидат проверен: с Poincare `smtp.beget.com:465` открыт (587 закрыт), плюс GlitchTip остаётся на Beget и доступен по имени. Это отдельная задача, в этот PR не входит — но без неё риск остаётся. Refs #3059, #3057, #2989
lekss361 added 1 commit 2026-08-23 20:33:30 +00:00
fix(ops): закрепить рабочий адрес api.telegram.org на новом хосте
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
05e82989a8
Замер 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
lekss361 merged commit ccdd7553ba into main 2026-08-23 20:41:22 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3060
No description provided.