Современная инфраструктура приложений динамична и требует гибкости от конфигурации ключевых элементов. В этой статье мы разберём все варианты реализации динамических групп серверов (upstream) в Angie. С помощью этих методов вы сможете изменять состав и статус серверов без вмешательства в конфигурацию Angie. 

Навигация по циклу

  1. Почему стоит переходить на Angie.

  2. Установка Angie из пакетов и в докере.

  3. Переезд с Nginx на Angie. Пошаговая инструкция.

  4. Настройка location в Angie. Разделение динамических и статических запросов.

  5. Перенаправления в Angie: return, rewrite и примеры их применения.

  6. Сжатие текста в Angie: статика, динамика, производительность.

  7. Серверное кэширование в Angie: тонкости настройки.

  8. Настройка TLS в Angie: безопасность и скорость.

  9. Настройка Angie в роли обратного HTTP-прокси.

  10. Балансировка нагрузки для HTTP(S) в Angie.

  11. Мониторинг Angie с помощью Console Light и API.

  12. Балансировка и проксирование L4-трафика в Angie.

  13. Клиентское кэширование в Angie.

  14. Динамические группы проксируемых серверов в Angie.

Видеоверсия

Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.

Обращение к серверам по именам

Самый доступный способ организации динамических серверов в группах — применение имён (DNS) для серверов. По умолчанию Angie при запуске или обновлении конфигурации производит преобразование имён в IP-адреса и далее использует их для подключения к серверам. Однако, если добавить параметр resolve директивы server, Angie будет обновлять адреса серверов в процессе обычной работы. Для корректной работы также необходимо добавить зону разделяемой памяти для хранения состояния серверов и объявить адрес DNS-сервера (resolver). Со стороны DNS необходимо создать нужное количество A-записей для доменного имени, которое будет использоваться в директиве server.

В результате минимальная конфигурация будет такой.

http {
  resolver 127.0.0.53 ipv6=off valid=30s status_zone=local;
  upstream backend {
    zone upstream-backend 10m;
    server back.example.ru resolve;
  }
  server {
    listen 80 default_server;
    location / {
    		proxy_pass http://backend;
    }
  }
}

В этом примере мы использовали локальный сервер (резолвер) systemd-resolved. При этом отключили использование IPv6, установили время жизни ответа в 30 секунд (таким образом переопределили значение TTL ответа DNS-сервера). Важно понимать, что DNS-сервер в этом сценарии должен обладать достаточной надёжностью, так как при его недоступности обновления списка проксируемых серверов не произойдёт. Кроме того, указанный сервер должен находиться в защищенной сети под вашим контролем. В директиве resolver можно указать несколько DNS-серверов, тогда они будут опрашиваться циклически.

В примере выше мы определили лишь одно имя сервера, но за ним может скрываться целая группа IP-адресов, так как DNS позволяет создавать множественные A-записи для одного доменного имени. Таким образом можно получить группу серверов для балансировки, но мы не сможем удобно управлять тонкими настройками каждого сервера (вес, статус запасного). Для решения этих задач можно использовать более продвинутые DNS-записи типа SRV

Использование SRV-записей

Мы продолжаем использовать DNS как источник для списка проксируемых серверов, но теперь создаём записи типа SRV, которые могут содержать больше свойств, чем обычные A‑записи. Например, рассмотрим следующую группу записей (фрагмент зоны local.lan, формат конфигурации bind):

localback       IN      A       127.0.0.1
_http._tcp.back IN      SRV 10 1 9001 localback.local.lan.
_http._tcp.back IN      SRV 10 2 9002 localback.local.lan.
_http._tcp.back IN      SRV 20 1 9003 localback.local.lan.
_http._tcp.back IN      SRV 20 2 9004 localback.local.lan.

Первая A‑запись отвечает за имя localback.local.lan. Остальные записи служат для определения списка серверов. В SRV‑записях мы видим три числовых параметра:

SRV 20 2 9004

Первое число (20) означает приоритет записи. В нашем случае это влияет на статус сервера: минимальное значение соответствует основному серверу (в работе), а все большие — в запасные (backup) серверы. Второе число (2) будет использоваться в качестве значения веса (weight) сервера, а третье число (9004) отвечает за порт сервера для проксирования.

Настройка DNS‑записей завершена, осталось изменить конфиг блока upstream для их использования.

upstream backend {
    zone upstream-backend 10m;
    server back.local.lan resolve service=http;
}

При использовании консоли мониторинга Angie Console Light можно посмотреть актуальный список апстримов (раздел HTTP-апстримы).

Статус апстримов
Статус апстримов

На скриншоте видно, что первые два сервера находятся в рабочем режиме, а два остальных являются запасными (b), также можно видеть веса серверов. Для работы в контейнерных средах наиболее удобным способом создания динамических групп серверов может стать работа с API напрямую (например, Docker API).

Управление с помощью Docker API

Работу с Docker API реализует модуль Docker. Он позволяет связаться с демоном Docker через API с использованием сокета. Далее Angie будет использовать специальные метки контейнеров для того, чтобы формировать список серверов в блоках upstream.

Первое, что нужно обеспечить модулю — связь с демоном через сокет. Адрес сокета (путь к файлу) по умолчанию /var/run/docker.sock.

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

Пользователь, с которым работает Angie должен иметь доступ на чтение и запись в этот сокет. Обычно это решается добавлением пользователя в группу docker, например для Ubuntu и пользователя www‑data достаточно следующей команды:

usermod -aG docker www-data

При подключении к Docker API на удалённых хостах связь с демоном будет осуществляться по HTTP или HTTPS. В этом случае может потребоваться аутентификация с использованием блока client. Ниже показан пример базовой HTTP аутентификации при удалённом доступе к Docker API. Также в примере есть внутренние локации для более тонкой настройки (@docker_events и @docker_containers).

http {
  docker_endpoint https://192.168.0.20:4242;
  client {
    proxy_set_header Host docker.example.com;
    proxy_set_header Authorization "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
    location @docker_events {
    }
    location @docker_containers {
    }
  }
}

Напоминаю, что базовая HTTP‑аутентификация не защищает передаваемые логин и пароль, строка аутентификации это логин и пароль, разделённые двоеточием, закодированные в base64. Поэтому при её использовании стоит настроить HTTPS.

echo `echo QWxhZGRpbjpvcGVuIHNlc2FtZQ== | base64 --decode`
Aladdin:open sesame

Теперь мы создаём привычную конфигурацию с сервером и блоком upstream, но без указания директивы server в этом блоке.

upstream u {
    zone upstream-backend 10m;
}

server {
    listen 80 default_server;

    server_name _;

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

Теперь необходимо подготовить контейнеры для серверов. Удобнее всего запускать из с помощью docker compose, а все метки указать в файле docker‑compose.yml. Ниже приведём пример такого файла для двух контейнеров.

services:
  debug-white:
    image: vscoder/webdebugger
    labels:
      - "angie.http.upstreams.u.port=8080"
      - "angie.network=docker_back_labels_app-network"
      - "angie.http.upstreams.u.weight=2"
      - "angie.http.upstreams.u.max_fails=3"
    container_name: debug-white
    restart: unless-stopped
    environment:
      APP_DELAY: 0
      APP_PORT: 8080
      APP_BGCOLOR: white
    networks:
      - app-network
    ports:
      - "9000:8080"
  debug-blue:
    image: vscoder/webdebugger
    labels:
      - "angie.http.upstreams.u.port=8080"
      - "angie.network=docker_back_labels_app-network"
      - "angie.http.upstreams.u.weight=1"
      - "angie.http.upstreams.u.max_fails=3"
    container_name: debug-blue
    restart: unless-stopped
    environment:
      APP_DELAY: 0
      APP_PORT: 8080
      APP_BGCOLOR: skyblue
    networks:
      - app-network
    ports:
      - "9001:8080"
networks:
  app-network:
    driver: bridge

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

docker compose up -d

При использования такой конфигурации можно увидеть список апстримов и их основные свойства.

Статус апстримов в контейнерах
Статус апстримов в контейнерах

Рассмотрим значения меток docker по отдельности:

  • angie.http.upstreams.u.port=8080 — внутренний порт контейнера для проксирования;

  • angie.network=docker_back_labels_app‑network — имя docker‑сети контейнера (к имени добавляется префикс директории, поэтому нужно указывать полное название сети);

  • angie.http.upstreams.u.weight=1 — параметр weight для директивы server;

  • angie.http.upstreams.u.max_fails=3 — параметр max_fails для директивы server.

Подробнее познакомиться со списком возможных меток можно в документации модуля Docker.

Все указанные возможности в статье применимы как для L7 HTTP(S), так и для L4 (TCP/UDP) проксирования.

Итоги

Мы рассмотрели три варианта организации динамических серверных групп: начиная от простого использования DNS, завершая работой с Docker API. Такие подходы значительно повышают гибкость управления инфраструктурой приложения и простоту обслуживания.

Комментарии (0)