Load balancing i HA połączeń mysql

Mysql umożliwia wykonanie replikacji i klastrowania z wykorzystaniem kilku różnych technologii. Samo jednak zestawienie klastra to jedno, a zapewnienie usługom korzystającym z bazy danych dostępu do klastra w trybie high avability to drugie. W tym wpisie pokażę jak stworzyć rozwiązanie pozwalające na uzyskanie konfiguracji HA dla mysql.

Cały projekt oparłem o HAProxy, które umożliwia loadbalancing połączeń TCP oraz natywnie wspiera komunikację z mysql. Należy jednak pamiętać, że ustawienie loadbalancera dla potrzeb połączeń mysql, choć wydaje się proste zawiera kilka pułapek na które należy zwrócić uwagę. Ponadto przygotowanie konfiguracji dla klastra wymaga zastosowania kolejnych dedykowanych rozwiązań.

Standardowy początek

Przedstawiona tutaj konfiguracja HAProxy obejmuje ustawienia dla klastra Mysql/Galera (wresp) opierające się na założeniu, że wszystkie aplikacje będą łączyły się do klastra poprzez ten sam FQDN, w naszym przypadku galera.server-local.com. 

Ponadto zakładamy, że klaster składa się z nodów znajdujących się w różnych lokalizacjach (o różnych adresacjach)  w związku z tym konfiguracja powinna wybierać nody z bliższej lokalizacji w pierwszej kolejności aby zminimalizować czas dostępu dla aplikacji.

Rozpoczniemy więc od definicji frontend dla naszego loadbalancingu:

frontend galeranodesfront
    bind 172.10.10.1:3306
    timeout client 20s
    mode tcp
    acl is_gal01 src 10.8.1.0/24
    acl is_gal02 src 10.8.2.0/24
    use_backend galeranodes_def_01 if is_gal01
    use_backend galeranodes_def_02 if is_gal02
    default_backend galeranodes_def_01

W powyższej definicji inicjujemy frontend: galeranodesfront. Dyrektywa mode tcp określa z jakim type połączeń będziemy mieli do czynienia. Timeout client ustawić należy na taką samą wartość, jak timeout ustawiony w konfiguracjach mysql, aby uniknąć ucinania połączeń przez loadbalancer. 

Po zdefiniowaniu tych podstawowych parametrów należy zająć się spełnieniem wymogu automatycznego wybierania najbliższych nodów. W tym celu wykorzystałem dostarczany przez HAproxy system acl (Access Control Lists). Najpierw zdefiniowałem więc dwa acl (is_gal01 oraz is_gal02) nadawane względem źródłowego adresu pakietu, a następnie przy ich pomocy określamy które z zapleczy ma zostać wybrane dla którego z acl:

    use_backend galeranodes_def_01 if is_gal01
    use_backend galeranodes_def_02 if is_gal02

W samych zaś zapleczach (co zobaczymy później) odpowiednio zdefiniujemy ich członków tak, by aplikacje odwoływały się do nodów klastra w obrębie swojej adresacji. Warto także zdefiniować domyślne zaplecze dla zapytań, których adres źródłowy jest inny niż wskazane w acl:

    default_backend galeranodes_def_01

co prawda w idealnym świecie taka definicja nie byłaby potrzebna, jednak ograniczy ona kłopoty z aplikacjami odpytującymi nasz klaster np. przez inny interfejs sieciowy z przypisaną inną adresacją.

Po zdefiniowaniu frontendu, który będzie odpowiedzialny za odbieranie połączeń i przekazywanie ich do zapleczy, należy zdefiniować same backendy. Definicję tę pokażę dla galeranodes_def_01, gdyż definicja drugiego będzie różnić się wyłącznie adresacją memberów.

backend galeranodes_def_01
    mode tcp
    timeout server 20s
    timeout connect 10s
    balance leastconn
    option tcp-check
    option mysql-check user checker
    server gal01 node1a.galera.server-local.com:3306 check maxconn 60 inter 3s fall 3 rise 3
    server gal02 node2a.galera.server-local.com:3306 check maxconn 60 inter 3s fall 3 rise 3
    server gal03 node3a.galera.server-local.com:3306 check maxconn 60 inter 3s fall 3 rise 3
    server gal04 node4a.galera.server-local.com:3306 check maxconn 60 inter 3s fall 3 rise 3

Podobnie jak w przypadku frontend definiujemy typ zaplecza jako tcp. Czas odpowiedzi serwera timeout server najlepiej ustawić na wartość odpowiadającą timeout w konfiguracji mysql, natomiast timeout connect w zależności od czasów połączenia występujących w obrębie infrastruktury.

Należy pamiętać, że skuteczność całej konfiguracji jest bardzo wrażliwa na wartości przypisane różnym timeout. Niezależnie od checków (które omówimy za chwilę) to właśnie timeout jest podstawowym sposobem sprawdzania czy serwery backend są dostępne. Wartości timeout pomnożone przez wpisaną w konfiguracji ilość nieudanych prób dadzą nam maksymalny czas po którym, w najgorszym przypadku, serwer zaplecza uznany zostanie za działający niepoprawnie. W przypadku dużych timeout czas ten może być dość długi co spowoduje, że spora ilość połączeń trafi do niedziałającego node.

Sposobem loadbalancingu dla długich i bardzo długich połączeń np. LDAP czy SQL, zalecanym przez twórców HAProxy, jest leastconn. Zakłada on, że połączenie trafi do serwera zaplecza z aktualnie najniższą liczbą połączeń a w przypadku równej liczny połączeń na serwerach przychodzący reqest zostanie przydzielony zgodnie z regułą roundrobin.

Pozostaje jeszcze kwestia ustawienia sposobu w jaki sprawdzane będą zaplecza. Ja wykorzystuję dwa checki:

    option tcp-check
    option mysql-check user checker

tcp-check w swojej podstawowej wersji sprawdza możliwość nawiązania połączenia TCP do wskazanego serwera po porcie do którego łączy się dany serwer zaplecza. Drugi z checków – mysql-check odpowiada za sprawdzenie możliwości zalogowania i poprawnego zamknięcia połączenia do mysql.

Należy pamiętać o pewnym istotnym ograniczeniu HAProxy. mysql-check wymaga użycia usera bez zdefiniowanego hasła! Jest to pole dla wystąpienia incydentów bezpieczeństwa! Aby zmniejszyć wpływ tego rozwiązania na środowisko można ograniczyć prawa logowania tego użytkownika do hosta loadbalancera oraz nadać mu minimalne uprawnienia np. USAGE zakładające dostęp read only:

GRANT USAGE ON *.* TO ‚checker’@’locahost’;

Tak przygotowana konfiguracja zapewni nam prawidłowe zachowanie loadbalancera w przypadku niedostępności któregoś z node mysql rozumianej przez brak otwartego socketa usługi. Niestety zakończenie konfiguracji w tym miejscu nie pozwoli nam poprawnie poradzić sobie z wykryciem stanu samego node, który może przyjmować połączenia będąc w stanie niepozwalającym na wykonywanie innych operacji, co może skutkować błędami aplikacji i niewłaściwym działaniem HA.

Współpraca z klastrem

Rozwiązaniem tego problemu byłoby przygotowanie dedykowanych checków dla sprawdzania stanu każdego nodemembera zaplecza. Z pomocą w tym przychodzi nam zaimplementowany w HAProxy agent-check.

Jego użycie zakłada wykorzystanie skryptu zwracającego odpowiedź w ustalonym formacie po odpytaniu TCP przez zdefiniowany port. Szczegółowe wytyczne odnośnie formatu odpowiedzi znajdują się w dokumentacji agent-check, ale standardowo zawiera ona przede wszystkim stan backendu po sprawdzeniu np. UP, DOWN. Dodatkowo zawierać może wyrażony w procentach stan dostępności zaplecza, który może być użyty jako waga dla loadbalancingu czy też komentarz dla stanu DOWN.

Przykładowa odpowiedź z agenta może wyglądać następująco:

user@localhost:~$ exec 3<> /dev/tcp/node1a.galera.server-local.com/9988
user@localhost:~$ cat <&3
100% UP

Należy więc przygotować skrypt, który będzie sprawdzał stan nodów klastra na podstawie informacji dostarczanych przez wresp i zwracał odpowiedni komunikat do HAProxy. Samemu skryptowi poświęcę osobny artykuł w najbliższym czasie. Na potrzeby tego tekstu załóżmy jednak, że możemy odpowiednio sprawdzić czy nasz node jest w stanie READY, jest zsynchronizowany z klastrem i może przyjmować kwerendy SQL.

Następnym krokiem jest zmodyfikowanie konfiguracji serwerów zaplecza tak, aby korzystały z agent-check. Dodałem więc do nich agent-check oraz agent-port po którym HAProxy połączy się z agentem – oba parametry są wymagane przy użyciu agen-check.

server gal01 node1a.galera.server-local.com:3306 check agent-check agent-port 9988 maxconn 60 inter 3s fall 3 rise 3

Przy stosowaniu tego rozwiązania bardzo istotne jest zapewnienie stabilnego działania agenta. HAProxy nie wyłączy zaplecza w przypadku braku połączenia z agentem (za sprawdzanie komunikacji z zapleczem odpowiadają zdefiniowane w options checki i dyrektywa check  w definicji serwera), jednak tylko agent jest w stanie zmienić stan zaplecza, który loadbalancer oznaczył na podstawie komunikatu z agenta. Jeżeli więc agent oznaczy serwer zaplecza jako DOWN a następnie stracimy z nim komunikację, HAProxy będzie utrzymywało ten stan dla wspomnianego serwera.

Podsumowanie

Przygotowanie loadbalancingu dla klastra mysql nie jest sprawą prostą. Uzyskanie stabilnego i skutecznego rozwiązania wymaga zastosowania kilku sztuczek i zegarmistrzowskiego szlifowania pojedynczy parametrów.

Poza podstawowymi ustawieniami wspomnianymi w tym artykule HAProxy dostarcza wiele parametrów pozwalających na optymalizację całego rozwiązania takich jak np. możliwość ustalenia interwałów pomiędzy sprawdzaniem checków agent-check czy też osobnego zdefiniowania po ilu nieudanych próbach dla jednych i drugich serwer zaplecza zostanie oznaczony jako niedziałający.

Poświęcając trochę czasu na doszlifowanie konfiguracji uzyskujemy wygodne i niezawodne rozwiązanie o wysokim stopniu uniwersalności, które ułatwi nie tylko życie developerom 😉 ale także szybkie i bezbolesne skalowanie infrastruktury o kolejne node.

Zagadnienie wysokiej dostępności przy wykorzystaniu programowych load-balancerów wymaga omówienia jeszcze dwóch kwestii – zapewnienia ha samym load-balancerom oraz dostosowania ustawień systemu operacyjnego dla poprawy wydajności load-balancingu. Zagadnienia te omówię w kolejnych artykułach.

Mikołaj Niedbała

I'm a Poland based IT administrator, linux administrator and IT engineer creating professional IT infrastructure solutions based on Linux and virtual environments.

One thought to “Load balancing i HA połączeń mysql”

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *