Структурная схема кластера
На серверах lb01 и lb02 устанавливается Haproxy для балансировки нагрузки (распределения запросов клиентов на пару Web-серверов(серверов приложений app01 и app02)), а также для отказоустойчивости. При выходе со строя одного из lb-ов второй из них автоматически становится активным сервером(цепляет на себя VIP(Virtual IP) на который прописаны сайты в ДНС)
Это достигается за счет настроенного KeepAlived на обоих loadbalancer-серверах. Именно KeepAlived постоянно мониторит доступность активного loadbalancer -сервера и в случае его недоступности по какой-либо причине VIP-адрес перемещается с проблемного loadbalancer -сервера на запасной.
Запасной(вторичный) loadbalancer -сервер становится активным до тех пор пока не поднимется основной(loadbalancer, который имеет больший вес, согласно настройкам KeepAlived – это lb01)
HAProxy в свою очередь также мониторит Web-сервера на доступность и в случае недоступности какого-то Web-сервера он будет автоматически выброшен с кластера
На структурной схеме обозначено:
-черным цветом штатный режим работы(lb01-активный,на него поступают все запросы клиентов, он мониторит Web-сервера и распределяет по ним запросы клиентов)
-в случае проблем с lb01 ,автоматически становится главным lb02 и он уже выполняет все функции.Этот режим работы обозначен зеленым цветом
-красным цветом обозначена master-master(между app01 и app02) и master-slave(между активным на текущий момент master-ом и slave-ом) репликации
(В целях экономии виртуальных машин я поднял на app-серверах также mysql-mmm-кластер)
Более подробно о mysql-mmm изложено здесь
https://kamaok.org.ua/?p=282
Итого имеем
VIP-адреc расшаренный/плавающий между двумя лоадбалансерами 192.168.1.127
Только активный в данный момент времени имеет на себе этот адрес( ip addr show покажет его)
1 2 3 4 |
lb01 - 192.168.1.38 lb02 - 192.168.1.39 app01- 192.168.1.110 app02 - 192.168.1.111 |
Установка и настройка Keepalived
На обоих lb устанавливаем Keepalived и вносим необходимое значение в переменную ядра
1 |
net.ipv4.ip_nonlocal_bind |
1 |
# yum install keepalived |
Для того, чтобы разрешить HAproxy прослушивать VIP-адрес(адрес, плавающий/расшаренный между двумя лоадбалансерами 192.168.1.127 )
1 |
# nano /etc/sysctl.conf |
1 2 |
net.ipv4.ip_forward = 1 net.ipv4.ip_nonlocal_bind = 1 |
1 |
# sysctl –p |
Активный lb01(192.168.1.38)
1 |
# nano /etc/keepalived/keepalived.conf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
vrrp_script chk_haproxy { script "killall -0 haproxy" # verify the pid existance interval 2 # check every 2 seconds weight 2 # add 2 points of prio if OK } vrrp_instance VI_1 { interface eth0 # interface to monitor state MASTER virtual_router_id 51 # Assign one ID for this route priority 100 # 100 on master, 50 on backup virtual_ipaddress { 192.168.1.127 # the virtual IP } track_script { chk_haproxy } } |
Пассивный lb02(192.168.1.39)
1 |
# nano /etc/keepalived/keepalived.conf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
vrrp_script chk_haproxy { script "killall -0 haproxy" # verify the pid existance interval 2 # check every 2 seconds weight 2 # add 2 points of prio if OK } vrrp_instance VI_1 { interface eth0 # interface to monitor state Backup virtual_router_id 51 # Assign one ID for this route priority 50 # 100 on master, 50 on backup virtual_ipaddress { 192.168.1.127 # the virtual IP } track_script { chk_haproxy } } |
Запускаем keepalived на lb01
1 |
# /etc/init.d/keepalived start |
Проверяем наличие VIP адреса на интерфейсе eth0
(смотрим через ip addr show(через ifconfig его не видно))
1 |
# ip addr show | grep 192.168.1.127 |
1 |
inet 192.168.1.127/32 scope global eth0 |
Запускаем keepalived на lb02
1 |
# /etc/init.d/keepalived start |
Проверяем отсутствие VIP адреса на интерфейсе eth0
1 2 |
# ip addr show | grep 192.168.1.127 # |
Тестируем Keepalived
Например, останавливаем keepalived на активном лоадбалансере lb01
1 |
# /etc/init.d/keepalived stop |
Логи для keepalived смотрим в
1 |
# tail -f /var/log/messages |
1 2 3 4 |
Dec 21 00:03:28 centos641 Keepalived[14819]: Stopping Keepalived v1.2.7 (02/21,2013) Dec 21 00:03:28 centos641 Keepalived_vrrp[14821]: VRRP_Instance(VI_1) sending 0 priority Dec 21 00:03:28 centos641 Keepalived_vrrp[14821]: VRRP_Instance(VI_1) removing protocol VIPs. Dec 21 00:03:30 centos641 ntpd[1758]: Deleting interface #18 eth0, 192.168.1.127#123, interface stats: received=0, sent=0, dropped=0, active_time=1624 secs |
1 2 |
# ip addr show | grep 192.168.1.127 # |
Бывший пассивный лоадбалансер lb02 стал активным путем получения роли Master и VIP-адреса
1 2 3 4 5 6 |
Dec 21 00:03:29 centos642 Keepalived_vrrp[5323]: VRRP_Instance(VI_1) Transition to MASTER STATE Dec 21 00:03:30 centos642 Keepalived_vrrp[5323]: VRRP_Instance(VI_1) Entering MASTER STATE Dec 21 00:03:30 centos642 Keepalived_vrrp[5323]: VRRP_Instance(VI_1) setting protocol VIPs. Dec 21 00:03:30 centos642 Keepalived_vrrp[5323]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 192.168.1.127 Dec 21 00:03:30 centos642 Keepalived_healthcheckers[5322]: Netlink reflector reports IP 192.168.1.127 added Dec 21 00:03:35 centos642 Keepalived_vrrp[5323]: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 192.168.1.127 |
1 |
# ip addr show | grep 192.168.1.127 |
1 |
inet 192.168.1.127/32 scope global eth0 |
Добавление в автозагрузку на обоих севрерах
1 |
# chkconfig --level 2345 keepalived on |
1 |
# chkconfig --level 2345 haproxy on |
Установка и настройка Haproxy
1 |
# yum install haproxy |
1 |
# cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg~ |
1 |
# cat /dev/null > /etc/haproxy/haproxy.cfg |
1 |
# nano /etc/haproxy/haproxy.cfg |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
global log 127.0.0.1 local2 notice maxconn 4096 #debug #quiet user haproxy group haproxy defaults log global mode http option httplog option dontlognull stats enable stats uri /haproxy stats realm Stricty\Private stats auth admin:123456 retries 3 option redispatch maxconn 10000 contimeout 5000 clitimeout 50000 srvtimeout 50000 listen webcluster 192.168.1.127:80 mode http option httpclose option forwardfor option httpchk HEAD /test.php HTTP/1.0 server app01 192.168.1.110:80 check server app02 192.168.1.111:80 check |
Настройка rsyslog для записи логов в файл /var/log/haproxy.log
1 |
# nano /etc/rsyslog.conf |
1 2 3 4 5 6 |
# Provides UDP syslog reception $ModLoad imudp $UDPServerRun 514 $UDPServerAddress 127.0.0.1 #### RULES #### local2.* /var/log/haproxy.log |
1 |
# /etc/init.d/rsyslog restart |
1 |
# /etc/init.d/haproxy restart |
Вся статистика по кластеру HAproxy становится доступна по адресу
1 |
http://192.168.1.127/haproxy |
с учеткой,указанной в haproxy.cfg
В корне сайта по умолчанию создаем файл test.php
Nginx у меня это определено так
1 |
# less /etc/nginx/nginx.conf |
1 2 3 4 5 6 7 8 9 |
server { listen 80 default_server; server_name _; location / { root /var/www/html; ........... } } |
Содержание проверочного файла
1 |
# cat /var/www/html/test.php |
1 |
<?php echo "OK" ?> |
1 |
# tail -f /var/log/haproxy.log |
1 |
192.168.1.39 - - [20/Dec/2013:20:42:04 +0200] "HEAD /test.php HTTP/1.0" 200 0 "-" "-" "-" |
Для проверки балансировки запросов на оба Web-сервера на обоих серверах создаем либо разные странички для сайта по умолчанию, либо файл,например, с таким содержимым
1 |
# nano /var/www/html/lb.php |
1 2 3 4 5 6 |
<?php header('Content-Type: text/plain'); echo "Server IP: ".$_SERVER['SERVER_ADDR']; echo "\nClient IP: ".$_SERVER['REMOTE_ADDR']; echo "\nX-Forwarded-for: ".$_SERVER['HTTP_X_FORWARDED_FOR']; ?> |
Далее тестируем распределение запросов
1 |
# curl http://192.168.1.127/lb.php |
1 2 3 |
Server IP: 192.168.1.110 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 |
1 |
# curl http://192.168.1.127/lb.php |
1 2 3 |
Server IP: 192.168.1.111 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 |
1 |
# curl http://192.168.1.127/lb.php |
1 2 3 |
Server IP: 192.168.1.110 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 |
1 |
# curl http://192.168.1.127/lb.php |
1 2 3 |
Server IP: 192.168.1.111 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 |
Т.е запросы по очереди направляются но оба Web-сервера
Далее, отключим, например, Nginx на сервере Web1
1 |
# /etc/init.d/nginx stop |
1 |
Stopping nginx:. [ OK ] |
В логах haproxy наблюдаем
1 |
# tail -f /var/log/haproxy.log |
1 |
Dec 20 22:10:16 localhost haproxy[3489]: Server webcluster/app01 is DOWN, reason: Layer4 connection problem, info: "Connection refused", check duration: 0ms. 1 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue. |
Тестируем
1 |
# curl http://192.168.1.127/lb.php |
1 2 3 |
Server IP: 192.168.1.111 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 |
1 |
# curl http://192.168.1.127/lb.php |
1 2 3 |
Server IP: 192.168.1.111 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 |
1 |
# curl http://192.168.1.127/lb.php |
1 2 3 |
Server IP: 192.168.1.111 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 |
Т.е неисправный Web-сервер был автоамтически выброшен из кластера HAProxy и все запросы идут только к одному исправному Web-серверу
Приведенная выше настройка HAproxy не предусматривает поддержку sticky user’s session
Т.е нет возможности направить все запросы пользователя на один и тот же сервер приложений. Такая привязка не нужна, например, если PHP-сессии хранятся в едином хранилище(например в общей папке примонтированной по NFS на серверах приложений, или PHP-сессии хранятся на сервере/серверах memcache или в базе данных)
В случае,если нет единого общего типа хранения PHP-сессий необходимо настроить HAproxy для поддержки PHP-сессий
Настройка HAProxy для поддержки PHP-сессий
При первом соединении с HAproxy-сервером клиент будет получать от HAproxy-сервера заголовок Set-Cookie со значением сервера, который его будет обслуживать.
При последующих подключениях этого клиента, браузер клиента сообщает HAproxy-серверу значение заголовка Cookie и уже на основании этого значения HAproxy-сервер направляет запрос клиента на сервер, который обслуживал его первый запрос т.е тем самым достигается возможность обработки всех запросов конкретного клиента одним и тем же сервером(т.е конкретный клиент прикрепляется к конкретному Web-серверу)
В данном случае HAproxy-сервер будет вставлять куку с именем SRVNAME (если только пользователь не пришел с такой кукой т.е, если это первый запрос пользователя),а значение этой куки будет app01 или app02.
1 |
# nano /etc/haproxy/haproxy.cfg |
1 2 3 |
cookie SRVNAME insert server app01 192.168.1.110:80 cookie app01 check server app02 192.168.1.111:80 cookie app02 check |
1 |
# /etc/init.d/haproxy restart |
Тестируем
1 |
# curl -i http://192.168.1.127/session.php |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
HTTP/1.1 200 OK Server: nginx Date: Fri, 20 Dec 2013 20:34:54 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: close X-Powered-By: PHP/5.3.27 Set-Cookie: PHPSESSID=ov4a41q9obh6j5crdd81s9c1b3; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Set-Cookie: SRVNAME=app01; path=/ This is the first time you're visiting this server Server IP: 192.168.1.110 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 Array ( ) |
Для эмуляции работы браузера (передачи полученной от HAproxy-сервера заголовка)
1 2 |
Set-Cookie: PHPSESSID=ov4a41q9obh6j5crdd81s9c1b3; path=/ Set-Cookie: SRVNAME=app01; path=/ |
Используем команду
1 |
# curl -i http://192.168.1.127/session.php --cookie "PHPSESSID=ov4a41q9obh6j5crdd81s9c1b3;SRVNAME=app01;" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
HTTP/1.1 200 OK Server: nginx Date: Fri, 20 Dec 2013 20:39:00 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: close X-Powered-By: PHP/5.3.27 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Your number of visits: 1 Server IP: 192.168.1.110 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 Array ( [PHPSESSID] => ov4a41q9obh6j5crdd81s9c1b3 [SRVNAME] => app01 ) |
1 |
# curl -i http://192.168.1.127/session.php --cookie "PHPSESSID=ov4a41q9obh6j5crdd81s9c1b3;SRVNAME=app01;" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
HTTP/1.1 200 OK Server: nginx Date: Fri, 20 Dec 2013 20:39:48 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: close X-Powered-By: PHP/5.3.27 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Your number of visits: 2 Server IP: 192.168.1.110 Client IP: 192.168.1.39 X-Forwarded-for: 192.168.1.127 Array ( [PHPSESSID] => ov4a41q9obh6j5crdd81s9c1b3 [SRVNAME] => app01 ) |
Оба этих запроса обработал все тот же сервер, который обрабатывал и самый первый запрос клиента(192.168.1.110 — app01)
Настройка HAproxy и Nginx на отображение реального IP-адреса клиента в логах Nginx
а) в haproxy.cfg проверяем наличие строк для требуемого блока listen
1 2 |
option httpclose option forwardfor |
б) проверяем сборку Nginx с опцией http_realip_module
1 2 3 4 |
# nginx –V ……………… with-http_realip_module ………………………… |
Альтернатива:
Источник
http://www.phphighload.com/2012/08/blog-post.html
http://www.timgalyean.com/2011/03/load-balancing-with-haproxy-and-apache/#!
http://www.howtoforge.com/haproxy_loadbalancer_debian_etch_p2