MAATRIX / Блог / Anycast для DNS, обычный IP для сайта: почему части одного проекта живут по-разному

Anycast для DNS, обычный IP для сайта: почему части одного проекта живут по-разному

MAATRIX

Заказываете DNS-хостинг у любого крупного провайдера — и его NS-серверы уже anycast, без единого действия с вашей стороны. Заказываете VPS под сайт — получаете один обычный IP в одном дата-центре, и если нужна гео-распределённость, придётся строить её самому через GeoDNS или балансировщик. Разница не случайна и не в том, что для сайта «пожалели» anycast: у DNS-запроса и у сессии веб-приложения принципиально разная природа, и она напрямую диктует, какая архитектура тут работает, а какая ломается в проде в первую же неделю.

DNS-запрос и HTTP-сессия — принципиально разные по природе

DNS-запрос — это, в подавляющем большинстве случаев, один UDP-пакет с вопросом и один UDP-пакет с ответом. Клиент спросил A example.com, резолвер ответил IP-адресом — и на этом транзакция закончена. Никакого «состояния» между запросом и ответом не хранится: сервер не помнит, кто и когда его спрашивал минуту назад, не ведёт сессию, не привязывает клиента к себе. Каждый запрос самодостаточен и обрабатывается независимо от всех остальных.

Запрос к веб-приложению устроен иначе. HTTP поверх TCP — это уже как минимум установленное соединение (three-way handshake, номера последовательности, таймауты), а поверх него почти всегда есть логика уровня приложения: авторизационная кука, серверная сессия, корзина покупок, WebSocket-канал, который живёт часами. Это состояние физически хранится на конкретном сервере — либо в памяти процесса, либо в локальном кеше, — и не «телепортируется» на другую машину просто потому, что маршрут в сети немного изменился.

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

Почему statelessness делает anycast идеальным для DNS

Ключевое свойство, которое делает DNS удобным кандидатом для anycast — не столько «короткий запрос», сколько то, что любой из anycast-узлов может дать абсолютно одинаковый, корректный ответ. Все узлы anycast-сети DNS-провайдера обслуживают одну и ту же зону с синхронизированными данными — если у вас A-запись 203.0.113.10, её отдаст и узел в Москве, и узел в Сингапуре, и узел в Сан-Паулу. Клиенту, по сути, всё равно, какой физический сервер ответил, лишь бы ответ был правильным и быстрым.

Из этого прямо следует терпимость к смене маршрута. Если два последовательных DNS-запроса от одного клиента (например, к разным доменам, или повторный запрос после истечения TTL) попадут на два разных anycast-узла — ничего не сломается: запросы независимы, состояние между ними не нужно, ответ будет корректным в любом случае. Даже если маршрут поменяется буквально между отправкой запроса и приходом ответа (редкий, но теоретически возможный сценарий route flap) — UDP не заметит разницы на уровне транспорта, а если пакет всё-таки потеряется, резолвер просто переспросит: у DNS изначально заложен повтор при таймауте, это штатное поведение протокола, а не аварийная ситуация.

Сравните это с TCP-сессией к веб-серверу: смена маршрута посреди активного соединения — это не «просто ещё один independent запрос», а разрыв единственного канала, в котором жило состояние. Восстановить его на другом узле нечем, если только само приложение не спроектировано это учитывать (об этом ниже).

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Как это выглядит на практике: NS-записи и публичные резолверы

Проверить это самостоятельно можно за минуту. Запросите NS-записи любого крупного домена и посмотрите, куда они указывают:

dig +short NS cloudflare.com
dig +short NS google.com

А затем прогоните mtr до одного из публичных DNS-резолверов с двух разных серверов — если они у вас есть в разных регионах:

# с сервера в РФ
mtr -rw -c 20 1.1.1.1

# с сервера в США
mtr -rw -c 20 1.1.1.1

Финальные хопы почти наверняка будут разными — это разные физические точки одной anycast-сети, а не один сервер «на всех». Само устройство этой маршрутизации через BGP подробно разобрано в статье как работает anycast и почему один IP отвечает из разных стран — здесь не буду повторять механику, важнее сам факт: практически все крупные публичные DNS-сервисы (резолверы вроде 1.1.1.1, 8.8.8.8, 9.9.9.9, корневые серверы интернета, авторитативные NS большинства крупных DNS-хостингов) работают именно так. Это не экзотика и не признак «продвинутого» провайдера — это настолько естественный выбор для DNS, что вы, скорее всего, уже им пользуетесь, даже не подозревая об этом: NS-серверы вашего собственного домена почти наверняка anycast, если вы используете DNS-хостинг любого сколько-нибудь крупного регистратора или облачного провайдера.

Веб-приложение — это состояние, которое живёт на одном сервере

У обычного веб-приложения на арендованном VPS состояние копится на нескольких уровнях сразу:

  • TCP-соединение. Номера последовательности, окно, таймауты — всё это привязано к конкретной паре сокетов на конкретной машине. Соединение физически не существует «нигде ещё», кроме как на том сервере, где оно было установлено.
  • Серверная сессия. Если сессии хранятся в памяти процесса (а не вынесены во внешнее хранилище вроде Redis), то данные о залогиненном пользователе, его корзине или состоянии формы физически лежат в оперативной памяти одного конкретного сервера.
  • WebSocket и другие долгоживущие соединения. Чат, live-обновления, стриминг — это TCP-соединение, которое сознательно держат открытым часами. Чем дольше оно живёт, тем выше вероятность попасть под любое изменение сетевого маршрута за это время.

Всё это — прямая противоположность DNS-запросу. Здесь клиент не может «спросить у любого узла и получить одинаковый ответ»: конкретный узел — это единственное место, где вообще есть данные, нужные для ответа.

Что ломается, если anycast «перекинет» TCP-сессию на другой узел

Представим, что вы всё же анонсировали IP веб-сервера через anycast из двух дата-центров без какой-то специальной прослойки поверх (то есть буквально голый TCP/HTTP-сервер за anycast-адресом). Пока маршрут стабилен, всё работает: конкретный клиент попадает на один и тот же узел на всём протяжении сессии, потому что BGP-таблицы у его провайдера не меняются каждую секунду.

Проблема начинается при route flap — изменении анонса из-за нестабильности пиринга, отказа узла, ребалансировки трафика у апстрима клиента. Если это происходит посреди активной сессии:

  • следующий пакет TCP-потока прилетает на физически другой сервер;
  • этот сервер ничего не знает про установленное соединение — у него нет ни номеров последовательности, ни состояния сессии;
  • с высокой вероятностью TCP-стек отвечает сбросом соединения (RST), потому что пакет не соответствует ни одному известному сокету;
  • клиент теряет соединение целиком: разлогинивается, теряет заполненную форму или корзину, WebSocket-канал рвётся и его нужно переустанавливать заново.

Для DNS аналогичное событие в худшем случае означает, что клиент чуть дольше ждёт ответа и резолвер делает повторный запрос — операция, которая по протоколу и так рассчитана на повтор. Для веб-приложения то же самое сетевое событие означает разрыв единственного канала, в котором жило всё состояние пользователя — и это не крайний случай, а прямое следствие того, что TCP и HTTP-сессия принципиально не спроектированы «плавать» между разными физическими серверами без специальной поддержки на уровне приложения.

Именно поэтому голый anycast без дополнительной инфраструктуры — плохой выбор для собственного веб-сервера на арендованных мощностях: риск обрыва растёт вместе с длительностью сессии, а средств починить это на уровне одного VPS у вас просто нет.

Практика: unicast + GeoDNS или балансировка на уровне приложения

Если гео-распределённость всё же нужна — часть аудитории в России, часть в Европе или США, и хочется снизить задержку для каждого региона — рабочий путь для проекта на арендованных серверах выглядит иначе, чем anycast:

  1. Unicast IP в каждом регионе. Разверните по серверу в ключевых для аудитории локациях — обычный, предсказуемый адрес, который весь трафик из любой точки мира при прямом обращении будет доставлять именно туда.
  2. GeoDNS поверх них. DNS-сервис отвечает разными IP в зависимости от того, откуда пришёл запрос — пользователь из России получает адрес российского сервера, пользователь из США — американского. Переключение происходит на уровне резолвинга, а не BGP, и клиент один раз получает «свой» адрес на весь TTL записи, а не рискует посреди сессии оказаться на другом узле.
  3. Или балансировщик уровня приложения, если нужна более тонкая логика, чем просто «ближайший регион» — например, health check с автоматическим исключением упавшего узла, распределение по нагрузке, а не только по географии.

Три рабочие схемы такой балансировки — GeoDNS, anycast только как частный инструмент внутри инфраструктуры провайдера, и L7-балансировщик с failover — вместе с типичными граблями (рассинхронизация данных между регионами, потерянные сессии при переключении) подробно разобраны в статье балансировка между серверами в разных странах. Там же стоит учитывать, что DNS-переключение — не мгновенное: даже при коротком TTL реальное время переключения клиентов часто оказывается больше ожидаемого из-за кеширования на стороне резолверов и провайдеров, это разобрано в статье про DNS failover, который срабатывает медленнее обещанного.

Условный пример GeoDNS-конфигурации (синтаксис зависит от конкретного DNS-провайдера):

; политика по региону запроса
region: russia   -> A 195.xxx.xxx.10   ; сервер в РФ
region: europe   -> A 46.xxx.xxx.20    ; сервер в ЕС
region: default  -> A 195.xxx.xxx.10   ; fallback

Если задача — не столько снизить задержку, сколько удержать сессию при переключении между узлами (например, для L7-балансировщика перед несколькими бэкендами в одном регионе), добавляют привязку по cookie или client IP на уровне самого балансировщика:

# пример affinity на HAProxy — привязка клиента к бэкенду по cookie
backend web_backends
    cookie SRV insert indirect nocache
    server web1 10.0.1.10:80 check cookie web1
    server web2 10.0.1.11:80 check cookie web2

Это не отменяет полностью риск потери состояния при аварийном переключении на другой узел — но переносит его в контролируемую плоскость приложения, где вы сами решаете, что делать с сессией (хранить её во внешнем Redis, форсировать повторный логин, восстанавливать WebSocket с реконнектом), а не отдаёте это на волю случайного BGP-флапа.

Исключение: anycast для веб-трафика у крупных CDN

Здесь стоит быть честным: anycast для HTTP-трафика в реальности существует и активно используется — просто не в виде голого TCP-сервера за anycast-адресом, а как часть инфраструктуры крупных CDN и anti-DDoS сервисов (Cloudflare, Fastly, Akamai и подобные). Общая идея, без деталей конкретных реализаций (они у каждого провайдера свои и не публикуются полностью): anycast там используется на первом шаге — «доставить пакет клиента до ближайшего edge-узла», а дальше, уже внутри инфраструктуры провайдера, подключается специальная логика более высокого уровня, которая старается сохранять состояние сессии даже при изменении сетевого пути — например, за счёт того, что решение о маршруте на своей внутренней сети провайдер контролирует куда стабильнее, чем произвольный BGP-флап у случайного транзитного провайдера, плюс терминация TCP/TLS происходит прямо на edge-узле, а не «где повезёт».

Это инфраструктура уровня сетевого оператора со своим AS-номером, десятками точек присутствия и годами инженерной работы над тем, чтобы anycast и TCP-состояние не конфликтовали. Повторить это самостоятельно на паре арендованных VPS невозможно и не нужно — сравнение того, что даёт такая инфраструктура и что реально достижимо на обычном сервере, подробно разобрано в статье anycast и обычный сервер: разница. Практический вывод для проекта на VPS: если нужна защита от нагрузки и атак на уровне сети — подключайте готовый anti-DDoS/CDN сервис, который anycast использует «под капотом» на своей стороне, а не пытайтесь строить его сами поверх собственного unicast-сервера.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли поставить свой веб-сервер за anycast-адрес, если очень хочется?

Технически да, если у вас есть свой AS-номер, блок IP и BGP-пиринг в нескольких дата-центрах — но без дополнительной прикладной логики для сохранения состояния сессии вы получите случайные обрывы TCP-соединений при каждом изменении маршрута. Для типового проекта на арендованных VPS это избыточная и рискованная конструкция.

А если мой сайт вообще без сессий — просто отдаёт статику?

Для чисто статического контента без cookie, авторизации и состояния риск от anycast меньше, потому что каждый HTTP-запрос ближе по природе к DNS — но TCP-соединение всё равно может оборваться посреди передачи файла при route flap, просто последствия не такие болезненные, как потеря сессии. Здесь как раз работает готовый CDN, а не собственный anycast.

Почему тогда мой DNS-провайдер не спрашивает меня, нужен ли мне anycast — он просто есть?

Потому что для DNS-инфраструктуры это стандартная практика провайдера, а не опция для клиента: раздача зоны через anycast — это то, как провайдер строит свою собственную сеть NS-серверов, вы получаете это бесплатно вместе с DNS-хостингом, без какой-либо настройки со своей стороны.

GeoDNS даёт такую же скорость переключения, как anycast?

Нет. Anycast переключает маршрут на уровне сети почти мгновенно (в терминах BGP-конвергенции), а GeoDNS ограничен TTL записи и кешированием на стороне резолверов — реальное время переключения клиентов обычно заметно больше, чем можно было бы ожидать по одному TTL. Зато GeoDNS не требует собственной AS-инфраструктуры и разворачивается за часы на обычных unicast-серверах.

Что делать, если WebSocket-соединение всё-таки нужно держать через несколько регионов?

Не пытайтесь балансировать активное WebSocket-соединение между узлами на лету — вместо этого закладывайте на клиенте логику переподключения (reconnect) с восстановлением состояния из внешнего хранилища (Redis, база), а маршрутизацию делайте один раз при установке соединения через GeoDNS или L7-балансировщик, а не через anycast поверх самого веб-сервера.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →