Haproxy: настраиваем active/standby балансировку — Fastenv

Fastenv - системное администрирование от профессионалов индустрии. Работаем с linux, уважаем opensource.

Haproxy: настраиваем active/standby балансировку
Сложность: Средняя | Автор: fastenv | May 2, 2016

Постановка задачи

Имеется два экземпляра http приложения. Требуется настроить работу прокси сервера так, чтобы под нагрузкой всегда находился только один. Активный экземпляр должен оставаться в работе до тех пор, пока не будет выключен или признан нерабочим.

Проблема

Haproxy предлагает множество алгоритмов балансировки, но, к сожалению, ни один из них не подходит под наши требования. Чаще всего вместо Active-Standby используют Active-Backup, когда создается бекенд с roundrobin балансировкой, в котором один из серверов помечается ключевым словом backup. Такой подход обладает следующим недостатком: трафик будет возвращаться на «упавшую» ноду всякий раз, как она будет помечена UP проверкой check. Иными словами, если сервер «сбоит» под нагрузкой, то трафик будет прыгать «туда-сюда». Для некоторых приложений это может оказаться критичным.

Решение

backend fastenvAS
        stick-table type ip size 3 store gpc0
        tcp-request content track-sc0 dst
 
        acl srv1_up srv_is_up(server1)
        acl srv2_up srv_is_up(server2)
 
        acl srv1 sc0_get_gpc0(fastenvAS) eq 1
        use-server server1 if srv1 srv1_up
 
        acl srv2 sc0_get_gpc0(fastenvAS) eq 2
        use-server server2 if srv2 srv2_up
 
        acl fake1 sc0_clr_gpc0 gt 0
        use-server fake if fake1 { always_false }
 
        acl fake2 sc0_inc_gpc0 gt 0
        use-server fake if fake2 { always_false }
        use-server server1 if srv1_up
 
        acl fake3 sc0_inc_gpc0 gt 0
        use-server fake if fake3 { always_false }
        use-server server2 if srv2_up
 
        server fake 127.0.0.1:1300 weight 0
        server server1 172.17.44.50:1300 check
        server server2 172.17.44.51:1300 check

Ключевая идея: в stick табличке храним число-индикатор активного сервера. Если этот сервер в рабочем состоянии, то отдаем запрос ему, если же нет — выполнение переходит к другим условиям.
Последовательно разберем используемые хаки:

  1. Первый трюк заключается в том, что мы храним в табличке dst, т.е. destination ip, а это, на tcp-уровне адрес самого haproxy. Вместе с ним хранится gpc0 — first General Purpose Counter, что не иначе как просто какой-то счетчик, который вы используете для своих целей.
    stick-table type ip size 3 store gpc0
    tcp-request content track-sc0 dst
  2. По нашему замыслу, значение gpc0=K сигнализирует о том, что в нагрузке находится K-й сервер. В соответствии с этим описываем блоки acl:
    acl srv1 sc0_get_gpc0(fastenvAS) eq 1
    use-server server1 if srv1 srv1_up
    ...

    Тут скрыт еще один нетривиальный момент. Для какого объекта будет выполнена sc0_get_gpc0? Для ip адреса связанного с текущим запросом, а это, как мы попросили прокси, всегда его собственный адрес.

  3. Рассмотрим следующий трюк:
    acl fake1 sc0_clr_gpc0 gt 0
    use-server fake if fake1 { always_false }

    Если управление перешло в эту точку, значит активный сервер у нас еще не выбран. Или же с ним что-то случилось. Так уж вышло, что haproxy опускает условия, которые нигде не используются. Мы описали fake сервер только для того, что бы вызывать aсl`ки работающие с gpc0. Смысл самой sc0_clr_gpc0 простой — обнуляем счетчик.

  4. Далее действуем аналогично:
    acl fake2 sc0_inc_gpc0 gt 0
    use-server fake if fake2 { always_false }
    use-server server1 if srv1_up
    1. Инкрементируем счетчик.
    2. Является ли этот сервер живым?
      1. Да — используем его, управление заканчивается.
      2. Нет — переходим к следующему аналогичному блоку.

Внимательный читатель, вероятно, обратил внимание: в нашем примере значение gpc0 < 3, тогда зачем мы используем конструкцию вида: { always_false }? Ведь достаточно немного изменить числа, и значения acl и так всегда будут False. Никакого скрытого смысла в этом нет. Это сделано для выразительности конфига.