image
Доброго времени суток. На хабре много писали (поиск) про то, как поднять MySQL кластер на основе решения Percona XtraDB Cluster. Но вот на днях ко мне подошёл программер и попросил сделать так, чтобы в MySQL можно было назначать хосты пользователям для разграничения доступа. Тут я вспомнил, что ip-то там отображаются далеко не клиентские, тут всё и началось :). В интернете было найдено решение аж 2009 года, которое заключалось в использовании tproxy патча, iproute2 и iptables, но это решило проблему частично, и только в пределах той машины где стоит haproxy, но что если мы хотим ещё и читать из разных мест? Вот что было сделано:


Я использую CentOS7.1.1503 (Core) с ядром 3.10.0-229.4.2.el7.x86_64.

1. Скачиваем исходники haproxy 1.5 haproxy-1.5.12-src.
2. Правим spec файл, который заботливо был подготовлен разработчиками, добавляем флаг USE_LINUX_TPROXY=1.
3. Собираем rpm пакет.
4. Устанавливаем на сервера, в моём случае это 3 сервера.
5. Убедиться, что haproxy собран с поддержкой tproxy, можно, набрав haproxy -vv.

Далее стандартная схема: на трёх нодах стоят keepalived для VIP (виртуальный ip, к которому будут подключаться клиенты), haproxy, MySQL.

Идея в слудующем: клиент использует для подключения один ip адрес (192.168.99.99) и 2 порта, порт 3306 для чтения и записи, порт 3307 только для чтения.

Работает это всё следующим образом: клиент соединяется c VIP 192.168.99.99:3306 и работает и на чтение, и на запись только на хосте с VIP. Если же он соединится с 192.168.99.99:3307, тогда он пойдёт на чтение на 2 другие ноды, отличные от той, где VIP.

Изначально проблема была именно с пробросом ip клиента при чтении. Решением оказалось использование протокола «proxy protocol» (строки send-proxy и accept-proxy в конфиге haproxy), написанного одним из разработчиков haproxy.
Я и подумал, а почему бы для проброса данных о src ip не использовать именно это решение. Жаль, что такие вещи как exim, postfix, nginx поддерживают протокол proxy, а PerconaCluster нет.

На каждом сервере делаем следующее:
iptables -t mangle -N DIVERT
iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
iptables -t mangle -A DIVERT -j MARK --set-mark 111
iptables -t mangle -A DIVERT -j ACCEPT
ip rule add fwmark 111 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100


net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.p4p2.rp_filter = 0


Конфиг haproxy node1:
global
  log 127.0.0.1 local0 notice
  maxconn 4096
  chroot /var/lib/haproxy
  pidfile /var/run/haproxy.pid
  #user haproxy
  #group haproxy
  daemon

defaults
  log global
  mode http
  option dontlognull
  retries 3
  option redispatch
  maxconn 3000
  retries                 3
  timeout http-request    10s
  timeout queue           1m
  timeout connect         10s
  timeout client          1m
  timeout server          1m
  timeout http-keep-alive 10s
  timeout check           10s

frontend status
  bind 192.168.99.99:80
  mode http
  default_backend mysql-status

backend mysql-status
  mode http
  balance roundrobin
  stats hide-version
  stats scope mysql-backend-rw-3306
  stats scope mysql-backend-ro-3307
  stats scope mysql-backend-ro-end-3307
  stats refresh 5s
  stats show-node
  stats uri /haproxy/stats
  stats auth pwd:pwd

frontend mysql-rw
  bind 192.168.99.99:3306
  mode tcp
  default_backend mysql-backend-rw-3306

frontend mysql-ro
  bind 192.168.99.99:3307
  mode tcp
  default_backend mysql-backend-ro-3307

frontend mysql-ro-end
  bind 192.168.99.28:3307 accept-proxy
  mode tcp
  default_backend mysql-backend-ro-end-3307

backend mysql-backend-rw-3306
  mode tcp
  source 0.0.0.0 usesrc clientip
  balance leastconn
  option httpchk
  server node1 192.168.99.28:3306 check port 9200 inter 12000 rise 3 fall 3

backend mysql-backend-ro-3307
  mode tcp
  balance leastconn
  option httpchk
  server node2 192.168.99.29:3307 send-proxy check port 9200 inter 12000 rise 3 fall 3
  server node3 192.168.99.30:3307 send-proxy check port 9200 inter 12000 rise 3 fall 3
  server node1 192.168.99.28:3306 source 0.0.0.0 usesrc clientip check port 9200 inter 12000 rise 3 fall 3 backup

backend mysql-backend-ro-end-3307
  mode tcp
  source 0.0.0.0 usesrc clientip
  balance leastconn
  option httpchk
  server node1 192.168.99.28:3306 check port 9200 inter 12000 rise 3 fall 3


Конфиг haproxy node2 (только отличия от node1):
frontend mysql-ro-end
  bind 192.168.99.29:3307 accept-proxy
  mode tcp
  default_backend mysql-backend-ro-end-3307

backend mysql-backend-rw-3306
  mode tcp
  source 0.0.0.0 usesrc clientip
  balance leastconn
  option httpchk
  server node2 192.168.99.29:3306 check port 9200 inter 12000 rise 3 fall 3

backend mysql-backend-ro-3307
  mode tcp
  balance leastconn
  option httpchk
  server node1 192.168.99.28:3307 send-proxy check port 9200 inter 12000 rise 3 fall 3
  server node3 192.168.99.30:3307 send-proxy check port 9200 inter 12000 rise 3 fall 3
  server node2 192.168.99.29:3306 source 0.0.0.0 usesrc clientip check port 9200 inter 12000 rise 3 fall 3 backup

backend mysql-backend-ro-end-3307
  mode tcp
  source 0.0.0.0 usesrc clientip
  balance leastconn
  option httpchk
  server node2 192.168.99.29:3306 check port 9200 inter 12000 rise 3 fall 3


Конфиг haproxy node3 (только отличия от node1):
frontend mysql-ro-end
  bind 192.168.99.30:3307 accept-proxy
  mode tcp
  default_backend mysql-backend-ro-end-3307

backend mysql-backend-rw-3306
  mode tcp
  source 0.0.0.0 usesrc clientip
  balance leastconn
  option httpchk
  server node3 192.168.99.30:3306 check port 9200 inter 12000 rise 3 fall 3

backend mysql-backend-ro-3307
  mode tcp
  balance leastconn
  option httpchk
  server node1 192.168.99.28:3307 send-proxy port 9200 inter 12000 rise 3 fall 3
  server node2 192.168.99.29:3307 send-proxy port 9200 inter 12000 rise 3 fall 3
  server node3 192.168.99.30:3306 source 0.0.0.0 usesrc clientip check port 9200 inter 12000 rise 3 fall 3 backup

backend mysql-backend-ro-end-3307
  mode tcp
  source 0.0.0.0 usesrc clientip
  balance leastconn
  option httpchk
  server node3 192.168.99.30:3306 check port 9200 inter 12000 rise 3 fall 3


P.S Пробовал сделать DirectRouting, но странным образом не менялся src ip.

Комментарии (2)


  1. RaveNoX
    25.06.2015 01:27

    Может подскажете, как вы решаете проблему автоматического запуска ( без ручного вмешательства ) при полной остановке кластера ( то есть всех нод примерно в одно время ) например из-за сбоя питания?
    Я у себя пробовал использовать percona, но данная проблема ( необходимость принудительного бутстрапа одной из нод для старта кластера ) остановила внедрение.


    1. uran238 Автор
      25.06.2015 08:36

      У меня в виртуалке в другом месте крутится демон garbd, он же арбитр. Он должен быть внесён в .my.cnf на всех нодах как gcomm://<его ip> т.е. как полноценный член кластера.