Keepalived и VRRP: нулевой даунтайм для платёжного шлюза банка

Задача: zero downtime для платёжного шлюза

Банк «ФинОнлайн» обрабатывает 8 000 платежей в минуту через собственный платёжный шлюз. Любой простой — это потерянные транзакции и штрафы от платёжных систем. Существующая архитектура: один сервер с HAProxy → два backend-сервера с приложением. HAProxy был единственной точкой отказа.

Требования были жёсткими:

  • RTO (Recovery Time Objective) — менее 5 секунд. Платёжная система Visa допускает таймаут ответа 30 секунд, но каждая секунда простоя — это 133 потерянных транзакции.
  • RPO (Recovery Point Objective) — 0. Ни одна транзакция не должна быть потеряна.
  • Автоматический failover — без ручного вмешательства, 24/7.
  • Уведомления — дежурный инженер должен узнать о переключении в течение 10 секунд.

Решение — Keepalived с протоколом VRRP для автоматического переключения виртуального IP между двумя HAProxy-серверами.

Как работает VRRP

VRRP (Virtual Router Redundancy Protocol, RFC 5798) — протокол, позволяющий нескольким серверам делить один виртуальный IP-адрес (VIP). В любой момент времени VIP принадлежит только одному серверу — MASTER. Остальные серверы — BACKUP — ждут своей очереди.

Механизм работы:

  1. MASTER периодически отправляет VRRP Advertisement пакеты (по умолчанию каждую секунду) по multicast 224.0.0.18.
  2. BACKUP слушают эти пакеты. Если пакеты перестают приходить (MASTER упал) — BACKUP с наивысшим приоритетом становится новым MASTER.
  3. Новый MASTER отправляет Gratuitous ARP, обновляя ARP-таблицы на всех устройствах в сети. Трафик переключается на новый сервер.

Время переключения = advert_int * (256 - priority_backup) / 256. При advert_int=1 и priority=100 время обнаружения — около 3.6 секунды. С параметрами нашей конфигурации — менее 4 секунд.

Установка Keepalived:

apt install -y keepalived

# Разрешаем binding на несуществующий IP (VIP может быть не на этом сервере)
echo "net.ipv4.ip_nonlocal_bind = 1" >> /etc/sysctl.conf
sysctl -p

Полная конфигурация keepalived.conf

Конфигурация MASTER-ноды (haproxy-01, IP 10.0.0.1):

global_defs {
    router_id HAPROXY_01
    script_user root
    enable_script_security
    # Уведомление при смене состояния
    notification_email {
        admin@finonline.ru
    }
    notification_email_from keepalived@finonline.ru
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
}

# Скрипт проверки HAProxy
vrrp_script chk_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    interval 2      # Проверяем каждые 2 секунды
    weight -30       # При неудаче снижаем приоритет на 30
    fall 2           # 2 неудачи подряд = сервис упал
    rise 2           # 2 успеха подряд = сервис восстановлен
    timeout 3        # Таймаут выполнения скрипта
}

# Скрипт проверки backend-ов
vrrp_script chk_backends {
    script "/etc/keepalived/check_backends.sh"
    interval 5
    weight -15
    fall 3
    rise 2
    timeout 5
}

vrrp_instance VI_PAYMENT {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 110                 # MASTER: 110, BACKUP: 100
    advert_int 1                 # Advertisement каждую секунду
    nopreempt                    # Не забирать VIP обратно при восстановлении

    # Unicast вместо multicast — надёжнее в облачных средах
    unicast_src_ip 10.0.0.1
    unicast_peer {
        10.0.0.2
    }

    authentication {
        auth_type PASS
        auth_pass Fin0nL1ne_VRRP!
    }

    virtual_ipaddress {
        10.0.0.100/24 dev eth0 label eth0:vip
    }

    track_script {
        chk_haproxy
        chk_backends
    }

    # Notify-скрипты для алертов
    notify_master "/etc/keepalived/notify.sh MASTER"
    notify_backup "/etc/keepalived/notify.sh BACKUP"
    notify_fault  "/etc/keepalived/notify.sh FAULT"
    notify_stop   "/etc/keepalived/notify.sh STOP"
}

Конфигурация BACKUP-ноды (haproxy-02, IP 10.0.0.2) — отличия минимальны:

global_defs {
    router_id HAPROXY_02
    # ... остальное идентично
}

vrrp_instance VI_PAYMENT {
    state BACKUP               # Роль — BACKUP
    interface eth0
    virtual_router_id 51       # Должен совпадать с MASTER!
    priority 100               # Ниже, чем у MASTER (110)
    advert_int 1

    unicast_src_ip 10.0.0.2
    unicast_peer {
        10.0.0.1
    }
    # ... остальное идентично
}

Параметр nopreempt означает, что после failover на BACKUP, даже когда MASTER восстановится — VIP останется на BACKUP. Это предотвращает «пинг-понг» при нестабильном сервисе на MASTER. Возврат VIP на MASTER производится только вручную после проверки.

Health check скрипты

Скрипт проверки HAProxy — проверяет не только процесс, но и реальную работоспособность:

#!/bin/bash
# /etc/keepalived/check_haproxy.sh

# 1. Проверяем, работает ли процесс HAProxy
if ! killall -0 haproxy 2>/dev/null; then
    echo "HAProxy process not running"
    exit 1
fi

# 2. Проверяем, отвечает ли HAProxy stats
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
    --max-time 3 \
    http://127.0.0.1:8404/haproxy-stats)

if [ "$STATUS" != "200" ] && [ "$STATUS" != "401" ]; then
    echo "HAProxy stats not responding: HTTP $STATUS"
    exit 1
fi

# 3. Проверяем через stats socket, есть ли активные бэкенды
ACTIVE=$(echo "show stat" | socat stdio /run/haproxy/admin.sock 2>/dev/null | \
    grep "be_app" | grep -c "UP")

if [ "$ACTIVE" -lt 1 ]; then
    echo "No active backends in HAProxy"
    exit 1
fi

exit 0
#!/bin/bash
# /etc/keepalived/check_backends.sh

# Проверяем доступность хотя бы одного backend напрямую
BACKENDS=("10.0.1.1:8080" "10.0.1.2:8080")
ALIVE=0

for backend in "${BACKENDS[@]}"; do
    if curl -s --max-time 2 "http://${backend}/health" | grep -q '"status":"ok"'; then
        ALIVE=$((ALIVE + 1))
    fi
done

if [ $ALIVE -eq 0 ]; then
    echo "All backends are down"
    exit 1
fi

exit 0

Важно сделать скрипты исполняемыми:

chmod +x /etc/keepalived/check_haproxy.sh
chmod +x /etc/keepalived/check_backends.sh

Notify-скрипты и Telegram-алерты

При смене состояния Keepalived вызывает notify-скрипт. Мы отправляем алерты в Telegram дежурной команды:

#!/bin/bash
# /etc/keepalived/notify.sh

STATE=$1
ROUTER_ID=$(hostname)
VIP="10.0.0.100"
DATE=$(date '+%Y-%m-%d %H:%M:%S')

BOT_TOKEN="6012345678:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
CHAT_ID="-100123456789"

case $STATE in
    MASTER)
        MSG="🟢 KEEPALIVED: ${ROUTER_ID} стал MASTER\nVIP ${VIP} теперь на этом сервере\n${DATE}"
        ;;
    BACKUP)
        MSG="🟡 KEEPALIVED: ${ROUTER_ID} перешёл в BACKUP\nVIP ${VIP} отдан другому серверу\n${DATE}"
        ;;
    FAULT)
        MSG="🔴 KEEPALIVED: ${ROUTER_ID} в состоянии FAULT!\nHealth check не прошёл\n${DATE}"
        ;;
    STOP)
        MSG="⚫ KEEPALIVED: ${ROUTER_ID} остановлен!\nТребуется ручное вмешательство\n${DATE}"
        ;;
esac

# Telegram
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
    -d chat_id="${CHAT_ID}" \
    -d text="${MSG}" \
    -d parse_mode="" \
    > /dev/null 2>&1

# Лог
echo "${DATE} — ${ROUTER_ID} transitioned to ${STATE}" >> /var/log/keepalived-notify.log

Скрипт вызывается мгновенно при смене состояния. На практике дежурный инженер получал Telegram-уведомление через 2-3 секунды после failover.

Split-brain prevention и dual-stack

Split-brain — самая опасная ситуация: оба сервера считают себя MASTER и оба держат VIP. Это приводит к ARP-конфликтам и потере пакетов.

Причины split-brain:

  • Потеря связи между MASTER и BACKUP (сетевой сбой, firewall).
  • Высокая нагрузка CPU — VRRP advertisement не успевает отправиться вовремя.

Наши меры предотвращения:

# 1. Unicast вместо multicast — надёжнее в виртуализированных средах
unicast_src_ip 10.0.0.1
unicast_peer {
    10.0.0.2
}

# 2. Отдельный интерфейс для VRRP heartbeat (если доступен)
# Два пути: основная сеть + management сеть
vrrp_instance VI_PAYMENT {
    interface eth0                # VIP на основном интерфейсе
}

# 3. Скрипт fence — если мы MASTER, но не можем пинговать шлюз,
#    значит мы изолированы — отдаём VIP
vrrp_script chk_gateway {
    script "/bin/ping -c 1 -W 1 10.0.0.254"
    interval 2
    weight -40    # Потеря шлюза = полная деградация приоритета
    fall 3
    rise 2
}

Логика: если сервер не может пинговать шлюз, он скорее всего изолирован от сети. Его приоритет падает на 40, что гарантирует переход VIP на другой сервер (если тот доступен).

Dual-stack (IPv4 + IPv6):

vrrp_instance VI_PAYMENT_V6 {
    state MASTER
    interface eth0
    virtual_router_id 61          # Отличается от IPv4!
    priority 110
    advert_int 1

    virtual_ipaddress {
        2001:db8::100/64 dev eth0
    }

    track_script {
        chk_haproxy
    }
}

# HAProxy слушает на обоих стеках
# bind [::]:443 ssl crt /etc/haproxy/certs/cert.pem v4v6

Тестирование failover и мониторинг

Перед выходом в production мы провели серию тестов failover:

# Тест 1: Остановка HAProxy на MASTER
systemctl stop haproxy
# Ожидание: VIP переходит на BACKUP за ~4 секунды
# Проверка:
ip addr show eth0 | grep 10.0.0.100   # На MASTER — пусто
# На BACKUP:
ip addr show eth0 | grep 10.0.0.100   # Должен появиться VIP

# Тест 2: Остановка Keepalived
systemctl stop keepalived
# VIP переходит мгновенно (VRRP advertisements прекращаются)

# Тест 3: Симуляция сетевого сбоя
iptables -A OUTPUT -d 10.0.0.2 -j DROP   # Блокируем VRRP
# BACKUP не получает advertisements → становится MASTER
# Split-brain! Проверяем, что chk_gateway это ловит

# Тест 4: Нагрузочный тест с failover
# Запускаем wrk на клиенте
wrk -t4 -c100 -d60s https://10.0.0.100/api/health
# Во время теста останавливаем HAProxy на MASTER
# Результат: 3-4 секунды ошибок, затем полное восстановление

Мониторинг через Prometheus:

# Keepalived exporter
# Устанавливаем keepalived-exporter (Go binary)
wget https://github.com/mehdy/keepalived-exporter/releases/download/v1.3.0/keepalived-exporter-linux-amd64
chmod +x keepalived-exporter-linux-amd64
mv keepalived-exporter-linux-amd64 /usr/local/bin/keepalived-exporter

# Systemd unit
cat > /etc/systemd/system/keepalived-exporter.service << 'EOF'
[Unit]
Description=Keepalived Exporter
After=keepalived.service

[Service]
Type=simple
ExecStart=/usr/local/bin/keepalived-exporter
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now keepalived-exporter

# Prometheus scrape config
# - job_name: keepalived
#   static_configs:
#     - targets: ['haproxy1:9165', 'haproxy2:9165']

Ключевые метрики для Grafana:

  • keepalived_vrrp_state — текущее состояние (0=INIT, 1=BACKUP, 2=MASTER, 3=FAULT).
  • keepalived_vrrp_advert_rcvd — получено VRRP advertisements (пропуск = проблема связи).
  • keepalived_vrrp_become_master_total — счётчик переключений в MASTER (всплеск = нестабильность).
  • keepalived_script_status — статус health check скриптов.

Результаты и выводы

За 6 месяцев эксплуатации система показала следующие результаты:

МетрикаЗначение
Запланированных failover (обслуживание)12
Незапланированных failover (сбои)3
Среднее время переключения3.8 секунды
Потерянных транзакций0
Суммарный даунтайм11.4 секунды (за 6 месяцев)
Availability99.9999%

Рекомендации:

  • Используйте nopreempt — автоматический возврат VIP на восстановленный MASTER может вызвать повторный failover при нестабильном сервисе.
  • Unicast вместо multicast — в облачных средах (AWS, Yandex Cloud) multicast часто не работает.
  • Скрипт проверки шлюза — лучшая защита от split-brain в средах без выделенного heartbeat-канала.
  • Тестируйте failover регулярно — хотя бы раз в месяц. В production failover, о котором вы узнали впервые — это не failover, а лотерея.
  • Notify-скрипты с Telegram — дежурный должен знать о переключении мгновенно, даже если всё прошло штатно.

Часто задаваемые вопросы

VRRP (Virtual Router Redundancy Protocol) — открытый стандарт (RFC 5798), работает на любом оборудовании. HSRP (Hot Standby Router Protocol) — проприетарный протокол Cisco. Функционально они практически идентичны: оба обеспечивают виртуальный IP с автоматическим failover. Keepalived реализует VRRP, поэтому совместим с любым VRRP-оборудованием в сети.
Можно, но редко нужно. В Kubernetes есть свои механизмы HA: kube-vip для control plane VIP, MetalLB для LoadBalancer-сервисов. Keepalived уместен для HA на уровне самих нод Kubernetes — например, для виртуального IP на master-нодах. Для этого существует проект kube-vip, который использует VRRP или Raft-протокол.
VRRP поддерживает неограниченное количество BACKUP-нод. Назначьте приоритеты: MASTER — 110, BACKUP1 — 100, BACKUP2 — 90. При падении MASTER VIP перейдёт на BACKUP1 (приоритет 100). Если и он упадёт — на BACKUP2 (приоритет 90). virtual_router_id должен совпадать на всех нодах. На практике две ноды покрывают 99% сценариев.
По умолчанию VRRP preemptive: когда MASTER восстанавливается, он забирает VIP обратно у BACKUP. nopreempt отключает это поведение — после failover VIP остаётся на BACKUP до ручного вмешательства. Используйте nopreempt когда: 1) переключение VIP вызывает кратковременные ошибки, 2) причина сбоя MASTER может быть нестабильной (flapping), 3) вы хотите контролировать возврат вручную после проверки.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#keepalived#vrrp#high availability#virtual ip#failover#keepalived.conf#health check#split-brain
Комментарии 0

Оставить комментарий

загрузка...