Два направления трафика, один узел
Добро пожаловать
Большинство архитектурных диаграмм показывают трафик в одном направлении: клиент сверху, сервер снизу, стрелка направлена вниз. В реальности трафик движется в обоих направлениях.
Входящий трафик (Ingress): внешние клиенты получают доступ к вашим сервисам через этот путь. Обратный прокси на границе вашей сети завершает TLS-соединения, маршрутизирует запросы и применяет политики доступа.
Исходящий трафик (Egress): ваши сервисы получают доступ к внешним сервисам через этот путь. Вызов API платежного провайдера, получение данных по адресу вебхука, отправка запроса партнеру. Часто через прямой прокси или NAT-шлюз с списком разрешений.
Многие архитектуры начинаются с одного узла, обрабатывающего оба направления. Это работает, пока не наступит день, когда перестанет. Режим отказа незаметен, проявляется только после того, как накопится достаточно внутренних сервисов, & преподает важный урок о разделении ответственности.
К концу этого урока вы поймете:
- Почему входящий (ingress) & исходящий (egress) трафик представляют собой фундаментально разные паттерны с различными осями масштабирования & режимами отказов
- Hairpin NAT & почему прокси, пытающийся подключиться к самому себе, не может это сделать
- Архитектурная развилка: один узел становится двумя, & что каждый из них теперь контролирует исключительно
- Прирост изоляции безопасности: каждая сторона может ограничить доступ только своими реальными разрешенными пиров
- Как определить, когда ваш одноузловой дизайн перешел порог, за которым разделение становится необходимым
Почему направления требуют разных инструментов
Два различных типа нагрузки на одной сетевой границе
Характеристики входящего трафика:
- Инициируется внешними сторонами (в масштабах всего интернета)
- Объем масштабируется в соответствии с вашей пользовательской базой
- Завершение TLS, маршрутизация запросов, ограничение частоты запросов по источнику
- Вопросы защиты в глубину: DDoS, злоупотребления, скрапинг
- Публичный IP-адрес должен принимать соединения от любого источника
Характеристики исходящего трафика:
- Инициируются вашими собственными сервисами (известный, небольшой набор клиентов)
- Объем зависит от паттернов вызовов между вашими сервисами и внешних API
- Разрешение по IP-адресам источников на удаленных конечных точках (у вас есть один фиксированный исходящий IP, которому доверяют партнеры)
- Вопросы многоуровневой защиты: утечка данных, скомпрометированные внутренние сервисы, инициирующие исходящие вызовы
- Должен отклонять соединения от всех, кроме ваших собственных сервисов
Ключевая асимметрия: входящий трафик (ingress) принимает соединения от всего мира; исходящий трафик (egress) принимает соединения только от ваших собственных сервисов. Размещение их на одном узле означает, что этот узел должен одновременно быть доступным извне (для входящего трафика) и доступен только вашим сервисам (для исходящего). Правила файрвола, удовлетворяющие одному требованию, противоречат другому.
Путь развития: небольшой проект может скрыть оба типа за одним IP и одним инструментом, так как объем мал, а список разрешенных IP-адресов партнеров короткий. По мере роста проекта трение между этими двумя ролями увеличивается, и однажды конкретный сценарий сбоя (hairpin NAT) вынуждает разделить их.
Баг, который вынуждает разделять компоненты
История об инциденте с «hairpin» (без указания реальных имён)
Представьте реальный архитектурный развилка, с которой сталкиваются продакшен-флоты. Имена ниже изменены; структура полностью совпадает с тем, с чем сталкиваются команды в реальных условиях.
Организация эксплуатирует один прокси-сервер на 203.0.113.5. Он обрабатывает входящий трафик (порт 443 для пользователей) и исходящий трафик (порт 1080 SOCKS5 для внутренних сервисов, выполняющих исходящие вызовы). Внутренние сервисы размещены в частных подсетях и направляют весь исходящий трафик через этот SOCKS5-прокси на 203.0.113.5:1080.
Один из сервисов, размещённых за тем же адресом 203.0.113.5, — это api.example.com. Публичный DNS резолвит api.example.com в 203.0.113.5.
Теперь другой внутренний сервис должен вызвать api.example.com. Его исходящий путь:
1. Внутренний сервис резолвит api.example.com → 203.0.113.5
2. Внутренний сервис отправляет запрос через SOCKS5-прокси исходящего трафика на 203.0.113.5:1080
3. Прокси пытается установить соединение от себя на 203.0.113.5:443
4. Отказ в подключении. Пакету пришлось бы покинуть NAT и снова войти в тот же самый NAT, что большинство сетевых стеков отклоняют. Прокси не может подключиться к самому себе через собственный публичный IP.
Это hairpin NAT: пакет, который покидает NAT и должен снова войти в тот же самый NAT, чтобы достичь своего назначения. Без специальной поддержки hairpin на уровне маршрутизации пакет будет потерян.
Почему проблема проявляется поздно
На ранних этапах жизненного цикла проекта каждый внутренний сервис либо общался с другими внутренними сервисами по внутреннему имени хоста (internal-api.local), либо не обращался к публичным сервисам своей организации. Путь hairpin просто не существовал.
Затем новая функция потребовала, чтобы сервис A обращался к api.example.com (публичное имя хоста). Путь hairpin активировался. Отказ в подключении. Сбой.
Исправление устранило симптом (принудительно заставило резолвер возвращать внутренний IP для api.example.com вместо публичного). Корневая причина: одна машина выполняла слишком много задач.
Архитектурная развилка
Один сервер становится двумя
Чистое решение: разделить прокси на два сервера.
Сервер входящего трафика (публичный IP 203.0.113.5):
- Caddy / обратный прокси на портах 80, 443
- Публичные DNS-записи указывают на этот сервер
- Хостит api.example.com, app.example.com и т. д.
Сервер исходящего трафика (другой публичный IP 203.0.113.99):
- SOCKS5 / прямой прокси на порту 1080
- Брандмауэр ограничивает входящие соединения только IP-адресами внутренней подсети
- Внутренние сервисы направляют весь исходящий трафик через этот адрес
Что это даёт:
1. Проблема hairpin-маршрутизации решена. Внутренний сервис, вызывающий api.example.com, направляет исходящий трафик через 203.0.113.99 (egress), который затем нормально подключается к 203.0.113.5 (ingress, другой IP-адрес). Цикл NAT исчезает, поскольку два IP-адреса находятся на разных машинах.
2. Изоляция безопасности. Брандмауэр сервера egress можно настроить так, чтобы он пропускал трафик только с небольшого набора внутренних IP-адресов. Брандмауэр сервера ingress остаётся открытым для внешнего мира. Два отдельных набора правил, каждый из которых чётко выражает одну роль.
3. Независимое масштабирование. Пропускная способность ingress масштабируется в зависимости от числа пользователей; пропускная способность egress — от активности внутренних сервисов. Можно обновить один компонент, не затрагивая другой.
4. Изоляция сбоев. Неверно настроенный egress больше не приводит к сбою публичного сайта. DDoS-атака на публичный сайт больше не приводит к исчерпанию пропускной способности egress.
5. Более чёткая ментальная модель. У каждой машины одна функция. Инженеры рассуждают о задачах ingress, не думая об egress, и наоборот.
Две оси, два решения по масштабированию
Независимое масштабирование
До разделения рост нагрузки в любом направлении создавал давление на один и тот же сервер. После разделения каждое направление имеет собственное выделение ресурсов.
Масштабирование входящего трафика (Ingress): масштабируется в зависимости от количества пользователей. Решения по пропускной способности принимаются на публичном уровне (увеличение числа реплик обратного прокси, более мощные ВМ, CDN перед ними). Бюджет пропускной способности рассчитывается исходя из пользовательского трафика в пиковые периоды.
Масштабирование исходящего трафика (Egress): масштабируется в зависимости от объема вызовов внутренних сервисов к внешним API. Часто доминирующими факторами являются доставка вебхуков, вызовы платежных процессоров или получение данных от сторонних источников. Бюджет пропускной способности рассчитывается исходя из паттернов внутренних вызовов.
Изоляция сбоев: DDoS-атака на публичный входящий трафик (ingress) больше не потребляет пропускную способность исходящего трафика (вызовы платежного процессора выполняются в любом случае). Сбой прокси исходящего трафика больше не приводит к недоступности публичного сайта (пользователи продолжают иметь доступ к сайту; срабатывают только внутренние исходящие вызовы).
Различные SLO: доступность входящего трафика важна для пользователей (видимый сбой сайта); доступность исходящего трафика важна для операторов (фоновые сбои, которые могут обнаруживаться дольше). Каждая сторона может иметь собственный SLO.
Несколько серверов исходящего трафика
После того как роль исходящего трафика (egress) выделена на отдельный сервер, следующим очевидным шагом является запуск нескольких серверов исходящего трафика за балансировщиком нагрузки для обеспечения отказоустойчивости (HA). Каждый новый внутренний сервис указывает на хост-имя исходящего трафика (которое резолвится в сбалансированный пул), а не на один конкретный IP-адрес.
Тот же урок, что и во всех распределённых системах: как только слой становится безсостоятельным и получает собственную роль, его можно легко масштабировать.
Новая интеграция с партнёром
Ваша организация работает с разделением входящего/исходящего трафика (ingress/egress) как задумано. Сервер исходящего трафика (egress) имеет фиксированный публичный IP-адрес (203.0.113.99), который вы добавили в белый список для трёх существующих API партнёров (процессор платежей, SMS-шлюз, провайдер электронной почты).
Команда продукта хочет добавить четвёртую интеграцию: систему доставки вебхуков, которая делает обратные вызовы на конечные точки клиентов по всему миру. Прогноз объёма: 10 000 вызовов в минуту, с пиками до 30 000.
Спроектируйте сетевую границу для растущего сервиса
Синтез
Вы узнали, почему входящий и исходящий трафик требуют разных инструментов, о сбое hairpin NAT, который вынуждает разделять их в реальных флотах, и о том, как независимое масштабирование, изоляция безопасности и изоляция отказов становятся возможными после такого разделения.
Примените все четыре аспекта.
Средняя SaaS-компания управляет тремя поддоменами продукта (app, api, admin) для своих пользователей, а также четырьмя исходящими интеграциями (Stripe, Twilio, SendGrid, система вебхуков клиентов). В настоящее время всё это работает за одним прокси-сервером с одним публичным IP-адресом. Начали поступать сообщения о периодических сбоях hairpin, когда внутренние сервисы пытаются вызвать api.example.com. Они хотят спроектировать постоянное решение.
Куда приведет этот курс дальше
Куда приведет этот курс дальше
Вы только что увидели один из самых чистых рефакторингов с разделением ответственности в распределенных системах: один узел становится двумя, каждый с четкой ролью, и система попутно получает преимущества в масштабируемости, безопасности и изоляции отказов.
В следующем уроке (cs_distsys_failure_modes_and_blast_radius) вы продолжите рассуждения об изоляции сбоев. Вы прочитаете обезличенный постмортем инцидента DNS-SERVFAIL, определите каскадный характер сбоя и составите безвинные (blameless) пункты плана действий, направленные на исправление систем, а не на обвинение людей.
Сопутствующий урок: geometry_of_ingress_egress_separation переосмысляет разделение входящего и исходящего трафика как двудольный граф и исследует сечевые вершины, сетевые разделения, а также то, что теория графов говорит о сетевых границах.
Отличная работа. Вперёд.