Как и зачем активно проверять работоспособность узлов при проксировании запросов с помощью Nginx

25bdc41b3c5fb99fcab9d035cb635b10.png

Привет, Хабр! В этом материале рассмотрим, как мы в SynGX реализовали активную проверку работоспособности узлов в группах балансировки, и как этот опыт может быть полезен при использовании прокси-серверов c аналогичной функциональностью.

Меня зовут Ринат Фатхуллин, я владелец продукта Platform V SynGX. В СберТехе мы с 2017 года развиваем собственную сборку Nginx для внутренних заказчиков, а в 2022 вышли на рынок под брендом Platform V SynGX.

Необходимость развития собственной сборки обусловлена спецификой требований нашей материнской компании. Наша сборка отличается набором модулей и патчей, которые мы применили. Кроме стандартных модулей Nginx у нас есть:

  • 13 сторонних модулей, которые мы взяли как есть,

  • 5 переработанных сторонних модулей,

  • 2 собственных модуля,

  • 40+ патчей.

Конечно, мы продолжаем наращивать функциональность. Так, например, в последнем обновлении добавили возможность получения и обновления сертификатов из HashiCorp Vault и дополнительные опции «липких» сессий — sticky route и sticky learn.

Особенности Platform V SynGX

Чтобы перейти к основной части, сначала разберемся в специфике продукта.

  1. Основан на версии Nginx 1.24 и полностью совместим с Nginx в плане конфигурации.

  2. Зарегистрирован в РРПО.

  3. Эксплуатируется в IT‑инфраструктуре на 2000+ серверах.

  4. Активно используется как L4 и L7 балансировщик в разных режимах (TCP, UDP, HTTP, HTTP2, websocket‑ы, gRPC) в автоматизированных системах, где необходима раздача статики и/или проксирование запросов.

  5. Содержит часть функциональности коммерческой версии Nginx+.

  6. Поставляется в виде набора RPM пакетов под Альт 8 СП и SberLinux OS.

Подробнее читайте здесь.

Одна из важных функций, которую мы активно развиваем в SynGX — это активная проверка работоспособности узлов в группе балансировки. Результаты проверки используются в процессе обработки запроса при выборе узла, на который будет перенаправлен запрос. Такой функциональности нет в оригинальной open source версии Nginx, но он есть во многих других реализациях прокси-сервера со своими особенностями. Например, в коммерческой версии Nginx+, есть в Envoy и HAProxy.

Несмотря на кажущуюся простоту функции активной проверки работоспособности узлов, существует множество нюансов и неочевидных моментов в реализации. Далее мы расскажем, как сами о них узнали, как решили или обходили их в своем продукте.

Как это работает

Active Health Check представляет собой механизм проверки работоспособности узлов в группе балансировки. Прокси-сервер реализует этот механизм путем периодической отправки запросов различного рода всем узлам в группе балансировки и анализа ответов. Это позволяет ему реагировать на изменения в их статусе — вводить в балансировку и/или выводить из балансировки. 

7382ec33bf65de4616a28afacffdf8c7.png

Почему решили доработать

Начальной точкой ещё до создания продукта Platform V SynGX было требование сделать так, чтобы Nginx проксировал HTTP-запросы только на живые узлы. При этом состояние узлов могло динамически меняться во времени — узлы могли «падать» и «подниматься» или могли временно выводиться из эксплуатации администраторами для выполнения работ. Этой функциональности нет в open source-версии Nginx, поэтому для решения этой задачи мы взяли модуль с открытым исходным кодом и подключили его в свою сборку Nginx. Тестирование, доработки и эксплуатация происходили в рамках миграции банковского мобильного приложения на микросервисную архитектуру. Подробнее о миграции можно узнать из этой статьи.

В процессе эксплуатации у нас появлялось всё больше вопросов, поэтому мы постоянно были в поиске оптимальных решений, которые бы могли повысить надежность СБОЛ и улучшить клиентский опыт. Вот эти вопросы:

Актуальные вопросы

Как было реализовано в подключенном модуле

Можно ли указать количество успешных и неуспешных запросов, после которых узел будет выведен из балансировки на тот случай, если, например, сеть «моргает»?

Можно.

Можно ли указать начальное состояние узла, если, например, сервис на узле «долго» стартует, и мы не хотим сразу слать на него запросы?

Можно.

Можно ли проверку работоспособности проводить по одному порту и url‑у, а проксировать запросы на другой порт и url, если за выдачу информации о «здоровье» отвечает другой endpoint?

Можно.

Можно ли при проверке работоспособности отправлять не просто запрос GET, а какой‑то свой специфичный запрос?

Можно, но нужно в явном виде писать HTTP‑запрос в виде строки.

Запрос проверки работоспособности отправляется протоколу HTTP 1.1, при этом соединение закрывается или держится открытым?  Лишние открытые соединения используют ресурсы.

Соединение закрывается, но запрос отправляется по протоколу HTTP 1.0.

Можно ли проверять работоспособность узлов по протоколу TCP в секции HTTP? Проверка по протоколу TCP может быть более быстрой, чем проверка по протоколу HTTP и быстрее дать результат.

Можно проверять работоспособность узла по протоколу TCP, но при этом проверяется только возможность установления соединения и возможность отправки одного байта.

Есть ли аналогичная функциональность для секции stream для протоколов TCP и UDP, если Nginx используется в качестве L4 балансировщика?

Для секции stream нужно подключать ещё один модуль.

Есть ли метрики, отражающие текущий статус узлов в группе балансировки?

Метрики есть в виде JSON и в виде HTML‑страницы.

Можно ли при проверке работоспособности использовать тело ответа, в котором будет закодировано текущее состояние узла?

К сожалению, нельзя. Однако есть возможность указать какие HTTP‑коды ответов будут считаться успешными при проверке работоспособности.

Что делать, если в директиве server в upstream указано доменное имя, соответствующие которому сетевые адреса тоже периодически меняются?

Периодическое разрешение доменных имен в сетевые адреса во время работы отсутствует в open source версии Nginx.

Можно ли проверять работоспособность узлов с использованием протокола TLS? Все endpoint‑ы сервисов закрыты с помощью TLS, даже те, что отвечают на запросы проверки работоспособности.

Можно проверять работоспособность узла только на основании запроса CLIENT HELLO и ответа SERVER HELLO.

Что делать, если каждому узлу в группе балансировки нужен свой SNI в запросе? Это нужно если в группе балансировки указаны, например, сервисы в разных кластерах k8s.

Такой функциональности нет в том числе и в коммерческой версии Nginx+.

Можно ли использовать несколько типов проверки одновременно,  либо задавать какой‑то порядок их выполнения?

Такой функциональности не оказалось.

Взятый за основу модуль от компании Alibaba обеспечил нам хороший старт, но закрыть все требования он не мог. Нам пришлось делать доработки по всем пунктам, кроме первых 4, чтобы ответить на каждый вопрос списка и соответствовать бизнес-требованиям.

К чему пришли сейчас

Наша итоговая реализация теперь выглядит так:

  • Функциональность активной проверки работоспособности есть для секций stream и HTTP.

  • HTTP‑запросы отправляются по умолчанию с использованием протокола HTTP 1.1 (вместе с заголовками Host и Connection), но соединение при этом закрывается.

  • Все параметры активной проверки работоспособности задаются в секции upstream. Для каждой группы балансировки могут быть свои настройки. Такая реализация отличается от варианта Nginx+, поскольку там параметры активной проверки работоспособности задаются для location. Нам пришлось сделать иначе, потому что у нас был кейс, когда внутри одного location запросы проксировались на разные группы балансировки, для каждой из которых надо было определить свои параметры проверки работоспособности.

  • Есть параметр interval (milliseconds) — период опроса узлов в группе балансировки.

  • Есть параметр fall (count) — количество неуспешных запросов, чтобы считать, что узел не доступен.

  • Есть параметр rise (count) — количество успешных запросов, чтобы включить узел в балансировку.

  • Есть параметр timeout (milliseconds) — время ожидания получения ответа.

  • Есть параметр default_down (true/false) — статус узла по умолчанию.

  • Есть параметр port (число) — указывает порт узла, который отличается от заданного в конфигурации в директиве server. Значением по умолчанию является 0, что означает, что порт такой же, как в директиве server.

  • Есть параметр type (строка) — указывает необходимый тип проверки работоспособности. Возможные значения: для http — tcp, ssl_hello, http, https; для stream — udp, tcp, http, ssl_hello, tcp_tls.

  • Реализована полноценная поддержка TLS для запросов проверки работоспособности. Для этого есть параметры для настройки TLS — аналогичные параметрам proxy_ssl_*, но c другим префиксом. Также добавлена возможность для каждого узла указывать свое значение SNI, которое будет добавляться в запрос, в том числе при проксировании.

  • Есть параметры check_http_send и check_http_expect_alive для секции http и параметры check_send и check_expect_alive для секции stream. Параметры check_http_send и check_send позволяют определить, какой запрос будет отправлен, а параметры check_http_expect_alive и check_expect_alive — что ожидается получить в ответе, который будет признан успешным.

  • Реализовано периодическое разрешение доменных имен в сетевые адреса для всей конфигурации Nginx (как в Nginx+ и Angie)

  • Есть метрики о состоянии узлов в формате JSON, Prometheus и HTML.

Функциональность активной проверки работоспособности содержит целый список нюансов. Эти особенности, как привило, становятся понятными при использовании в системах с большим количеством сервисов и высокими требованиями к доступности и надежности. Например, к таким системам относятся финансовые сервисы для физических лиц, государственные информационные ресурсы, геоинформационные ресурсы, крупные интернет-магазины.

Если вы разрабатываете или развиваете такие системы, наш опыт в плане заданных вопросов и найденных решений может быть вам полезен, даже если вы используете не SynGX, а другие прокси сервера. Скорее всего реализация в них функциональности активной проверки работоспособности будет в той или иной мере схожей, но отличаться в деталях.

В одном из следующих материалов мы расскажем, какие ещё доработки в части проксирования запросов и для чего мы реализовали в своем продукте. Среди них есть, например, отправка TCP пакета с флагом RST при попытке установить соединение с SynGX, если все узлы выпали из балансировки и многие другие. Подписывайтесь на блог и следите за нашими обновлениями.

© Habrahabr.ru