Высокодоступные, но потерянные транзакции. Keepalived неожиданно закрывает соединение при переключении между мастером и слейвом.

Вопрос или проблема

Описание

Кластер из двух виртуальных машин был развернут для HA на основе множества крутых руководств от профессионалов.
например: https://grimoire.carcano.ch/blog/high-available-ha-proxy-tutorial-with-keepalived/

Стек технологий

HAproxy + Keepalived

Более детально

Хосты:
haproxy1.mycompany.com (10.10.199.101 Master)
haproxy2.mycompany.com (10.10.199.102 Slave)
Плавающий IP-адрес:
myservice.mycompany.com (10.10.199.150)

Ожидаемое поведение

Использование VIP через Keepalived не приведет к потерям.

Тестирование HA через сервисы

  • R/W операции S3 объектного хранилища (minio)
  • Запросы в Postgresql

Конфигурации

Keepalived MASTER

global_defs {
  router_id haproxya1
  enable_script_security
  script_user root
}

vrrp_script haproxy_check {
  script "/usr/libexec/keepalived/haproxy_check.sh"
  interval 2
  weight 100
}

vrrp_instance VI_1 {
  interface enp6s18
  state MASTER
  priority 100
  virtual_router_id 101
  advert_int 1
  unicast_src_ip 10.10.199.101
  unicast_peer {
    10.10.199.102
  }
  virtual_ipaddress {
    10.10.199.150
  }
  track_script {
    haproxy_check
  }
}

Keepalived BACKUP

global_defs {
  router_id haproxya2
  enable_script_security
  script_user root
}

vrrp_script haproxy_check {
  script "/usr/libexec/keepalived/haproxy_check.sh"
  interval 2
  weight 100
}

vrrp_instance VI_1 {
  interface enp6s18
  state BACKUP
  priority 50
  virtual_router_id 101
  advert_int 1
  unicast_src_ip 10.10.199.102
  unicast_peer {
    10.10.199.101
  }
  virtual_ipaddress {
    10.10.199.150
  }
  track_script {
    haproxy_check
  }
}

Общая конфигурация HAproxy

global
    maxconn 100000
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    user haproxy
    group haproxy
    daemon

defaults
    mode tcp
    log global
    retries 10
    option redispatch
    timeout client 30m
    timeout connect 30s
    timeout server 30m
    timeout check 5s
    option http-keep-alive
    no option http-server-close
    http-reuse always

listen hapserver
    mode http
    bind 10.10.199.150:7000
    stats enable
    stats uri /

# PostgreSQL
listen postgres
    bind 10.10.199.150:5000
    server postgres 10.10.199.123:5430 check

# S3 MinIO
listen s3minioWEB
    bind myservice.mycompany.com:9001 ssl crt /etc/haproxy/ssl/myservice.mycompany.com.pem
    mode http
    balance leastconn
    server minio1 minio1.mycompany.com:9001 check inter 2s
    server minio2 minio2.mycompany.com:9001 check inter 2s
    server minio3 minio3.mycompany.com:9001 check inter 2s
    server minio4 minio4.mycompany.com:9001 check inter 2s

listen s3minioAPI
    bind myservice.mycompany.com:9000 ssl crt /etc/haproxy/ssl/myservice.mycompany.com.pem
    mode http
    balance leastconn
    server minio1 minio1.mycompany.com:9000 check inter 2s
    server minio2 minio2.mycompany.com:9000 check inter 2s
    server minio3 minio3.mycompany.com:9000 check inter 2s
    server minio4 minio4.mycompany.com:9000 check inter 2s

Во время тестирования выполнялась длительная задача одного из сервисов (Postgresql, S3) (с другого компьютера в той же локальной корпоративной сети). Пока задача выполнялась, MASTER (haproxy1) был остановлен. Как и ожидалось, VIP был поднят на другом хосте.

Однако при тестировании S3 minio всегда соединение разрывалось.
Но при тестировании Postgresql ошибка появлялась через раз.
Тестирование запросов проводилось с помощью скрипта:

#!/bin/bash

export DB_HOST="10.10.199.14"
export DB_PORT="5000"
export DB_NAME="db"
export DB_USER="user"
export PGPASSWORD='password'

# ------------------------------------

start_time=$(date +%s)
end_time=$(( start_time + 50 ))

iterator=1

echo "Start"
while [[ $(date +%s) -le $end_time ]]; do
  echo "cur: $iterator"
  psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "INSERT INTO haptest (num, timest) VALUES ("$iterator", CURRENT_TIMESTAMP)"
  iterator=$((iterator+1))
done

iterator=$((iterator-1))
echo "Made records: $iterator"
echo "End"

Ребята, можете помочь, возможно, я где-то ошибся или неправильно провел тестирование?

Ответ или решение

Теория

Ваша ситуация с потерей транзакций при переключении между главным и резервным узлом в кластере HAproxy + Keepalived является довольно распространённой проблемой в системах высокой доступности. Основной целью использования Keepalived в таких сетапах является минимизация времени простоя и обеспечивание непрерывности сервиса путем автоматического переключения на резервный узел в случае сбоя. Однако наиболее уязвимым временем является переходной период, когда происходит переключение от мастера к резервному узлу.

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

Пример

Рассмотрим вашу конфигурацию: у вас есть две виртуальные машины, на одной из которых HAproxy установлен в режиме мастера, а на другой – в режиме резервного. Переключение выполняется с помощью Keepalived, и виртуальный IP адрес используется для маршрутизации трафика.

Вы используете следующую конфигурацию:

  • Постоянное подключение к PostgreSQL на порту 5000.
  • Подключение к серверам MinIO через HTTP с использованием SSL на портах 9000 и 9001.
  • Используется стратегия балансировки нагрузки leastconn для MinIO.

Применение

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

  2. Настройте разрывы соединений и переключения:

    • HAproxy: Убедитесь, что параметры timeout client, timeout server и timeout connect правильно настроены для минимизации вероятности разрыва соединений.
    • Keepalived: Возможно, вам стоит протестировать разные значения priority и advert_int, чтобы достичь более гладкой передачи ролей между узлами.
  3. Проверьте скрипты проверки. Скрипт состояния HAproxy может оказаться критичным. Убедитесь, что ваш скрипт /usr/libexec/keepalived/haproxy_check.sh чётко и быстро определяет состояние HAproxy. Быстрое обнаружение проблем может помочь в более оперативной передаче ролей.

  4. Используйте устойчивые алгоритмы переключения и инструментальные средства:

    • При работах с PostgreSQL, рассмотрите возможность использования инструментов, которые поддерживают автоматическое переподключение и повтор транзакций, таких как PgBouncer или Patroni для лучшего управления состоянием кластеров.
    • Для S3 MinIO, проверьте наличие поддерживаемых конфигураций, которые могут удерживать (persist) состояния транзакций на каких-либо промежуточных уровнях, чтобы предотвратить их потерю.
  5. Логи и мониторинг. Настройте логирование и мониторинг, чтобы получать детализированную информацию о причинах переключений и потерях соединений. Это поможет вам не только в диагностике проблемы, но и в том, чтобы научиться предсказывать и предотвращать её в будущем.

  6. Постепенное переключение нагрузки. Для более мягкого переключения, рассмотрите возможность постепенного переключения трафика от мастера к резерву, что позволит завершить текущие транзакции прежде, чем узел потеряет статус мастера.

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

Используйте данную стратегию совместно с подробным тестированием, чтобы минимизировать потенциальные точки отказа и гарантировать сохранность данных в вашей HA конфигурации.

Оцените материал
Добавить комментарий

Капча загружается...