Отказоустойчивый кластер с балансировкой нагрузки с помощью keepalived

в 15:53, , рубрики: Без рубрики

Отказоустойчивый кластер с балансировкой нагрузки с помощью keepalived - 1

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

Итак, предположим, что у нас есть сервер, который должен работать без перебоев. Его нельзя просто так взять и выключить посреди рабочего дня — клиенты или пользователи не поймут. Тем не менее любой сервер время от времени надо обслуживать, ставить обновления, менять аппаратную конфигурацию или сетевые настройки. Кроме того, нужно быть готовым к росту нагрузки, когда мощности сервера перестанет хватать. У сервера должна быть предусмотрена возможность быстрого масштабирования. Если вы озаботились этими проблемами, то вам нужно организовать отказоустойчивый кластер.

Чаще всего такие задачи приходится делать с HTTP-серверами. Я сегодня покажу сборку кластера на примере DNS, потому что опробовал технологию именно на нем, но с минимальными изменениями то же самое можно сделать и с серверами, работающими по TLS или HTTP.

Лирическое отступление о проблемах DNS

В принципе, DNS задуман так, чтобы необходимости городить огород с кластерами не возникало. В каждой зоне можно прописать множество NS-записей, в каждой сети можно раздать список DNS-серверов с помощью DHCP. DNS-сервера умеют реплицировать зоны, поэтому они хорошо масштабируются. Однако на практике это плохо работает. Когда я попробовал добавить 2-й DNS-сервер, то обнаружил, что

  1. Половина пользователей сидят со статическими настроенными адресами DNS-серверов. Ставят себе 4 восьмерки в качестве primary и локальный DNS в качестве secondary.
  2. Многие Linux-сервера не умеют из коробки корректно обновлять настройки DNS. Там такой зоопарк из glibc, nsswitch.conf, resolv.conf, NetworkManager, resolvconf, systemd-resolved, hosts, dhclient.conf и т. п., которые конфликтуют между собой, что рассчитывать на автоматическое обновление по DHCP просто не приходится.
  3. Windows шлет запросы одновременно на все сервера, но обязательно дожидается ответа или таймаута от 1-го
  4. Linux сначала обращается к 1-му DNS в списке и только в случае ошибки переходит к следующему.

Если долгое время в сети используется DNS-сервер с определенным IP, то он оказывается прописан в десятках разных мест. Например:

  • Директива resolver в nginx.conf
  • daemon.json в docker
  • В настройках docker-контейнеров
  • В конфигах модных нынче систем вроде kubernetes или openshift во внутренних файлах на каждой ноде и еще там же в конфигах dnsmasq.
  • В конфигах почтовых серверов.
  • В настройках VPN-серверов.

Никакими плейбуками и DHCP-декларациями это все обновить просто нереально. Поэтому было бы очень хорошо, если бы собираемый кластер можно было повесить на 1 уже существующий IP. Тогда для пользователей ничего не изменится, и софт перенастраивать не понадобится.

Поэтому я выбрал решение с keepalived и протоколом VRRP.

Подготовка

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

/var/named/zones/load.balance.zone

$ORIGIN load.balance.
$TTL 1h
load.balance. 86400 IN SOA ns.mydomain.ru. dnsmaster.mydoamin.ru. (
     1603065098 3600 1800 604800 30
)
load.balance. IN NS ns.mydomain.ru.
health IN TXT =nameserver-1=

На 1-м сервере ресурсная запись TXT для health.load.balance содержит текст =nameserver-1=, на 2-м сервере — =nameserver-2=, и т. д. Таким образом, отправляя запрос в кластер, я по ответу могу определить, какой сервер мне ответил, что очень удобно для отладки.

Если у вас HTTP-сервер, то поместите эту информацию в HTTP-заголовок. Например, для nginx я использую вот такую директиву

add_header serega-trace "$hostname" always;

Убедитесь, что ваши сетевые файерволлы не блокируют IP-трафик протокола 112 по адресу 224.0.0.18. Этот адрес будут использовать ваши сервера, чтобы договариваться между собой о том, кто из них MASTER, а кто BACKUP.

Открыть VRRP в iptables

iptables -t filter -I INPUT -p vrrp -d 224.0.0.18 -j ACCEPT
iptables -t filter -I OUTPUT -p vrrp -d 224.0.0.18 -j ACCEPT

При организации DNS нужно выделить мастер-сервер, на который не будут идти клиентские запросы. DNS-мастер используется только для управления. Он реплицирует свои зоны на slave-сервера, которые уже видны пользователям, и именно на них идет нагрузка. Далее речь будет идти о кластеризации именно DNS-slave.

Уровень 1 (Easy)

Если вам просто достаточно резервирования на случай аварии, то рекомендую рассмотреть самый быстрый и простой вариант. 2 одинаковых сервера разделяют между собой общий виртуальный IP (далее буду называть его VIP). У кого в данный момент в сетевом интерфейсе прописан VIP, тот сервер и работает. 2-й сервер следит за мастером (имеется в виду мастер VRRP, а не DNS), и как только обнаруживает, что он перестал вещать, сразу объявляет мастером себя и поднимает VIP на своем сетевом интерфейсе.

Меня приятно удивило то, как быстро удалось поднять кластер по такому варианту. Несмотря на то, что раньше я никогда не имел дело с keepalived, уже через час все работало. Так что если вам нужно решить задачу быстро, то уровень 1 вам подойдет в самый раз.

Сбор информации

Для успешного развертывания вам понадобится собрать следующую информацию. Здесь я привожу значения для примера. У вас эти значения должны быть свои.

Параметр Возможное значение Описание
vip 10.2.1.5 виртуальный IP, на который шлют запросы клиенты
dev0 eth0 1-й сетевой интерфейс на узлах кластера
ip01 10.2.1.2 IP 1-го узла кластера на 1-м сетевом интерфейсе
ip02 10.2.1.3 IP 2-го узла кластера на 1-м сетевом интерфейсе
net0 10.2.1.0/24 подсеть, которой принадлежат ip01 и ip02

Установите keepalived и snmpd. SNMP ставить необязательно, но мне кажется, что, если серверов с виртуальными IP будет много, он будет полезен.

setenforce 0 # если вдруг у вас selinux
dnf install -y keepalived nmap-ncat net-snmp net-snmp-utils
systemctl enable keepalived
systemctl enable snmpd

Netcat нужен для диагностики и healthcheck-ов.

В файл /etc/sysconfig/keepavlied добавьте опцию -x. Она нужна для взаимодействия keepalived с snmpd. Если вы не собираетесь поднимать SNMP, то этот шаг можете пропустить.

Отредактируйте файл /etc/snmp/snmpd.conf следующим образом:

/etc/snmp/snmpd.conf

master agentx
rocommunity public

В /etc/keepalived положите вот такой скрипт keepalived-notify.sh:

/etc/keepalived/keepalived-notify.sh

#!/bin/sh
umask -S u=rwx,g=rx,o=rx
exec echo "[$(date -Iseconds)]" "$0" "$@" >>"/var/run/keepalived.$1.$2.state"

Этот скрипт будет вызываться демоном keepalived при изменении состояния кластера. Он запишет в каталог /var/run статусный файл, который удобно использовать для диагностики.

Отредактируйте основной конфигурационный файл keepalived.conf следующим образом:

global_defs {
    enable_script_security
}

vrrp_script myhealth {
    script "/bin/nc -z -w 2 127.0.0.1 53"
    interval 10
    user nobody
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 5
    priority 100
    advert_int 1
    nopreempt
    notify /etc/keepalived/keepalived-notify.sh root
    authentication {
        auth_type PASS
        auth_pass KPSjXfRG
    }
    virtual_ipaddress {
        10.2.1.5
    }
    track_script {
        myhealth
    }
}

Блок global_defs содержит единственную необходимую настройку enable_script_security, которая по умолчанию отключена.

Блок vrrp_script описывает скрипт, который демон keepalived будет использовать для определения работоспособности своего сервера. Если этот скрипт вернет ошибку, то демон перейдет в состояние FAIL, и не будет претендовать на роль MASTER. В этом же блоке описывается периодичность выполнения healthchek-ов и указывается пользователь, от имени которого запускается скрипт. В нашем случае используется утилита netcat, которая устанавливает соединение c локальным DNS-сервером по TCP-порту 53. Можно использовать разные проверки, например, прозвонить UDP-порт 53 утилитой dig.

В блоке VRRP_INSTANCE задаются настройки 1 экземпляра сервера с виртуальным IP.

  • state задает начальное состояние сервера BACKUP или MASTER. В режиме nopreempt единственное допустимое значение — BACKUP.
  • interface указывает, на каком сетевом интерфейсе будет поднят VIP
  • virtual_router_id уникальный идентификатор роутера VRRP. Возможные значения от 1 до 255. У всех узлов кластера это значение должно быть одинаковым. Рекомендуется в качестве router_id использовать последний байт VIP, чтобы не запутаться, когда у вас таких виртуальных адресов будет много.
  • priority задает приоритет данного экземпляра при выборе мастера. Мастером назначается сервер, у которого значение параметра priority выше. Если у нескольких серверов priority одинаковый, то мастер будет выбран случайным образом.
  • advert_int определяет, с какой периодичностью мастер должен сообщать остальным о себе. Если по истечению данного периода сервера не получат от мастера широковещательное уведомление, то они инициируют выборы нового мастера.
  • nopreempt означает, что если мастер пропал из сети, и был выбран новый мастер с меньшим приоритетом, то по возвращении старшего мастера, он останется в состоянии BACKUP. Т. е. если вы перезагрузили мастер, то он больше мастером не станет, пока новый мастер не отвалится. Если вы предпочитаете, чтобы мастером был какой-то конкретный сервер, то замените настройку nopreempt на preempt_delay.
  • notify задает хук-скрипт, который будет вызываться при каждом изменении состояния сервера, и имя пользователя, от имени которого данный скрипт будет выполняться.
  • authentication задает пароль, длиной до 8 символов, который будет защищать кластер от случайных коллизий с другими серверами в локальной сети.
  • virtual_ipaddress задает VIP
  • track_script указывает на описание скрипта, осуществляющего healthcheck.

Выполните указанные настройки на обоих серверах кластера и запустите сервисы:

systemctl start snmpd
systemctl start keepalived

Проверьте логи и статусные файлы на наличие ошибок.

journalctl -u snmpd
journalctl -u keepalived
tail /var/run/keepalived.INSTANCE.VI_1.state

Если у вас используется selinux, не забудьте по данным audit.log обновить политики и вернуть enforcing mode командой set enforce 1.

Проверка

Если в логах ошибок нет, можно проверять. Поскольку мы кластеризовали DNS, то будем использовать dig. Для проверки HTTP-сервера подойдет curl.

Запустите на клиенте такой скрипт:

while true; do
    dig -4 +short +notcp +norecurse +tries=1 +timeout=1 
        -q health.load.balance. -t txt @10.2.1.5; 
    sleep 1;
done

Этот скрипт будет раз в секунду выдавать ресурсную запись health.load.balance/IN/TXT, которая содержит идентификатор сервера. В нашей конфигурации это будет тот сервер, который сейчас VRRP-мастер.

Зайдите на этот сервер и убедитесь, что в файле /var/run/keepalived.INSTANCE.VI_1.state в последней строке указано MASTER.

Перезапустите на мастере сервис keepalived. Если у вас все сделано правильно, то мастер поменяется. На 1-м сервере в файле keepalived.INSTANCE.VI_1.state появятся строки STOP и BACKUP, на втором сервере в этом же файле появится строка MASTER, а клиентский скрипт станет выдавать ресурсную запись с идентификатором нового мастера.

[2020-10-13T10:48:00+00:00] /etc/keepalived/keepalived-notify.sh INSTANCE VI_1 BACKUP 100
[2020-10-13T11:26:29+00:00] /etc/keepalived/keepalived-notify.sh INSTANCE VI_1 MASTER 100

"=nameserver-1="
"=nameserver-1="
"=nameserver-1="
"=nameserver-2="
"=nameserver-2="

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

Если у вас все получилось, значит вы успешно прошли 1-й уровень кластеризации. Он обладает следующими плюсами:

  • очень легко и быстро настраивается,
  • практически невозможно что-либо сломать,
  • на всех узлах кластера одинаковая конфигурация, что сильно сокращает трудоемкость администрирования.

Однако есть у данного варианта развертывания и существенный минус. В один момент времени в кластере работает только один сервер, а остальные, что называется, курят бамбук. Во-первых, это неэффективно с точки зрения использования вычислительных ресурсов. Во-вторых, если ваш сервер перестанет справляться с нагрузкой, то такой простенький кластер вам не поможет.

Чтобы обеспечить не только отказоустойчивость, но и масштабирование, переходите на следующий уровень кластеризации, о котором я расскажу прямо сейчас.

Уровень 2 (c балансировкой нагрузки)

Чтобы задействовать все сервера кластера, нужно научить VRRP-мастер, на сетевом интерфейсе которого прописан VIP, не только принимать трафик для своих локальных сервисов, но и направлять часть трафика на остальные сервера. Демон keepalived как раз умеет это делать.

Отредактируйте конфиг keepalived.conf, приведя его к следующему виду:

/etc/keepalived/keepalived.conf

global_defs {
    enable_script_security
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 5
    priority 100
    advert_int 1
    nopreempt
    notify /etc/keepalived/keepalived-notify.sh root
    authentication {
        auth_type PASS
        auth_pass KPSjXfRG
    }
    virtual_ipaddress {
        10.2.1.5
    }
}

virtual_server 10.2.1.5 53 {
    protocol UDP
    delay_loop 10
    lvs_sched rr
    lvs_method NAT

    real_server 10.2.1.2 53 {
        DNS_CHECK {
            type txt
            name health.load.balance.
        }
    }

    real_server 10.2.1.3 53 {
        DNS_CHECK {
            type txt
            name health.load.balance.
        }
    }
}

virtual_server 10.2.1.5 53 {
    protocol TCP
    delay_loop 10
    lvs_sched rr
    lvs_method NAT

    real_server 10.2.1.2 53 {
        TCP_CHECK {
            connect_timeout 3
        }
    }

    real_server 10.2.1.3 53 {
        TCP_CHECK {
            connect_timeout 3
        }
    }
}

Начало конфигурации аналогично предыдущему варианту без балансировки, только из блока vrrp_instance исчез track_script, соответственно за ненадобностью был удален блок vrrp_script.

Главное отличие в новой конфигурации заключается в блоках virtual_server. Для DNS требуется 2 виртуальных сервера, для 53-го порта TCP и для 53-го порта UDP. В случае HTTP-сервера аналогично потребуются сервера для 80-го и 443-го портов TCP.

Каждый виртуальный сервер идентифицируется 3 значениями: IP, порт и протокол. IP и порт через пробел указываются в заголовке блока virtual_server, а протокол определяется параметром protocol внутри блока. Допустимые протоколы TCP и UDP. В случае DNS как раз нужны оба.

Параметр delay_loop задает периодичность, с которой балансировщик опрашивает бэкенды для определения их состояния.

Самые важные параметры в виртуальном сервере — это lvs_sched и lvs_method. lvs_sched задает алгоритм, по которому балансировщик определяет, куда отправить очередной IP-пакет. lvs_method задает механизм, который использует балансировщик для направления пакетов в выбранный пункт назначения.

В приведенном примере lvs_sched равен rr, что означает round robin, т. е. балансировка равномерно по очереди. lvs_method используется NAT. Кроме NAT доступны также механизмы DR (direct routing) и TUN (tunneling). На мой взгляд, NAT — единственный рабочий вариант. Остальные методы работают только в очень специфических условиях.

Каждый виртуальный сервер состоит из нескольких реальных серверов, что отражено в соответствующих вложенных блоках real_server. Внутри блока real_server надо описать способ опроса его состояния, т. е. healthcheck.

После такой настройки keepalived работает следующим образом:

  1. Принимает IP-пакет с адресом получателя равным VIP.
  2. Выбирает real_server, куда необходимо направить пакет, анализируя протокол, порт, lvs_sched и результаты healthcheck-ов.
  3. Заменяет в IP-пакете адрес получателя на IP-адрес выбранного реального сервера и отправляет его дальше.

Только для работы этого недостаточно. Выбранный реальный сервер отправит ответный IP-пакет, в котором адрес отправителя будет равен его собственному IP-адресу. Клиент дропнет такой пакет, потому что он отправлял запрос на VIP и ожидает ответы от VIP, а не с IP-адреса реального сервера.

Чтобы решить эту проблему, нужно заставить реальный сервер отправить ответ не на IP клиента, а на IP мастера, чтобы тот выполнил обратную подстановку реального IP на виртуальный.

Для этого понадобится itpables и некоторые настройки ядра.

dnf -y install iptables iptables-services
systemctl enable iptables
systemctl start iptables

echo "net.ipv4.ip_forward=1" >>/etc/sysctl.d/99-sysctl.conf
echo "net.ipv4.vs.conntrack=1" >>/etc/sysctl.d/99-sysctl.conf
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.vs.conntrack=1

Добавьте в iptables следующее правило:

iptables -t nat -I POSTROUTING 1 -d 10.2.1.0/24 -j SNAT --to-source 10.2.1.5
service iptables save

Действие SNAT означает, что после маршрутизации в IP-пакете IP-адрес источника будет заменен на IP-адрес to-source. Вместо SNAT можно также использовать действие MASQUERADE, которое делает то же самое, только определяет исходящий IP автоматически.

Поскольку были внесены изменения в такие вещи как iptables и параметры ядра, рекомендуется перезагрузить сервер и убедиться, что ничего из этого не потерялось.

Выполните описанные действия на обоих серверах кластера. Проверьте логи и статусные файлы. Если ошибок нет, то выполните проверку работы кластера способом, описанным выше. Теперь должен получиться вот такой вывод:

"=nameserver-1="
"=nameserver-2="
"=nameserver-1="
"=nameserver-2="
"=nameserver-1="

Поскольку в балансировщике задан алгоритм round robin, то сервера отвечают строго по очереди друг за другом.

Остановите named на 2-м сервере, и получите:

"=nameserver-1="
"=nameserver-1="
"=nameserver-1="
"=nameserver-1="
"=nameserver-1="

Снова запустите на 2-м сервере named, и на клиенте снова начнется чередование ответов.

Не забудьте про корректировку политик selinux, о которой рассказано выше.

Поздравляю. Только что мы с вами прошли 2-й уровень кластеризации.

Теперь наш кластер не только отказоустойчивый, но и масштабируемый.

Тем не менее, данная конструкция имеет и большой минус. Из-за трансляции адресов на подчиненные сервера запросы приходят с VIP, и невозможно по логам определить, какой запрос от какого клиента пришел. Возможность вычислить клиента по IP очень полезна для решения различных проблем. Поэтому, мой совет — не останавливаться на 2-м уровне и, по возможности, перейти на 3-й.

Уровень 3 (Expert)

3-й уровень кластеризации подразумевает, что вы уже находитесь на 2-м, и все у вас работает как надо. Необходимо только обеспечить проброс клиентского IP-адреса до реальных серверов.

Принцип проброса следующий: все пакеты, приходящие на сервер, содержат IP реального клиента, а ответы на них отправляются на VIP. Т. е. VIP для реального сервера является шлюзом по умолчанию. Причем на этот шлюз направляются даже те пакеты, получатель которых находится в локальной подсети.

Но у серверов уже есть шлюз по умолчанию, и заменить его нельзя. Если убрать стандартный шлюз по умолчанию, то балансировка может быть и будет работать, однако все остальное сетевое взаимодействие у сервера сломается, и на него нельзя будет даже зайти по SSH.

Решение заключается в создании на сервере 2-го сетевого интерфейса. На 1-м сетевом интерфейсе будет производиться стандартный сетевой обмен, а на 2-м как раз и будет настроен VIP в качестве шлюза по умолчанию. Соответственно, у серверов вместе с дополнительным сетевым интерфейсом должен будет появиться и дополнительный IP-адрес.

Итак, добавьте на сервера кластера дополнительные сетевые интерфейсы и назначьте им IP-адреса.

Соберите информацию, необходимую для дальнейшей настройки. В нашем примере будут использоваться следующие значения:

Параметр Возможное значение Описание
vip 10.2.1.5 виртуальный IP, на который шлют запросы клиенты
dev0 eth0 1-й сетевой интерфейс на узлах кластера
dev1 eth1 2-й сетевой интерфейс на узлах кластера
ip01 10.2.1.2 IP 1-го узла кластера на 1-м сетевом интерфейсе
ip02 10.2.1.3 IP 2-го узла кластера на 1-м сетевом интерфейсе
ip11 10.2.1.6 IP 1-го узла кластера на 2-м сетевом интерфейсе
ip12 10.2.1.7 IP 2-го узла кластера на 2-м сетевом интерфейсе
net0 10.2.1.0/24 подсеть, которой принадлежат ip01 и ip02

Скорректируйте конфигурацию keepalived по указанному образцу.

/etc/keepalived/keepalived.conf

global_defs {
    enable_script_security
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 5
    priority 100
    advert_int 1
    nopreempt
    notify /etc/keepalived/keepalived-notify.sh root
    authentication {
        auth_type PASS
        auth_pass KPSjXfRG
    }
    virtual_ipaddress {
        10.2.1.5
    }
}

virtual_server 10.2.1.5 53 {
    protocol UDP
    delay_loop 10
    lvs_sched rr
    lvs_method NAT

    real_server 10.2.1.6 53 {
        DNS_CHECK {
            connect_ip 10.2.1.2
            type txt
            name health.load.balance.
        }
    }

    real_server 10.2.1.7 53 {
        DNS_CHECK {
            connect_ip 10.2.1.3
            type txt
            name health.load.balance.
        }
    }
}

virtual_server 10.2.1.5 53 {
    protocol TCP
    delay_loop 10
    lvs_sched rr
    lvs_method NAT

    real_server 10.2.1.6 53 {
        TCP_CHECK {
            connect_ip 10.2.1.2
            connect_timeout 3
        }
    }

    real_server 10.2.1.7 53 {
        TCP_CHECK {
            connect_ip 10.2.1.3
            connect_timeout 3
        }
    }
}

От предыдущей конфигурации новая отличается тем, что для реальных серверов указаны IP с дополнительных сетевых интерфейсов, но healthcheck-и отправляются на IP основных сетевых интерфейсов. Обратите внимание на этот момент. Все сервера кластера опрашивают друг друга, но на дополнительных сетевых интерфейсах будут настроены маршруты на VIP, поэтому узлы, которые сейчас в состоянии BACKUP, не смогут получить ответ от новых IP-адресов.

Теперь необходимо прописать правильные маршруты для дополнительных сетевых интерфейсов.

Добавьте в файл /etc/iproute2/rt_tables 2 новых таблицы маршрутизации. В примере ниже добавлены таблицы table0 и table1.

/etc/iproute2/rt_tables

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
20      table0
21      table1

По документации к NetworkManager и CentOS 8 статические маршруты и правила следует помещать в файлы /etc/syconfig/network-scripts/route-eth0 и rule-eth0. На многих моих серверах именно так и сделано. Только почему-то на серверах, поднятых из одного и того же образа, формат этих файлов оказался разным. На большинстве серверов route-eth0 выглядит так:

route-eth0 здорового человека

192.168.1.0/24 via 192.168.1.1
172.10.1.0/24 via 172.10.1.1

но почему-то на моих серверах DNS эти же файлы содержат вот это:

route-eth0 курильщика

ADDRESS0=192.168.1.0
NETMASK0=255.255.255.0
GATEWAY0=192.168.1.1
ADDRESS1=172.10.1.0
NETMASK1=255.255.255.0
GATEWAY1=172.10.1.1

Формат понятен, только совершенно непонятно, как в него поместить имя таблицы маршрутизации. Я не смог разобраться в этом вопросе. Если кто-то знает, в чем дело, поделитесь, пожалуйста, информацией в комментариях.

Поскольку сохранить маршруты в специальных системных файлах не удалось, пришлось сделать костыль.

/etc/keepalived/routes.sh

#!/bin/sh

# https://tldp.org/HOWTO/Adv-Routing-HOWTO/lartc.rpdb.multiple-links.html

vip="10.2.1.5"
dev0="eth0"
ip0="10.2.1.2" # "10.2.1.3"
dev1="eth1"
ip1="10.2.1.6" # "10.2.1.7"

ip route add 10.2.1.0/24 dev "$dev0" src "$ip0" table table0
ip route add default via 10.2.1.1 table table0
ip rule add from "$ip0" table table0
ip route add "$vip/32" dev "$dev1" src "$ip1"
ip route add default via "$vip" table table1
ip route add "$vip/32" dev "$dev1" src "$ip1" table table1
ip rule add from "$ip1" table table1

В комментарии в скрипте указаны значения, которые надо скорректировать при переносе скрипта на другой сервер.

Данный скрипт я добавил в автозапуск вместе с сервисом keepalived.

systemctl edit keepalived

[Service]
ExecStartPre=/etc/keepalived/routes.sh

Не самое элегантное решение, но работает нормально, поэтому можете спокойно использовать, если не знаете, как можно сделать лучше.

Принципы задания правил маршрутизации описаны в Linux Advanced Routing & Traffic Control HOWTO. Идея заключается в том, что создается 2 независимые таблицы маршрутизации, в каждой свой шлюз по умолчанию. В зависимости от того, с какого сетевого интерфейса отправляется пакет, с помощью правил ip rule выбирается либо одна таблица, либо другая.

Удалите из iptables правило SNAT в цепочке POSTROUTING, добавленное при прохождении 2-го уровня. Сохраните состояние iptables.

iptables -t nat -D POSTROUTING -d 10.2.1.0/24 -j SNAT --to-source 10.2.1.5
service iptables save

Проверьте результат. Клиент должен получать ответы от каждого сервера поочередно, в логах на серверах должны писаться реальные IP клиентов.

Теперь имеется отказоустойчивый кластер, в котором все живые узлы принимают нагрузку и видят IP-адреса своих клиентов. Единственный небольшой минус у этой конфигурации заключается в том, что есть 1 скрипт, который на разных серверах должен иметь разные параметры. Небольшое усложнение администрирования, которое с лихвой окупается полученными преимуществами.

Сложность Масштабирование Единые настройки IP клиента
Уровень 1 Easy Нет Да Да
Уровень 2 Normal Да Да Нет
Уровень 3 Expert Да Нет Да

Автор: Сергей Б.

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js