Использование DNS-резольвера Docker в nginx для балансировки нагрузки между контейнерами

Если использовать nginx в качестве проксирующего фронтенда к контейнерам с бэкендом, возникает вопрос, как распределять равномерно запросы к ним. Нельзя просто так определить блок upstream, т. к. контейнеры создаются динамически docker-compose, мы не знаем, сколько и какие будут IP.

Предположим, масштабируется бэкенд app:

$ docker-compose up -d --scale app=5

Мы запустили 5 контейнеров app, каждый из них получил свой IP:

$ docker-compose run --rm nginx /bin/bash -c 'dig +short app'

Если в nginx определить следующую конфигурацию:

server {
    listen 80;
    server_name localhost;

    location / {
        ....

        proxy_pass http://app:8000;
    }
}

Все запросы будут отправляться в контейнер app_1. Потому что nginx кэширует результаты DNS-резолвинга до следующей перезагрузки. Выполните несколько запросов к nginx и посмотрите логи:

$ for i in `seq 5`
> do
> curl -s -o /dev/null localhost
> done

$ docker-compose logs -f app

Вы обнаружите, что app_1 берет на себя все, а остальные контейнеры простаивают.

Round-robin с помощью переменных и DNS-резольвера Docker

Выдержка из документации nginx к директиве proxy_pass:

The address can also be specified using variables (1.11.3):

proxy_pass $upstream;

In this case, the server name is searched among the described server groups, and, if not found, is determined using a resolver.

Использование переменных заставляет nginx определять IP с помощью указанного резольвера. Встроенный в Docker DNS-сервер (127.0.0.11) преобразует имя сервиса в фактические IP-адреса контейнеров. Он реализует циклический перебор (Round-robin).

То есть, если в блоке server определить переменную, содержащую хост сервиса, и указать DNS-резольвер Docker, то проблема, связанная с кэшированием, отпадет:

resolver 127.0.0.11;
set $apps app;

location / {
    ....
    proxy_pass http://$apps:8000;
}

Недостатки

Описанный подход удобен для некоторых проектов, но не универсален, т. к. имеет важные недостатки.

Отказываясь от блока upstream, мы отказываемся от полезных функций, предоставляемых одноименным модулем nginx, в том числе политика балансировки нагрузки, вес бэкендов и мониторинг доступности. Для проектов с серьезными требованиями стоит присмотреться к таким инструментам, как traefik и nginx-proxy.