# теория · шаг 2 из 5
Keepalive и выкладка без простоя
Коротко
Два вопроса эксплуатации группы: не тратить время на установку соединений и выкладывать новую версию так, чтобы пользователь не увидел ни одной ошибки.
- Keepalive к бэкенду с 1.29.7 включён сам, но только в группе: у
литерального адреса в
proxy_passкаждое соединение новое. - Имена серверов группы резолвятся (превращаются в IP-адрес) при старте. Пересоздал контейнер - nginx
ходит на чужой адрес, пока не сделаешь
reload. - Хуже: если имя не резолвится, nginx не стартует и не перезагружается. Один мёртвый контейнер блокирует правку любого другого сайта.
- Лечит
zoneплюсresolverвнутри группы плюсresolveу сервера - в открытой версии это работает с ветки 1.27.
Если знаешь, что имена группы резолвятся при старте, и видел параметр resolve - листай до «Теперь сам».
Сначала ответь сам
Docker, группа из двух серверов по именам. Ты пересоздал контейнер a1, и он
получил другой адрес. Контейнеры живы, из соседнего контейнера всё открывается.
Что покажет лог nginx?
/after -> 172.25.0.2:8000, 172.25.0.4:8000 status=200 ustatus=502, 200
/after -> 172.25.0.4:8000 status=200
/after -> 172.25.0.4:8000 status=200
nginx ходит на 172.25.0.2 - адрес, которого у a1 давно нет (в замере его
успел занять посторонний контейнер). Клиенты получают 200, потому что выручает
повтор, а половина мощности при этом выключена. Реальный адрес a1 в этот
момент - 172.25.0.8.
На что это похоже
Записанный на бумажке телефон коллеги. Пока номер не менялся, бумажка работает и кажется надёжнее телефонной книги. В день, когда коллега сменил номер, ты продолжаешь звонить по старому - и попадаешь к незнакомому человеку, который на этот номер только что переехал.
Именно так и выглядит худший вариант: если по старому адресу окажется чужой живой сервис, nginx начнёт отправлять к нему пользовательские запросы.
Механизм: когда именно резолвится имя
Правило
upstream app {
zone app 64k; # общая память обязательна
resolver 127.0.0.11 valid=5s; # DNS указывают ВНУТРИ группы
server a1:8000 resolve;
server a2:8000 resolve;
keepalive 32;
}
Три условия, и все обязательны. Без zone конфиг не соберётся:
nginx: [emerg] resolving names at run time requires upstream "app" in /etc/nginx/nginx.conf:11 to be in shared memory
nginx: configuration file /etc/nginx/nginx.conf test failed
resolver именно внутри группы - в server его не видно:
nginx: [emerg] no resolver defined to resolve names at run time in upstream "app" in /etc/nginx/nginx.conf:11
nginx: configuration file /etc/nginx/nginx.conf test failed
Проверено на стенде: после смены адреса контейнера группа с resolve сама
перешла на новый адрес, а соседняя группа без него продолжала стучаться в
старый. Ветка 1.27 это умеет; 1.24 и 1.26 отвечают "resolver" directive is
not allowed here.
Разбор: мёртвый контейнер блокирует весь nginx
Это хуже устаревшего адреса, и на это натыкаются реже, потому что беда приходит в неудачный момент.
nginx: [emerg] host not found in upstream "a1:8000" in /etc/nginx/nginx.conf:11
nginx: configuration file /etc/nginx/nginx.conf test failed
Конфиг не проходит проверку. Значит:
nginx -s reloadне сработает - и правка любого другого сайта на этой машине не применится, пока контейнер не поднимут;- при перезапуске службы nginx не стартует вовсе. Один остановленный бэкенд роняет весь фронт.
Проверено на стенде обеими сторонами: с обычной записью старт падает, с
resolve тот же конфиг с несуществующим именем стартует нормально и просто
считает сервер недоступным.
Разбор: keepalive работает не везде
Замер портов на приёмной стороне, шесть запросов подряд:
| как записан бэкенд | порты |
|---|---|
proxy_pass http://app:8000 |
36494, 36498, 36508, 36518, 36524, 36538 |
тот же адрес внутри upstream |
34576, 34576, 34576, 34576, 34576, 36542 |
С 1.29.7 кеш соединений включён по умолчанию (keepalive 32), версия к бэкенду
1.1, свой Connection nginx не шлёт. Но группой литеральный адрес не
является, и переиспользование его не касается: каждый запрос - новое
TCP-соединение, а к HTTPS-бэкенду ещё и рукопожатие.
keepalive задаёт не общее число соединений, а размер кеша простаивающих:
сколько держать про запас на каждый воркер. Слишком большое значение просто
держит лишние сокеты на бэкенде.
Что ломается без этого
Три строки из старых руководств - keepalive в группе, proxy_http_version
1.1 и proxy_set_header Connection "" в location - на свежих версиях
необязательны. Вредными они не стали, и в чужом конфиге их не надо вычищать.
Одно место, где явный Connection "" по-прежнему встречается, - блок, где в тот
же location проксируется WebSocket. Там Connection выставляется через map, и
для обычных запросов map отдаёт close. У этого есть цена: Connection: close
просит бэкенд закрыть соединение после ответа, то есть отменяет keepalive к нему -
замер это подтверждает, с close у каждого запроса новый порт. На
WebSocket-маршруте это не беда: долгие соединения живут сами по себе. Но общий
трафик под keepalive в такой блок заводить не стоит - для него Connection к
бэкенду оставляют пустым.
Зачем это в работе
Выкладка без потерянных запросов в открытой версии - это цикл по одному серверу за раз:
# 1. вывести из ротации (строка в конфиге записана ровно как `server a1:8000;`)
sed -i 's|server a1:8000;|server a1:8000 down;|' /etc/nginx/nginx.conf
nginx -t && nginx -s reload
sed -i правит файл на месте, s|что|на что| - замена (вертикальная черта
вместо обычного слеша, чтобы не сталкиваться со слешами в путях), && запускает
reload только если nginx -t прошёл. Шаблон должен совпасть дословно: если в
конфиге стоит server a1:8000 resolve;, строку server a1:8000; sed не найдёт
и молча ничего не поменяет - правь шаблон под свою строку.
Дальше: подождать, пока старые воркеры дотянут начатые запросы (границу ставит
worker_shutdown_timeout), обновить приложение, дождаться, пока его собственный
/health ответит 200, вернуть строку без down и снова reload. Потом то же
со вторым сервером.
Проверять /health до возврата в ротацию обязательно: slow_start в открытой
версии нет, вернувшийся сервер сразу получит полную долю трафика, и роль
проверки готовности выполнят первые пользователи.
Если у группы стоит sticky, вместо down удобнее drain: закреплённые
клиенты дообслуживаются на своём сервере, новые туда не приходят.
Теперь сам
В docker-compose пять сайтов на одном nginx. Разработчик остановил контейнер
приложения одного из них, чтобы «не мешался», и через час другой инженер
поправил конфиг соседнего сайта и сделал reload. Что произошло?
Reload не применился: nginx -t упал на host not found in upstream из-за
остановленного контейнера. Правка соседнего сайта не доехала, а инженер этого
мог не заметить - старый конфиг продолжает работать. Чинится либо поднятием
контейнера, либо переводом групп на zone плюс resolver плюс resolve, чтобы
имена перестали быть условием старта.
Главное
Keepalive к бэкенду с 1.29.7 включён сам, но работает только внутри upstream:
литеральный адрес в proxy_pass открывает соединение на каждый запрос. Имена
серверов группы резолвятся при старте, поэтому пересозданный контейнер получает
трафик по старому адресу, а имя, которое не резолвится, не даёт nginx ни
перезагрузиться, ни стартовать. Лечится тройкой zone плюс resolver внутри
группы плюс resolve у сервера - в открытой версии с ветки 1.27. Выкладка без
простоя - цикл «пометил down → reload → обновил → дождался /health → вернул
→ reload» по одному серверу, а при sticky вместо down берут drain.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий