Что стоит перед практически каждым веб-сервисом
Добро пожаловать
Если вы введете example.com в браузер, вы почти никогда не попадете на тот компьютер, который фактически запускает приложение. Вы попадете на компьютер, который перенаправляет запрос на тот, который его выполняет. Этот компьютер-перенаправитель имеет название: обратный прокси-сервер.
В этом уроке вы узнаете, что делает обратный прокси-сервер, почему практически каждый публичный веб-сервис скрывается за ним и какие три задачи одновременно выполняет слой на границе (edge layer).
К концу урока вы будете понимать:
- Разница между клиентом, прокси и исходным сервером (origin)
- Почему прокси перед исходным сервером обеспечивает защиту, масштабируемость и позволяет заменять компоненты незаметно для пользователей
- Три задачи, которые одновременно выполняет обратный прокси: сокрытие исходного сервера, завершение TLS-соединения и распределение нагрузки между репликами
- Как запрос проходит путь от браузера к прокси, затем к вышестоящему серверу (upstream) и обратно, шаг за шагом
К концу урока вы сможете уверенно рассуждать о размещении прокси, разделении ответственности на границе сети и о том, почему безсостоятельный слой прокси легко масштабируется, в то время как одиночный исходный сервер — нет.
Прямой прокси и обратный прокси
Два направления прокси
Оба типа прокси находятся между двумя сторонами и передают трафик. Они различаются тем, какую сторону они представляют.
Прямой прокси (Forward proxy): располагается перед группой клиентов. Клиенты знают о существовании прокси; внешний сервер видит адрес прокси, а не адрес клиента. Корпоративные прокси для исходящего трафика, фильтры контента и SOCKS-прокси соответствуют этой схеме.
Обратный прокси (Reverse proxy): располагается перед группой серверов. Внешний мир (клиенты) общается с адресом прокси; клиенты не подозревают, что за ним скрывается реальный сервер. Почти каждый публичный веб-сервис использует такой прокси.
Мнемоника: прямой прокси скрывает клиентов от серверов. Обратный прокси скрывает серверы от клиентов.
Почему важно понимать направление? Задачи, режимы отказов и границы безопасности различаются. Прямой прокси заботится о том, с кем связываются его пользователи; обратный прокси заботится о том, кто связывается с его серверами.
Клиент, отправляющий трафик через оба типа прокси одновременно, проходит путь: client -> forward_proxy -> internet -> reverse_proxy -> origin.
Почему так много функций выполняется на краю
Краевой уровень оправдывает своё существование
Обратный прокси одновременно выполняет три задачи. Любая из них сама по себе оправдывает наличие этого уровня; выполнение всех трёх на одном и том же адресе объясняет, почему почти каждая производственная веб-архитектура имеет одинаковую структуру на входе.
Задача 1: Скрыть исходный сервер. Прокси отвечает на публичном IP-адресе. Бэкенды находятся на частных IP-адресах, недоступных из интернета. Атакующему, желающему атаковать исходный сервер, сначала необходимо скомпрометировать прокси.
Задача 2: Завершение TLS. Прокси хранит сертификат для example.com и расшифровывает входящие HTTPS-запросы. Бэкенды общаются с прокси по простому HTTP (или более простому внутреннему TLS) через доверенный сегмент сети. Ротация, продление сертификатов и политика шифрования сосредоточены в одном месте.
Задача 3: Распределение нагрузки. Прокси выбирает, какой бэкенд будет обрабатывать каждый запрос. Бэкенды за одним прокси образуют пул; прокси выбирает один из них для каждого запроса, используя определенную стратегию (round-robin, наименьшее количество соединений, хеш по заголовку). Увеличение мощности означает добавление бэкенда в пул, а не информирование каждого клиента о новом адресе.
Каждая задача — это небольшая программа. Вместе они объясняют, почему даже такой простой слой, как «Caddy перед Python-приложением», несёт на себе больший вес в проектировании, чем само Python-приложение.
Проектирование с учётом всех трёх задач
Ваша команда запускает небольшой API на одном Python-процессе, слушающем порт 8000 на одном ВМ. Трафик вырос настолько, что одного ВМ уже не хватает, а проверка безопасности выявила, что у ВМ есть публичный IP-адрес и оно использует собственный TLS-сертификат.
Вы решаете поставить прокси-сервер перед ним. Наскелите схему: куда указывает DNS, где находится сертификат, как нагрузка доходит до бэкендов и что меняется в исходном ВМ?
Замена компонента без чьего-либо уведомления
Непрямые ссылки дают свободу
Существует старое правило из области информатики: любую проблему можно решить, добавив слой индирекции (кроме проблемы слишком большого количества слоёв индирекции). Обратный прокси-сервер — это одна из самых полезных форм индирекции в распределённых системах.
Что это даёт:
- Заменяемые бэкенды. Переносите приложение с Python на Go? Мигрируете из одного дата-центра в другой? Внедряете новую версию без простоя? Всё это происходит за стабильным публичным адресом. Для пользователей ничего не меняется.
- Независимое масштабирование. Слой прокси масштабируется по пропускной способности и загрузке CPU на уровне TLS. Слой бэкенда масштабируется по объёму прикладных задач. Каждый слой растёт по своей оси, поскольку они работают на разных машинах.
- Изоляция сбоев. Ошибочный деплой на бэкенде не приводит к недоступности публичного адреса. Прокси-сервер продолжает работать; вы выпускаете исправление или откатываете изменения; мир переподключается, когда бэкенд восстанавливается.
- Кросс-функциональные задачи в одном месте. Ограничение частоты запросов, геоблокировка, журналирование запросов, переписывание заголовков, кэширование, сжатие ответов: всё это находится на прокси-сервере. Код бэкенда остаётся сфокусированным на приложении.
Без прокси-сервера каждая из этих проблем должна существовать внутри процесса приложения. С прокси-сервером они находятся в одном слое, за который отвечает одна команда.
Цена: ещё один слой для эксплуатации. Опытные команды принимают эту цену, потому что сам слой прокси-сервера работает без состояния и масштабируется горизонтально; замена одного прокси-сервера на два не требует согласования.
Синее/зелёное развёртывание через прокси
Ваша команда запускает версию 1 API на трёх виртуальных машинах бэкенда (синий пул) за обратным прокси. Вы хотите развернуть версию 2 с возможностью отката менее чем за тридцать секунд, если что-то пойдёт не так.
Вы запускаете три новые виртуальные машины бэкенда (зелёный пул) с версией 2, параллельно с синим пулом, но пока не направляете на них трафик.
От браузера к бэкенду и обратно
Отслеживание одного запроса от начала до конца
Отследите один HTTPS-запрос GET к https://api.example.com/users/42 через обратный прокси перед пулом бэкендов.
Шаг 1: Разрешение DNS. Браузер запрашивает у резолвера адрес api.example.com. Резолвер возвращает публичный IP прокси (например, 203.0.113.10). Браузер устанавливает TCP-соединение с 203.0.113.10:443.
Шаг 2: Рукопожатие TLS. Прокси предъявляет свой сертификат для api.example.com. Браузер проверяет сертификат, обе стороны договариваются о сеансовом ключе, & шифрованный канал открывается.
Шаг 3: HTTP-запрос внутри TLS. Браузер отправляет GET /users/42 HTTP/1.1\nHost: api.example.com\n.... Прокси расшифровывает байты запроса.
Шаг 4: Выбор бэкенда. Прокси-сервер обращается к своему пулу upstream-серверов для api.example.com и выбирает один бэкенд (например, 10.0.0.21:8000) с использованием своей стратегии балансировки нагрузки.
Шаг 5: Upstream-запрос. Прокси-сервер открывает (или переиспользует) обычное HTTP-соединение с 10.0.0.21:8000 и пересылает запрос. По пути прокси-сервер может переписать заголовки: добавить X-Forwarded-For: <client-ip>, корректно установить Host:, удалить заголовки hop-by-hop, такие как Connection.
Шаг 6: Обработка бэкендом. Бэкенд-приложение считывает запрос, выполняет запрос к своей базе данных и формирует JSON-ответ.
Шаг 7: Upstream-ответ. Бэкенд отправляет ответ обратно прокси-серверу по обычному HTTP.
Шаг 8: Ответ на границе. Прокси-сервер может переписать или сжать ответ, шифрует его обратно через TLS-сессию и отправляет его в браузер.
Шаг 9: Жизненный цикл соединения. TLS-сессия обычно остается открытой для следующего запроса (HTTP/2 мультиплексирует множество запросов по одному соединению). Соединение прокси-сервера с бэкендом часто пулируется для повторного использования.
Каждый публичный веб-сервис следует какому-то варианту этой схемы. Знание шагов позволяет рассуждать о том, где накапливается задержка, где должно вестись логирование и где может скрываться сбой.
Куда ушло время?
Пользователь жалуется, что API работает медленно. Вы проводите замеры и обнаруживаете, что запрос занимает 850 мс от начала до конца. Журналы сервера на бэкенде показывают, что приложение обработало запрос за 40 мс. Журналы прокси показывают, что прокси потратил 50 мс на свою часть работы (рукопожатие TLS + маршрутизация + запись ответа).
Спроектируйте минимальный edge для нового сервиса
Синтез
Вы узнали о различиях между forward- и reverse-прокси, о трёх задачах, которые одновременно выполняет reverse-прокси, о том, почему сокрытие origin-сервера приносит выгоду каждый раз, когда необходимо что-то изменить, и о том, как запрос проходит по хопам через edge.
Теперь примените эти знания.
Небольшая команда планирует запустить новый сервис notes.example.com. Пользователи смогут читать и писать личные заметки. При запуске команда будет использовать два backend-VM и планирует масштабироваться до десяти в течение следующего года. Им необходим HTTPS для пользователей, постепенный вывод новых версий и отсутствие публичного доступа к IP-адресам backend-серверов.
Куда направится этот курс дальше
Куда направится этот курс дальше
В этом уроке была определена структура пограничного слоя. Четыре следующих урока в этом курсе строятся на её основе:
- Бессостоятельное горизонтальное масштабирование: почему прокси-слой (и бэкенды за ним) масштабируется дёшево, и математика для расчёта количества реплик при пиковых нагрузках.
- Разделение входящего и исходящего трафика: почему один прокси-сервер, обрабатывающий и входящий, и исходящий трафик, в конечном итоге выходит из строя неожиданными способами, и как разделить эти слои.
- Режимы отказа и радиус поражения: как одно изменение конфигурации может привести к каскадному срабатыванию и полному отключению, и как составить безобидные (blameless) пункты плана действий для предотвращения повторения инцидента.
- Наблюдаемость и пропускная способность: что следует измерять на периферии, чтобы обнаружить сбой до того, как о нем узнают пользователи.
Каждый урок является самостоятельным. В совокупности они дают вам действующую ментальную модель веб-флота масштаба.
Сопутствующий урок: geometry_of_proxies_and_origins переосмысляет все из этого урока в виде ориентированного графа и исследует, что теория графов говорит о пути запроса.
Отличная работа. Вперед.