Если в настройках 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, но и в общесистемных.