Merge pull request 'fix(ops): закрепить рабочий адрес api.telegram.org на новом хосте' (#3060) from fix/telegram-pin-selectel-override into main
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 14s
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy / deploy (push) Successful in 1m34s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / deploy (push) Successful in 2m29s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 8s
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 14s
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy / deploy (push) Successful in 1m34s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / deploy (push) Successful in 2m29s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 8s
This commit is contained in:
commit
ccdd7553ba
2 changed files with 141 additions and 0 deletions
|
|
@ -206,6 +206,41 @@ eff_root="$(sshd -T 2>/dev/null | awk '/^permitrootlogin /{print $2}')"
|
|||
echo "итог: passwordauthentication=$eff_pass permitrootlogin=$eff_root"
|
||||
[ "$eff_pass" = "no" ] || die "PasswordAuthentication остался '$eff_pass' — какой-то файл в sshd_config.d перебивает наш; НЕ перезапускаю"
|
||||
|
||||
log "11. Закрепление рабочего адреса api.telegram.org"
|
||||
# Замер 2026-08-23 с этого хоста: у api.telegram.org семь публикуемых адресов,
|
||||
# отвечает РОВНО ОДИН — 149.154.167.220. Резолвер при этом отдаёт 149.154.166.110,
|
||||
# который мёртв. Скан всего 149.154.167.0/24 подтвердил: живой он один во всём блоке.
|
||||
# Это не блокировка Selectel — с Beget отвечают три адреса из тех же семи, просто
|
||||
# там штатный резолвер попадает на живой. Через прокси ASocks Telegram тоже не
|
||||
# проходит (все четыре узла — 000), так что обход только через закрепление IP.
|
||||
#
|
||||
# Контейнерам это даёт tradein-mvp/docker-compose.selectel.yml (extra_hosts для
|
||||
# tgbot и backend). Здесь — для ХОСТОВЫХ скриптов, которым compose не помогает:
|
||||
# ops/lib-backup.sh (уведомления о бэкапах) и ops/uptime-healthcheck.sh.
|
||||
# Без этого они молча перестают слать алерты — а это ровно тот канал, которым
|
||||
# мы узнали бы о любой другой поломке.
|
||||
TELEGRAM_API_IP="${TELEGRAM_API_IP:-149.154.167.220}"
|
||||
if grep -qE '^[0-9.]+[[:space:]]+api\.telegram\.org$' /etc/hosts; then
|
||||
sed -i -E "s|^[0-9.]+([[:space:]]+api\.telegram\.org)$|${TELEGRAM_API_IP}\1|" /etc/hosts
|
||||
echo "запись обновлена -> ${TELEGRAM_API_IP}"
|
||||
else
|
||||
echo "${TELEGRAM_API_IP} api.telegram.org" >> /etc/hosts
|
||||
echo "запись добавлена -> ${TELEGRAM_API_IP}"
|
||||
fi
|
||||
# Проверяем не сам факт записи, а что Bot API реально отвечает: 401 на заведомо
|
||||
# фиктивный токен означает, что до API достучались и он нас понял.
|
||||
tg_code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 \
|
||||
https://api.telegram.org/bot000000:FakeProbeToken/getMe 2>/dev/null || echo 000)"
|
||||
if [ "$tg_code" = "401" ]; then
|
||||
echo "проверка: Bot API отвечает (401 на фиктивный токен) — закрепление работает"
|
||||
else
|
||||
echo "ВНИМАНИЕ: Bot API вернул '$tg_code' вместо 401 — адрес ${TELEGRAM_API_IP} мог умереть."
|
||||
echo " Найди живой: for ip in \$(seq 1 254); do curl -s -o /dev/null --max-time 3 \\"
|
||||
echo " --resolve api.telegram.org:443:149.154.167.\$ip -w \"149.154.167.\$ip %{http_code}\\n\" \\"
|
||||
echo " https://api.telegram.org/; done | grep 302"
|
||||
echo " и пропиши его в TELEGRAM_API_IP (здесь и в .env.runtime для контейнеров)."
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
============================================================
|
||||
|
|
|
|||
106
tradein-mvp/docker-compose.selectel.yml
Normal file
106
tradein-mvp/docker-compose.selectel.yml
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# 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.
|
||||
Loading…
Add table
Reference in a new issue