Nginx и DNS

26 ноября 2019

Если в настройках nginx используются доменные имена (например, в upstream/server), неправильная настройка DNS может привести как к медленной работе, так и к неработоспособности.

Сначала про неработоспособность:

nginx.service для Systemd содержит зависимость от network-online.target. Однако где гарантия, что DNS-серверы окажутся доступны к этому моменту? Дерево зависимостей network-online является следующим:

# systemd-analyze critical-chain network-online.target

network-online.target @10.298s
└─NetworkManager-wait-online.service @4.385s +5.913s
  └─NetworkManager.service @3.432s +951ms
    └─dbus.service @3.307s
      └─basic.target @3.159s
        └─sockets.target @3.159s
          └─snapd.socket @3.154s +4ms
            └─sysinit.target @3.153s
              └─systemd-backlight@backlight:intel_backlight.service @2.689s +463ms
                └─system-systemd\x2dbacklight.slice @2.688s
                  └─system.slice @1.187s
                    └─-.slice @1.184s

Как нетрудно видеть, упоминаний про распознавание DNS-имён в нём нет.

Следовательно, если система и Nginx используют локальный DNS-сервер — нет гарантии, что он окажется запущен.

Если используют внешний — нет гарантии, что достижение network-online.target приведёт к наличию связи с ним. Например, после активации физического линка может потребоваться установить BGP-сессию (кроме того, ниже мы объясняем, почему внешние DNS — это плохо).

Поэтому рекомендуется указывать для nginx.service дополнительную зависимость — от nss-lookup.target:

# systemd-analyze critical-chain nss-lookup.target

nss-lookup.target @4.893s
└─unbound.service @4.481s +411ms
  └─network.target @4.385s
    └─NetworkManager.service @3.432s +951ms
      └─dbus.service @3.307s
        ...

Хотя и её может оказаться недостаточно, т.к. systemd может преждевременно счесть сервис активным, потому что не всегда умеет отличать запущенный сервис от активного.

Например, для Type=simple сервис считается активным сразу после запуска процесса, а для Type=forking — после успешного завершения master-процесса, если остался работать хотя бы один slave.

Однако сервису может потребоваться относительно долгая инициализация, прежде чем он сможет обрабатывать запросы. Для надёжной обработки таких ситуаций в systemd имеются Type=notify и Type=dbus, но часто ли они встречаются в реальной жизни?

И здесь мы подходим к сути проблемы:

Если при старте системы Nginx получит ошибку ресолвинга, потому что успеет обратиться к DNS-сервису, пока тот недоступен/инициализируется/не имеет связи с внешним миром/..., то откажется стартовать.

Поэтому если конфигурация Nginx содержит проксирование с DNS-именами, перед запуском Nginx необходимо убедиться, что DNS-распознавание уже работает. Оно может быть простейшим. Например, создайте /etc/systemd/system/nginx.service.d/WaitDNS.conf с таким содержимым:

[Service]
ExecStartPre=/bin/sh -c 'for a in $(seq 1 30); do host ya.ru && exit; sleep 1; done; exit 1'

Теперь про быстродействие:

Мы столкнулись с тем, что Nginx на одном из узлов стал медленно перечитывать настройки.

strace для "nginx -t" показал, что задержка происходит при обращениях к внешнему DNS-серверу:

0.000028 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0 <0.000014>
0.000033 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}]) <0.000018>
0.000053 sendto(5, "\352\256\1\0\0\1\0\0\0\0\0\0\4hls4\10alkotest\2ua\0\0\1"..., 34, MSG_NOSIGNAL, NULL, 0) = 34 <0.000013>
0.000031 poll([{fd=5, events=POLLIN}], 1, 2000) = 0 (Timeout) <2.002036>
2.002101 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 6 <0.000014>

Конечно, Nginx имеет встроенный DNS-ресолвер (асинхронный, производительный, кэширующий и т.д., как всё в Nginx'e), но при чтении настроек он НЕ ИСПОЛЬЗУЕТСЯ, потому что сам от них зависит, т.е. начинает работать только после (и в зависимости от) того, как настройки прочитаны.

Поэтому при разборе настроек для проверки URL-имён Nginx-функция ngx_parse_url вызывает стандартные системные вызовы gethostbyname/getaddrinfo, со всеми вытекающими последствиями:

  • отправка запросов в один поток;
  • синхронное ожидание ответа;
  • использование серверов и настроек, указанных в /etc/resolv.conf.

На локальной системе был запущен DNS-сервер Unbound, и ресолвер Nginx'a был настроен обращаться к нему. А в /etc/resolv.conf был указан гугловский 8.8.8.8, к которому полагалось обращаться всем остальным приложениям, не доросшим до обладания собственным встроенным ресолвером (асинхронным, кэширующим и т.д.).

Время таймаута также читалось из /etc/resolv.conf:

options timeout:2

Соответственно, пропуск нескольких процентов запросов (или ответов) приводил к многосекундным задержкам.

Вывод:

  • если настройки Nginx'a содержат в директивах proxy_pass и upstream/server много доменных имён, для их распознавания следует использовать кэширующий DNS-сервер, запущенный на этом же компьютере, чтобы DNS-запросы выполнялись (а) с максимальной скоростью и (б) минимальной зависимостью от состояния сети;
  • 127.0.0.1 в качестве адреса DNS должен быть указан не только в настройках Nginx'a, но и в общесистемных.


← Назад в Блог

Подпишитесь на новые статьи: