Search
Write a publication
Pull to refresh

Angie 1.10: разбор фич, планы на 1.11

Level of difficultyEasy
Reading time17 min
Views946

Следуя устоявшемуся квартальному ритму выпуска значимых релизов, мы выпустили стабильные версии Angie и Angie PRO 1.10 — форка nginx, развиваемого в основном бывшими ключевыми разработчиками оригинального проекта.

Как и в прошлый раз, расскажем подробнее о нововведениях, приводя примеры. Вы узнаете, в чём ключевая фишка нового релиза (картинка под заголовком намекает); также слегка приоткроем завесу тайны над тем, что у нас припасено на будущее.

А пока — краткий список нововведений, которые будут разобраны ниже:

  • автоматическое проксирование и балансировка веб‑сервисов в Docker‑контейнерах (или Podman);

  • автоматическое получение TLS‑сертификатов для потокового модуля (stream);

  • прием соединений Multipath TCP (MPTCP);

  • контроль перегрузки CUBIC в QUIC‑соединениях;

  • привязка сессий с внешним хранилищем в модуле stream;

  • новые режимы привязки сессий при проксировании HTTP‑запросов;

  • режим постоянного перехода на резервную группу в модуле stream.

Последние три пункта из этого списка доступны только в коммерческой версии Angie PRO. Все остальные нововведения присутствуют и в бесплатной open‑source версии Angie. Как всегда, помимо исходного кода, мы готовим пакеты для большинства актуальных операционных систем и Docker‑образы.

Итак, поехали!

Автоматизация проксирования Docker-контейнеров

Для балансировки и масштабирования сервисов, запускаемых в контейнерах, перед ними обычно ставят реверс‑прокси. Для увеличения производительности или обновления сервисов запускают новые контейнеры и останавливают старые. Ранее, чтобы nginx узнавал о запуске новых контейнеров или переставал направлять запросы на остановленные, требовались дополнительные инструменты. Они обновляли конфигурацию и инициировали её перезагрузку — что сопровождалось всеми сопутствующими неудобствами и недостатками этого процесса.

Ситуацию немного улучшила поддержка динамического резолвинга, которую мы добавили в Angie два года назад. Однако это решение требовало настройки и динамического обновления DNS для контейнеров, а также не позволяло гибко настраивать параметры балансировки и привязки сессий.

Было бы удобнее, если бы балансировщик самостоятельно отслеживал изменения в пуле контейнеров и мгновенно реагировал на них. Запросы на такую функциональность поступали уже давно, с тех пор, как пользователи nginx познакомились с Traefik. Однако многих не устраивала его сравнительно низкая производительность. К тому же работать с привычным, проверенным временем инструментом всегда комфортнее, чем осваивать новое решение с его особенностями и ограничениями.

В Angie 1.10.0 мы наконец реализовали эту возможность. Теперь в конфигурации достаточно указать всего одну директиву с адресом Docker API:

docker_endpoint unix:/var/run/docker.sock;

В примере используется UNIX‑сокет, но можно указать и IP‑адрес. Затем нужно просто добавить контейнерам специальные метки (object labels в терминологии Docker).

Для HTTP‑модуля:

angie.http.upstreams.<имя_блока_upstream>.port=<порт>

Для модуля stream:

angie.stream.upstreams.<имя_блока_upstream>.port=<порт>

При запуске Angie автоматически обнаружит все контейнеры с такими метками и добавит их адреса и порты в указанную группу upstream. Любые изменения состояния контейнеров — запуск новых, остановка старых или приостановка — будут отслеживаться в реальном времени. Список серверов для балансировки обновится мгновенно, без перезагрузки конфигурации.

Возьмем, например, такую конфигурацию Angie (напоминаю, что за исключением новых возможностей, конфиг будет аналогичен nginx):

http {
    docker_endpoint unix:/var/run/docker.sock;

    upstream web {
        zone z1 1m; # Зона разделяемой памяти для upstream
    }

    server {
        listen 80;

        location / {
            proxy_pass http://web;
        }
    }
}

stream {
    upstream mqtt {
        zone z2 1m; # Зона разделяемой памяти для upstream
    }

    server {
        listen 1883;
        proxy_pass mqtt;
    }
}

Здесь настроено проксирование HTTP на порту 80, а также проксирование порта 1883 (часто используемого для MQTT) в потоковом режиме (stream), без анализа протокола на уровне приложений.

Предположим, у нас есть сервис в контейнере, который одновременно предоставляет веб‑интерфейс и обрабатывает MQTT‑сообщения. Чтобы Angie начал автоматически балансировать на него трафик, добавим в docker-compose.yml следующие метки:

services:
  my_app:
    image: my_app:latest
    labels:
      - "angie.network=my_bridge"           # Сеть Docker, из которой брать IP
      - "angie.http.upstreams.web.port=80"  # HTTP-порт контейнера для upstream `web`
      - "angie.stream.upstreams.mqtt.port=1883" # Stream-порт контейнера для upstream `mqtt`
...

При запуске контейнера его IP‑адрес в сети my_bridge будет автоматически добавлен:

  • В upstream‑блок web (секция http) — как сервер с портом 80

  • В upstream‑блок mqtt (секция stream) — как сервер с портом 1883

Используя метки, можно также задать параметры сервера (директивы server) в upstream:

      …
      - "angie.http.upstreams.web.weight=5"
      - "angie.http.upstreams.web.max_fails=3"
      - "angie.http.upstreams.web.fail_timeout=10s"
      - "angie.http.upstreams.web.slow_start=60s"
      - "angie.stream.upstreams.mqtt.weight=5"
      - "angie.stream.upstreams.mqtt.max_conns=50"
      - "angie.stream.upstreams.mqtt.sid=iot42"

Доступны почти все параметры, которые можно указать в конфигурации Angie: веса (weight), настройки проверок здоровья (max_fails, fail_timeout), ограничения соединений (max_conns), идентификатор для привязки сессий (sid), и т. д.

Замечу, что в Angie реализована не только базовая привязка сессий для HTTP и stream, но и расширенные возможности. Например, для протокола MQTT можно автоматически извлекать идентификатор клиента (ClientID) или имя пользователя (Username) из пакета CONNECT и использовать их для привязки сессии.

Если контейнер поставить на паузу (docker pause), Angie автоматически пометит соответствующий сервер в upstream как down. Это временно исключит его из балансировки, но не приведет к сбросу накопленной статистики сервера (доступной через мониторинг Angie) и пересчету хешей (если используется балансировка на основе хеширования — директива hash).

Авторизация: Для доступа к защищенному Docker API используется блок client {}, который мы реализовали в этой же версии. В нем настраиваются параметры модуля proxy для внутренних исходящих запросов, которые Angie сам отправляет к Docker API (и другим сервисам, например ACME или Upstream Probe):

client {
     # Можно добавить заголовок для авторизации HTTP Basic
     proxy_set_header  Authorization "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==";

     # Или клиентский сертификат
     proxy_ssl_certificate /path/to/auth.cert;
     proxy_ssl_certificate_key /path/to/auth.key;

     location @docker_events {}
     location @docker_containers {}
}

Как всегда, за всеми деталями обращайтесь к документации по модулю:

Модуль Docker опционален. Мы включаем его в наши стандартные пакеты, но если вам эта функциональность не нужна, его всегда можно исключить из сборки (--without-http_docker_module). А теперь перейдем к новым возможностям модуля ACME!

Автоматические TLS-сертификаты для stream

Уже второй год мы расширяем возможности Angie по автоматическому выпуску и обновлению TLS‑сертификатов с помощью протокола ACME (его используют Let's Encrypt и другие популярные центры сертификации). Это позволяет упростить настройку и отказаться от сторонних утилит вроде certbot или acme.sh (хотя ничто не мешает использовать их вместе с Angie, если вам так удобнее).

До сих пор эта функциональность работала преимущественно в HTTP‑модуле, где TLS‑терминация применяется чаще всего. Однако пользователи запрашивали её и для модуля stream, который проксирует произвольный TCP/UDP‑трафик, — особенно после того, как в нём появилась поддержка виртуальных серверов (SNI).

В Angie 1.10 мы добавили полноценную поддержку ACME для stream. Настройка почти идентична HTTP‑модулю, достаточно лишь прописать пару директив.

Рассмотрим на примере:

http {
    resolver 127.0.0.53;  # Для динамического DNS-резолвинга

    acme_client my_client https://acme-v02.api.letsencrypt.org/directory;

    server {
        listen 80;
        return 444;  # Закрываем соединение для всех не-ACME запросов
    }
}

stream {
    server {
        listen 443 ssl;
        server_name example.com www.example.com;  # Домены для сертификата

        acme my_client;  # Используем ACME-клиент "my_client"

        # Автоматически заполняемые переменные:
        ssl_certificate $acme_cert_my_client;
        ssl_certificate_key $acme_cert_key_my_client;

    }
}

Эта конфигурация почти идентична типичной для nginx схеме, за исключением двух директив: acme_client и acme.

Директива resolver задана, чтобы Angie мог в процессе работы через резолвер асинхронно запрашивать IP‑адреса серверов Let's Encrypt. Эта директива требуется для любого динамического резолвинга (хотя у нас есть в планах научить Angie читать системные настройки и избавить пользователей от необходимости указывать директиву resolver).

Сервер Let's Encrypt должен убедиться, что домен принадлежит нам. Для этого используются различные типы подтверждения (так называемый «challenge»). Пока Angie поддерживает два типа — HTTP и DNS. В ближайшей версии мы собираемся добавить поддержку ALPN‑подтверждения, которая не нуждается в открытии HTTP‑порта. По умолчанию, если иного не указано в директиве acme_client, используется HTTP‑подтверждение.

Angie сам автоматически отвечает на HTTP‑проверку; для этого ему нужен только открытый 80-порт в рамках HTTP‑модуля. В примере выше не планируется обрабатывать какие‑либо другие запросы на этом порту, поэтому указан return 444, что сразу же оборвет соединение для всех остальных запросов. Но если вы обслуживаете также HTTP, то у вас уже, скорее всего, будет обработка порта 80, и ничего дополнительно настраивать не потребуется. Также там может быть настроен редирект на HTTPS.

Далее следует главный герой нашей функциональности — блок stream. Внутри его блока server задана директива acme с именем ранее сконфигурированного клиента. Она указывает, что в данном блоке server нужно использовать домены из директивы server_name, и эти домены попадут в запрос на сертификат. Сам сертификат и ключ для него будут содержаться в переменных, соответствующих имени клиента: $acme_cert_my_client и $acme_cert_key_my_client.

В результате Angie сам запросит мультидоменный сертификат для example.com и www.example.com, а также будет следить за тем, чтобы своевременно его обновлять.

Если вы поменяете набор доменов в директиве server_name или укажете ту же директиву acme my_client в каких‑то других блоках server с другим набором доменов, то Angie автоматически запросит новый сертификат с учетом ваших изменений. Функциональность из серии «один раз настроил и забыл»; далее всё будет работать автоматически и реагировать на изменения конфигурации.

Подробнее о всех настройках можно прочитать в документации на сайте:

В будущей версии мы хотим научить модуль ACME автоматически открывать порт 80 без необходимости указывать блок server в http {}.

Multipath TCP (MPTCP)

Протокол MPTCP по своей сути является надстройкой для TCP, позволяющей логически разделить соединение и поток данных. Это дает возможность агрегировать несколько каналов для повышения пропускной способности и отказоустойчивости, а также бесшовно переключаться между ними. Например, пользователь может покинуть зону покрытия сети Wi‑Fi, но передача данных продолжится через мобильную сеть практически незаметно для прикладного уровня.

Для включения приема MPTCP‑соединений в Angie достаточно указать опцию multipath в директиве listen:

listen 443 ssl multipath;

Таким образом, на 443 порту будут приниматься как обычные TCP‑соединения, так и MPTCP.

К сожалению, с поддержкой на стороне клиентов пока не всё так хорошо. Она неплохо реализована в продукции Apple, и её можно попытаться настроить в дистрибутивах Linux со свежими ядрами. А вот пользователи Windows и Android пока не у дел. Также можно столкнуться с тем, что некоторые VPN‑провайдеры фильтруют опции MPTCP в SYN/ACK‑пакетах (пакетах установки TCP‑соединения), и в этом случае MPTCP превращается в обычный TCP.

CUBIC в QUIC

Протокол QUIC — это транспорт для протокола HTTP/3, по аналогии с тем, как TCP выступает транспортом для протоколов HTTP/1.x и HTTP/2.

Как и TCP, QUIC гарантирует доставку, поэтому через него можно передавать большие объемы данных. Поскольку QUIC работает поверх UDP, ему потребовалось реализовать так называемый механизм контроля перегрузки (congestion control), как в TCP. Этот механизм следит за тем, чтобы пакеты в каждый момент времени отправлялись с доступной пропускной способностью до конечного получателя. Если отправлять пакеты медленнее, полоса пропускания не будет полностью задействована. Если отправлять их быстрее, на маршрутизаторах будут переполняться буферы, начнется потеря пакетов, и будут впустую тратиться ресурсы на их повторную передачу.

Существует целый набор подобных алгоритмов. До выхода новой версии в нашей реализации QUIC применялся известный алгоритм контроля перегрузки NewReno, но теперь будет использоваться CUBIC. Этот алгоритм способен более агрессивно наращивать скорость передачи в условиях высоких задержек, что дает преимущество, особенно при широкой пропускной способности канала.

Алгоритм был портирован из свежей версии nginx. При этом в нашей реализации он поддерживается не только для входящих соединений, но и для исходящих — при проксировании по протоколу HTTP/3.

Для использования CUBIC в HTTP/3 достаточно обновиться на свежую версию Angie. Никаких дополнительных настроек не требуется, если у вас уже включен HTTP/3.


Все перечисленные новинки были добавлены в последней бесплатной версии Angie с открытым исходным кодом. Вопреки нелепым фантазиям некоторых «диванных экспертов», никто нас не спонсирует и грантов не выделяет. Разработка целиком ведется на собственные средства, и лучшая благодарность для нас — это покупка лицензии на Angie PRO. А о том, какие ещё новые возможности реализованы в коммерческой версии, поговорим далее.

Привязка сессий с внешним хранилищем в stream

Привязка сессий позволяет балансировщику направлять все соединения в рамках одной сессии на один и тот же закрепленный бэкенд. Это требуется для горизонтального масштабирования приложений, чтобы избежать синхронизации данных между всеми бэкендами. Определение сессии и время её жизни задаются в настройках.

В Angie привязку можно реализовать:

  • по правилам конфигурации (режим route);

  • с запоминанием в разделяемой памяти (режим learn с zone);

  • в новом режиме learn с remote_action, где информация о привязке принимается из внешнего хранилища или сервиса.

Ранее этот режим был доступен только в HTTP‑модуле, но в Angie PRO 1.10 он добавлен и в потоковом модуле (stream).

Режим особенно полезен в двух практических сценариях:

  1. Кластер балансировщиков. Когда несколько экземпляров Angie работают вместе, а входящие подключения клиента (в рамках одной сессии) должны попадать на один бэкенд независимо от того, какой балансировщик их обработал. Это позволяет и масштабировать сами балансировщики, и повышать отказоустойчивость.

  2. Внешний «директор». Когда отдельный сервис (на основе бизнес‑логики приложения) определяет, куда направлять соединения конкретного клиента.

Оба сценария эффективно реализуются новой функцией. При этом можно использовать почти любое хранилище или реализовать специализированный сервис‑»директор».

Посмотрим на примере:

http {
    client {
        location @my_sticky {
            proxy_pass http://192.0.0.1;  # Внешнее хранилище/директор
        }
    }
}

stream {
    upstream backend {
        server 192.168.0.100:1986;
        server 192.168.0.102:1986;
        server 192.168.0.104:1986;

        sticky learn
             lookup=$remote_addr          # Ключ сессии (IP клиента)
             remote_action=@my_sticky     # Location для запроса
             remote_uri=/get?id=$sticky_sessid  # URI запроса
             remote_result=$upstream_http_x_backend;  # Переменная c результатом
    }

    server {
        listen 1986;
        proxy_pass backend;
    }
}

В самом начале в рамках уже упомянутого ранее блока client (http) объявлен location @my_sticky, который обращается к внешнему хранилищу или директору. В примере используется proxy_pass, но поддерживаются и любые другие методы, например: fastcgi_pass, redis2_pass, postgres_pass, и даже js_content для сложной обработки на JavaScript.

В блоке stream директива sticky learn в группе балансировки upstream содержит следующие параметры:

  • lookup=$remote_addr — в качестве ключа для сессии используется IP‑адрес клиента. В теории можно использовать любую переменную, которая доступна или может быть извлечена на этапе соединения, например $ssl_session_id, $mqtt_preread_clientid или $rdp_cookie.

  • remote_action=@my_sticky — задаем, что сессию следует запрашивать в контексте блока location с именем @my_sticky, который мы ранее объявили в рамках блока client { }.

  • remote_uri=/get?id=$sticky_sessid — задали URI для запроса сессии, где в качестве аргумента передаем собственно идентификатор сессии, ранее указанный через lookup.

  • remote_result=$upstream_http_x_backend — идентификатор сервера, на который мы хотим направить нашего клиента, будем брать из заголовка X‑Backend ответа на запрос к нашему хранилищу или директору.

Как можно заметить, настройки максимально гибки, а в качестве хранилища можно использовать почти всё что угодно. Так, в качестве хранилища может выступать и сам Angie с модулем keyval. Более того, в рамках того же location @my_sticky c proxy_pass можно воспользоваться богатыми возможностями по кэшированию ответов и настроить директивы proxy_cache, чтобы сократить задержки и количество обращений к внешнему хранилищу.

Всё это дает впечатляющую гибкость и полностью решает вопрос с масштабированием как самого кластера балансировщиков, так и хранилища.

Тут приведен лишь небольшой пример; за более подробной информацией обращайтесь к документации:

Новые режимы привязки сессий в HTTP

Аналогичная возможность запрашивать привязку сессии из внешнего хранилища появилась в HTTP‑модуле ещё в предыдущих версиях. В свежей же версии добавилось несколько новых опций, указывающих, как часто следует обращаться к внешнему хранилищу.

Директива sticky в блоке upstream модуля HTTP позволяет настраивать кэширование запрошенных сессий в разделяемой памяти:

sticky learn zone=my_zone:1m
	         lookup=$cookie_bar
		     remote_action=/remote_session
             remote_result=$upstream_http_x_sticky_sid
             timeout=5m;

Таким образом, полученная из внешнего хранилища привязка будет храниться в разделяемой памяти и использоваться для последующих запросов в рамках сессии без обращения к внешнему хранилищу. Время хранения привязанной сессии в разделяемой памяти в примере выше задано как пять минут с момента последнего использования с помощью параметра timeout=5m.

Если запросы в рамках сессии поступают часто (здесь — чаще, чем раз в пять минут), то повторного обращения к хранилищу может не быть достаточно долго, так как таймер устаревания сессии сбрасывается при каждом обращении. Такой режим не всем подходит, и иногда требуется обновлять данные о сессии с некоторой гарантированной периодичностью. Для этого теперь можно указать новую опцию norefresh:

sticky learn zone=my_zone:1m
             lookup=$cookie_bar
             remote_action=/remote_session
             remote_result=$upstream_http_x_sticky_sid
             timeout=5m norefresh;

Опция отключает сброс таймера устаревания при использовании привязанной сессии. При этом привязка сессии будет повторно запрашиваться из внешнего хранилища согласно заданному интервалу, то есть раз в пять минут в данной конфигурации.

В некоторых случаях сохранение сессии локально полностью недопустимо, либо требуется более продвинутая логика кэширования привязки сессий. Для этого теперь можно не задавать зону разделяемой памяти вовсе:

sticky learn lookup=$cookie_bar
             remote_action=/remote_session
             remote_result=$upstream_http_x_sticky_sid;

Тогда на каждый новый запрос будет порождаться подзапрос. Ответ на него уже можно кэшировать с использованием, например, развитых возможностей proxy_cache:

location /remote_session {
       internal;
       proxy_pass http://sessions_store;
       proxy_cache session_cache;
}

Можно даже вообще не настраивать кэширование, или же добавить скриптовой магии на JavaScript (либо Lua):

location /remote_session {
       internal;
       njs_content sessions.fetch;
}

Все это даёт почти неограниченные возможности по управлению привязкой сессий в рамках кластера из балансировщиков на базе Angie PRO.

Постоянное переключение на резерв в stream

Теперь настройка backup_switch стала доступна и в рамках модуля stream для балансировки на уровне TCP/UDP.

Мы уже обсуждали эту функциональность в контексте предыдущего релиза, где она впервые появилась для групп upstream‑серверов в HTTP‑модуле. Поэтому не буду сейчас на этом останавливаться — подробности читайте в предыдущей статье. Опишу лишь кратко, что параметр позволяет избежать частого переключения между резервной и основной группой, если серверы в одной из групп работают нестабильно.

Можно после перехода на резервную группу оставаться на ней не менее заданного интервала времени для стабилизации ситуации:

backup_switch permanent timeout=1m;

В этом примере после переключения на резерв попытки использовать серверы из основной группы будут происходить не чаще, чем раз в минуту.

Если вообще не указывать параметр timeout, то попыток возврата на основную без вмешательства администратора не будет до тех пор, пока резерв остается работоспособен.

В будущих версиях настройки для логики ввода резерва будут расширяться и дальше.


Это, пожалуй, всё, что касается коммерческой версии. Команда разработчиков Angie также занимается доработками компонента балансировки для Angie ADC — нашего комплексного решения по управлению сетевым трафиком. Но его релизы выходят по своему графику, и рассказ о нём выходит за рамки этой статьи. Далее мы поговорим о планах на следующий релиз, прежде всего в рамках бесплатной версии Angie.

Планы на Angie 1.11

Помимо уже озвученных ранее планов на поддержку ALPN в ACME, первое, что ожидается в будущей версии, — расширение возможностей статистики практически до бесконечности. В конфигурации можно будет объявить метрику на основе любой переменной и пересчитывать её на указанной стадии обработки запроса. Доступны самые разные типы метрик: счетчик, среднее, максимум‑минимум или гистограмма.

Таким образом, можно будет настроить подсчет статистики под любую задачу: к примеру, подсчитать число запросов по URI и найти самые частые, либо самые медленные из них; посчитать количество пользователей по странам с использованием GeoIP, или какими браузерами чаще всего они пользуются. В общем‑то, всё ограничивается только вашей фантазией. Как и со встроенными метриками, значения пользовательских метрик будут доступны через RESTful API в формате JSON или Prometheus.

Эта функциональность готовилась к выходу 1.10, но на очередной итерации ревью выявилась серьёзная проблема, исправление которой потребовало дополнительного времени. Следуя традициям nginx, мы скрупулезно подходим к качеству кода и вопросам ревью, при этом, напомним, цикл релизов у нас регулярный: примерно каждые три месяца выходит очередной стабильный релиз с готовой и протестированной к этому времени функциональностью.

Помимо нового модуля статистики, есть высокие шансы, что до первого стабильного релиза будет доведена и возможность запуска PHP‑приложений для замены PHP‑FPM. Задача эта очень сложная и комплексная, поэтому уже довольно долго находится в разработке. Тем не менее, многие эту функциональность ждут, и хочется оправдать ожидания.

Это, пожалуй, самое большое и интересное из того, что ждет вас в будущем релизе. Кроме того, ожидается множество небольших полезных доработок: дополнительная защита от slowloris‑атак, поддержка передачи TLV‑записей на бэкенд в PROXY Protocol, автоматическая настройка DNS‑резолвера (директивы resolver), фильтрация и форматирование журнала ошибок.

Что‑то из этого, возможно, не будет готово или не успеет пройти всего цикла ревью и испытаний к будущему релизу, а значит, отложится 1.12, но мы будем стараться.

Как обычно, ждем обратной связи в комментариях, в Telegram‑группе, на форуме или в GitHub, где репозиторий Angie недавно преодолел планку в 1500 звезд — мелочь, а приятно:

https://github.com/webserver-llc/angie
Tags:
Hubs:
+22
Comments11

Articles