Перейти к содержанию

4. Остановка и перезапуск HAProxy

Сигналы, мягкая остановка, перезагрузка конфигурации и перезапуск в режиме master-worker

HAProxy поддерживает плавную и жёсткую остановку. Жёсткая остановка проста: при отправке процессу haproxy сигнала SIGTERM он немедленно завершается, а все установленные соединения закрываются. Плавная остановка запускается сигналом SIGUSR1, отправленным процессу haproxy. При этом процесс лишь освобождает прослушиваемые порты, продолжая обрабатывать существующие соединения до их закрытия. После закрытия последнего соединения процесс завершается.

Жёсткая остановка используется для действий «stop» и «restart» сценария управления службой. Плавная остановка применяется для действия «reload», которое пытается незаметно для клиентов загрузить новую конфигурацию в новом процессе.

Оба сигнала может отправлять сам новый процесс haproxy при перезагрузке конфигурации или перезапуске, чтобы это происходило как можно позже и только при крайней необходимости. Именно это делают параметры «-st» (жёсткая остановка) и «-sf» (плавная остановка) соответственно.

В режиме master-worker для перезагрузки конфигурации не требуется запускать новый процесс haproxy. Получив сигнал SIGUSR2, главный процесс повторно запускает себя с параметром -sf, за которым следуют PID рабочих процессов. Затем главный процесс разбирает файл конфигурации и создаёт новые рабочие процессы с помощью fork.

Чтобы лучше понять применение этих сигналов, важно разобраться во всём механизме перезапуска.

Изначально уже работает процесс haproxy. Администратор выполняет команду, принятую в данной системе, например «/etc/init.d/haproxy reload», чтобы применить новый файл конфигурации. Далее происходит следующее. Сначала сценарий службы (/etc/init.d/haproxy или его аналог) проверяет корректность разбора файла конфигурации командой «haproxy -c». Затем он пытается запустить haproxy с этим файлом конфигурации, используя «-st» или «-sf».

После этого HAProxy пытается привязаться ко всем прослушиваемым портам. При фатальной ошибке (например, если адрес отсутствует в системе или доступ запрещён) процесс завершается с ошибкой. Если привязка сокета не удалась из-за того, что порт уже занят, процесс сначала отправляет сигнал SIGTTOU всем PID из списка, заданного в «-st» или «-sf». Это так называемый сигнал приостановки: он предписывает всем существующим процессам haproxy временно прекратить прослушивание своих портов, чтобы новый процесс мог повторить попытку привязки. В это время старый процесс продолжает обрабатывать существующие соединения. Если привязка по-прежнему не удаётся (например, порт занят другим демоном), новый процесс отправляет старым процессам сигнал SIGTTIN, предписывая возобновить работу как ни в чём не бывало. Старые процессы снова начинают прослушивать порты и принимать соединения. Обратите внимание: этот механизм зависит от системы, и некоторые операционные системы могут не поддерживать его в многопроцессном режиме.

Если новому процессу удалось привязаться ко всем портам, он отправляет всем процессам либо SIGTERM (жёсткая остановка при «-st»), либо SIGUSR1 (плавная остановка при «-sf»), уведомляя их, что теперь он обслуживает трафик, а старые процессы должны завершиться — немедленно или после окончания своей работы.

Важно учитывать, что в этот промежуток есть два небольших окна длительностью по несколько миллисекунд, в течение которых при высокой нагрузке возможны единичные сбои соединений. Обычно наблюдается около 1 сбоя при перезагрузке конфигурации на каждые 10000 новых соединений в секунду. Это означает, что высоконагруженный сайт с 30000 новых соединений в секунду может получать примерно 3 неудачных соединения при каждой перезагрузке конфигурации. Такие сбои возникают в двух случаях:

  • Если новый процесс не может привязаться к порту из-за старого процесса, ему сначала приходится выполнить последовательность SIGTTOU+SIGTTIN. Для нескольких десятков фронтендов она обычно занимает около одной миллисекунды, в течение которой часть портов уже освобождена старым процессом, но ещё не занята новым. HAProxy обходит эту проблему в системах с поддержкой параметра сокета SO_REUSEPORT, который позволяет новому процессу привязаться к порту, не запрашивая предварительно его освобождение у старого. Большинство систем BSD поддерживают его практически всегда. Linux поддерживал его в версии 2.0, но примерно в версии 2.2 поддержка была удалена, хотя к тому времени существовали отдельные патчи. Она вернулась в ядре 3.9, поэтому, если частота сбоев соединений выше указанной, убедитесь, что используете ядро 3.9 или новее либо что соответствующие патчи перенесены в ваше ядро (что менее вероятно).

  • Когда старые процессы закрывают прослушиваемые порты, ядро не всегда перераспределяет ожидающие соединения, оставшиеся в очереди сокета. При высокой нагрузке пакет SYN может прийти непосредственно перед закрытием сокета, что приведёт к отправке клиенту пакета RST. В некоторых критически важных средах, где недопустима даже одна потеря, с этим иногда борются с помощью правил межсетевого экрана, блокирующих пакеты SYN на время перезагрузки конфигурации и вынуждающих клиента повторить передачу. Поведение целиком зависит от системы: некоторые системы могут просмотреть другие очереди прослушивания и избежать отправки RST. Второй случай касается клиентского ACK для локального сокета, который непосредственно перед закрытием находился в состоянии SYN_RECV. Этот ACK вызовет отправку RST, хотя процесс haproxy ещё ничего об этом не знает. Избавиться от такого случая сложнее, но упомянутые правила фильтрации межсетевого экрана хорошо сработают, если применить их примерно за секунду до перезапуска процесса.

У подавляющего большинства пользователей подобных потерь никогда не возникает, поскольку нагрузки недостаточно для возникновения гонок. А для большинства пользователей с большим объёмом трафика частота сбоев остаётся в пределах статистического шума, если их системы хотя бы корректно поддерживают SO_REUSEPORT.