Bu örneğimizde sanal bir IP adresine istek göndereceğiz, aynı istek aktif olan bir load balancer (HAProxy) sunucusuna iletilecek ve cevabı yine load balancer aracılığıyla web sunucularından alacağız. İstekler hiçbir zaman web sunucusuna direkt olarak iletilmeyecek. İsteklerin hangi web sunucular tarafından işleneceğine load balancer karar verir. Bu örnek vagrant ile layer 4 (transport layer) load balancing modeli üzerine kuruludur. HAProxy konfigürasyon bilgileri için burayı tıklayın. Aktif olan HAProxy sunucusun kullanılması için Keepalived arka planda her zaman çalışıyor olacak. Eğer bir load balancer çalışmaz haldeyse, Keepalived sanal IP adresini çalışana atayacak.

Örnekte iki web sunucusunda apache ve diğer iki load balancer sunucusunda ise HAProxy ve Keepalived çalışıyor olacak. Load balancer GUI host makinadan ulaşılabilir olacak ve bu şekilde web sunucuları ile ilgili istatistikleri görebileceğiz.


Not


Bu örnek vagrant üzerine kurulu olduğu için, Keepalived sanal IP beklendiği gibi çalışmayabilir ama aynı durumun normal sunucular üzerinde de ortaya çıkacağı anlamına gelmez.


Yaptığımız şey



Konfigürasyon


Sisteminizde vagrant ve Oracle VM yazılımlarının kurulu olduğunu varsayıyorum. Bununla birlikte ubuntu/trusty64 kutusunun sisteminize vagrant box add ubuntu/trusty64 komutu ile eklendiğini de varsayıyorum. Eğer emin değilseniz ls -l ~/.vagrant.d/boxes/ komutu ile kontrol edebilirsiniz.


Yeni proje klasörü yaratmak


mkdir lay4-hap2-web2
$ cd lay4-hap2-web2/

webserver.sh dosyası


#!/usr/bin/env bash

# BEGIN ########################################################################
echo -e "-- ---------- --\n"
echo -e "-- BEGIN ${HOSTNAME} --\n"
echo -e "-- ---------- --\n"

# VARIABLES ####################################################################
echo -e "-- Setting global variables\n"
APACHE_CONFIG=/etc/apache2/apache2.conf
SITES_ENABLED=/etc/apache2/sites-enabled
LOCALHOST=localhost

# BOX ##########################################################################
echo -e "-- Updating packages list\n"
apt-get update -y -qq

# APACHE #######################################################################
echo -e "-- Installing Apache web server\n"
apt-get install -y apache2 > /dev/null 2>&1

echo -e "-- Adding ServerName to Apache config\n"
grep -q "ServerName ${LOCALHOST}" "${APACHE_CONFIG}" || echo "ServerName ${LOCALHOST}" >> "${APACHE_CONFIG}"

echo -e "-- Updating vhost file\n"
cat > ${SITES_ENABLED}/000-default.conf <<EOF
<VirtualHost *:80>
DocumentRoot /var/www/html

SetEnvIf Request_Method OPTIONS do-not-log-haproxy-ping
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined env=!do-not-log-haproxy-ping
</VirtualHost>
EOF

echo -e "-- Adding a custom LogFormat to Apache config catch client's request IP\n"
grep -q 'LogFormat "%{X-Forwarded-For}i %l %u %t \\"%r\\" %>s %b \\"%{Referer}i\\" \\"%{User-Agent}i\\"" combined' ${APACHE_CONFIG} || echo 'LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined' >> ${APACHE_CONFIG}

echo -e "-- Restarting Apache web server\n"
service apache2 restart

# TEST #########################################################################
echo -e "-- Creating a dummy index.html file\n"
cat > /var/www/html/index.html <<EOD
<html>
<head>
<title>${HOSTNAME}</title>
</head>
<body>
<h1>${HOSTNAME}</h1>
<p>Hi sir, I am going to serve you today!</p>
</body>
</html>
EOD

# END ##########################################################################
echo -e "-- -------- --"
echo -e "-- END ${HOSTNAME} --"
echo -e "-- -------- --"

haproxy.sh dosyası


#!/usr/bin/env bash

# BEGIN ########################################################################
echo -e "-- ---------- --\n"
echo -e "-- BEGIN ${HOSTNAME} --\n"
echo -e "-- ---------- --\n"

# VARIABLES ####################################################################
echo -e "-- Setting global variables\n"
SYSCTL_CONFIG=/etc/sysctl.conf

# BOX ##########################################################################
echo -e "-- Updating packages list\n"
apt-get update -y -qq

# HAPROXY ######################################################################
echo -e "-- Installing HAProxy\n"
apt-get install -y haproxy > /dev/null 2>&1

echo -e "-- Enabling HAProxy as a start-up deamon\n"
cat > /etc/default/haproxy <<EOF
ENABLED=1
EOF

echo -e "-- Configuring HAProxy\n"
cat > /etc/haproxy/haproxy.cfg <<EOF
global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
daemon
maxconn 2000

defaults
log global
mode http
option httplog
option dontlognull
retries 3
option redispatch
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms

frontend http-in
bind *:80
default_backend webservers

backend webservers
mode http
stats enable
stats auth admin:admin
stats uri /haproxy?stats
balance roundrobin
option httpchk
option forwardfor
option http-server-close
server web1 192.168.50.21:80 maxconn 32 check
server web2 192.168.50.22:80 maxconn 32 check
EOF

echo -e "-- Validating HAProxy configuration\n"
haproxy -f /etc/haproxy/haproxy.cfg -c

echo -e "-- Starting HAProxy\n"
service haproxy start

# KEEPALIVED ###################################################################
echo -e "-- Installing Keepalived\n"
apt-get install -y keepalived > /dev/null 2>&1

echo -e "-- Allowing HAProxy to bind to the virtual IP address\n"
grep -q "net.ipv4.ip_nonlocal_bind=1" "${SYSCTL_CONFIG}" || echo "net.ipv4.ip_nonlocal_bind=1" >> "${SYSCTL_CONFIG}"

echo -e "-- Enabling virtual IP binding\n"
sysctl -p

echo -e "-- Configuring Keepalived\n"
cat > /etc/keepalived/keepalived.conf <<EOF
vrrp_script chk_haproxy {
script "killall -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_1 {
interface eth1 # This may be eth0
state MASTER
virtual_router_id 51
priority ${PRIORITY}
virtual_ipaddress {
192.168.50.10
}
track_script {
chk_haproxy
}
}
EOF

echo -e "-- Starting Keepalived\n"
service keepalived start

# END ##########################################################################
echo -e "-- -------- --"
echo -e "-- END ${HOSTNAME} --"
echo -e "-- -------- --"

Vagrantfile içeriği


# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64"

# Configs for haproxy 1 (master)
config.vm.define :hap1 do |hap1_config|
hap1_config.vm.provider :virtualbox do |vb_config|
vb_config.name = "HAProxy 1 - lay4-hap2-web2"
end
hap1_config.vm.hostname = "hap1"
hap1_config.vm.network "private_network", ip: "192.168.50.11"
hap1_config.vm.provision :shell, path: "haproxy.sh", env: {"PRIORITY" => "101"}
end

# Configs for haproxy 2 (backup)
config.vm.define :hap2 do |hap2_config|
hap2_config.vm.provider :virtualbox do |vb_config|
vb_config.name = "HAProxy 2 - lay4-hap2-web2"
end
hap2_config.vm.hostname = "hap2"
hap2_config.vm.network "private_network", ip: "192.168.50.12"
hap2_config.vm.provision :shell, path: "haproxy.sh", env: {"PRIORITY" => "100"}
end

# Configs for web server 1
config.vm.define :web1 do |web1_config|
web1_config.vm.provider :virtualbox do |vb_config|
vb_config.name = "Web Server 1 - lay4-hap2-web2"
end
web1_config.vm.hostname = "web1"
web1_config.vm.network "private_network", ip: "192.168.50.21"
web1_config.vm.provision :shell, path: "webserver.sh"
end

# Configs for web server 2
config.vm.define :web2 do |web2_config|
web2_config.vm.provider :virtualbox do |vb_config|
vb_config.name = "Web Server 2 - lay4-hap2-web2"
end
web2_config.vm.hostname = "web2"
web2_config.vm.network "private_network", ip: "192.168.50.22"
web2_config.vm.provision :shell, path: "webserver.sh"
end
end

Vagrant kutusunu başlatmak


$ vagrant up --provision
Bringing machine 'web1' up with 'virtualbox' provider...
Bringing machine 'web2' up with 'virtualbox' provider...
Bringing machine 'hap1' up with 'virtualbox' provider...
Bringing machine 'hap2' up with 'virtualbox' provider...
==> hap1: -- ---------- --
==> hap1: -- BEGIN hap1 --
==> hap1: -- ---------- --
==> hap1: -- Setting global variables
==> hap1: -- Updating packages list
==> hap1: -- Installing HAProxy
==> hap1: -- Enabling HAProxy as a start-up deamon
==> hap1: -- Configuring HAProxy
==> hap1: -- Validating HAProxy configuration
==> hap1: Configuration file is valid
==> hap1: -- Starting HAProxy
==> hap1: * Starting haproxy haproxy
==> hap1: ...done.
==> hap1: -- Installing Keepalived
==> hap1: -- Allowing HAProxy to bind to the virtual IP address
==> hap1: -- Enabling virtual IP binding
==> hap1: net.ipv4.ip_nonlocal_bind = 1
==> hap1: -- Configuring Keepalived
==> hap1: -- Starting Keepalived
==> hap1: * Starting keepalived keepalived
==> hap1: ...done.
==> hap1: -- -------- --
==> hap1: -- END hap1 --
==> hap1: -- -------- --
==> hap2: -- ---------- --
==> hap2: -- BEGIN hap2 --
==> hap2: -- ---------- --
==> hap2: -- Setting global variables
==> hap2: -- Updating packages list
==> hap2: -- Installing HAProxy
==> hap2: -- Enabling HAProxy as a start-up deamon
==> hap2: -- Configuring HAProxy
==> hap2: -- Validating HAProxy configuration
==> hap2: Configuration file is valid
==> hap2: -- Starting HAProxy
==> hap2: * Starting haproxy haproxy
==> hap2: ...done.
==> hap2: -- Installing Keepalived
==> hap2: -- Allowing HAProxy to bind to the virtual IP address
==> hap2: -- Enabling virtual IP binding
==> hap2: net.ipv4.ip_nonlocal_bind = 1
==> hap2: -- Configuring Keepalived
==> hap2: -- Starting Keepalived
==> hap2: * Starting keepalived keepalived
==> hap2: ...done.
==> hap2: -- -------- --
==> hap2: -- END hap2 --
==> hap2: -- -------- --
==> web1: -- ---------- --
==> web1: -- BEGIN web1 --
==> web1: -- ---------- --
==> web1: -- Setting global variables
==> web1: -- Updating packages list
==> web1: -- Installing Apache web server
==> web1: -- Adding ServerName to Apache config
==> web1: -- Updating vhost file
==> web1: -- Adding a custom LogFormat to Apache config catch client's request IP
==> web1: -- Restarting Apache web server
==> web1: * Restarting web server apache2
==> web1: ...done.
==> web1: -- Creating a dummy index.html file
==> web1: -- -------- --
==> web1: -- END web1 --
==> web1: -- -------- --
==> web2: -- ---------- --
==> web2: -- BEGIN web2 --
==> web2: -- ---------- --
==> web2: -- Setting global variables
==> web2: -- Updating packages list
==> web2: -- Installing Apache web server
==> web2: -- Adding ServerName to Apache config
==> web2: -- Updating vhost file
==> web2: -- Adding a custom LogFormat to Apache config catch client's request IP
==> web2: -- Restarting Apache web server
==> web2: * Restarting web server apache2
==> web2: ...done.
==> web2: -- Creating a dummy index.html file
==> web2: -- -------- --
==> web2: -- END web2 --
==> web2: -- -------- --

Kutulara girmek


# Server 1
$ vagrant ssh web1
vagrant@web1:~$

# Server 2
$ vagrant ssh web2
vagrant@web2:~$

# HAProxy 1
$ vagrant ssh hap1
vagrant@hap1:~$

# HAProxy 2
$ vagrant ssh hap2
vagrant@hap2:~$

Keepalived sanal IP kontrolü


Aşağıda da gördüğümüz gibi sanal IP master olan hap1 sunucusuna atanmış durumda. Satırlardan inet 192.168.50.10/32 scope global eth1'e dikkat edin. Aynı satır backup olan hap2 sunucusunda görünmüyor.


HAProxy 1


vagrant@hap1:~$ sudo ip addr sh eth1

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:79:c5:df brd ff:ff:ff:ff:ff:ff
inet 192.168.50.11/24 brd 192.168.50.255 scope global eth1
valid_lft forever preferred_lft forever
inet 192.168.50.10/32 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe79:c5df/64 scope link
valid_lft forever preferred_lft forever

HAProxy 2


vagrant@hap2:~$ sudo ip addr sh eth1

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:17:29:f3 brd ff:ff:ff:ff:ff:ff
inet 192.168.50.12/24 brd 192.168.50.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe17:29f3/64 scope link
valid_lft forever preferred_lft forever

Eğer hap1 üzerinde çalışmakta olan HAProxy sunucusunu durdurursanız, Keepalived sanal IP adresini hap2 sunucusuna atayacak ve böylece inet 192.168.50.10/32 scope global eth1 satırı master durumuna geçen hap2 içinde görünecek. Eğer her iki sunucuda sudo cat /var/log/syslog komutunu kullanırsanız, durum değişimi ile ilgili bilgileri görebilirsiniz.


Testler


Normal hayatta web sunucularına direkt ulaşımın kesilmesi lazım ama ben bunu bu örnekte yapmayacağım. HAProxy sunucuları ise sadece Keepalived ile yönetilen sanal IP adresi ile ulaşılabilir durumdadır.


Web sunucusu 1


Aşağıdaki isteğin sonucu her zaman aynı olacak.


# Request
http://192.168.50.21/

# Response
web1
Hi sir, I am going to serve you today!

Web sunucusu 2


Aşağıdaki isteğin sonucu her zaman aynı olacak.


# Request
http://192.168.50.22/

# Response
web2
Hi sir, I am going to serve you today!

Load balancer


Aşağıdaki isteğin sonucu her zaman farklı olacak çünkü load balancer istekleri sunucular arasında paylaştırır.


# Request
http://192.168.50.10/

# Response
web1
Hi sir, I am going to serve you today!

# Request
http://192.168.50.10/

# Response
web2
Hi sir, I am going to serve you today!

# Request
http://192.168.50.10/

# Response
web1
Hi sir, I am going to serve you today!

Sistem yetersizliği testi


Web sunucusu 1 kapalı


Load balancer tüm istekleri web sunucusu 2'ye yönlendirecek.


Web sunucusu 2 kapalı


Load balancer tüm istekleri web sunucusu 1'e yönlendirecek.


Web sunucusu 1 ve 2 kapalı


Load balancer istekleri artık hiçbir yere yönlendiremeyecek ve aşağıdaki hata mesajı alınacak. Hata mesajı HAProxy config dosyasındaki ayarlar ile değiştirilebilir.


503 Service Unavailable
No server is available to handle this request.

HAProxy 1 kapalı


Keepalived sanal IP adresini hap2 sunucusuna atayacak ve sistem çalışmaya devam edecek.


HAProxy 2 kapalı


Keepalived sanal IP adresini hap1 sunucusuna atayacak ve sistem çalışmaya devam edecek.


HAProxy 1 ve HAProxy 2 kapalı


Sistem çalışmaz hale gelecek çünkü isteklerin gideceği başka bir load balancer yok.


Session kontrolü


Web uygulamalarında kullanıcı session bilgileri sunucu üzerindeki geçici bir yerde tutulur. Load balancer kullanılan durumlarda kullanıcı sürekli farklı sunuculara yönlendirileceği için, session bilgileri ulaşılabilir halde olmayacak. Sonuç olarak, örneğin kullanıcı sürekli login olmak zorunda kalabilir. Bu problemi çözmek için üç farklı yöntem vardır:



Web sunucusu kayıtları


Keepalived her iki saniyede bir web sunucusunu kontrol ettiği için, Apache access.log dosyası şişecektir. Bu tarz gereksiz şişmeleri engellemek için /etc/apache2/sites-enabled/000-default.conf dosyasını aşağıdaki gibi yeniledik.


SetEnvIf Request_Method OPTIONS do-not-log-haproxy-ping
CustomLog /var/log/apache2/access.log combined env=!do-not-log-haproxy-ping

Eğer bir kullanıcı http://192.168.50.10 adresine bir istek gönderirse, load balancer isteği web sunucusuna iletir ve web sunucusu bu istek ile ilgili bilgileri aşağıdaki gibi access.log dosyası içinde kayıt altına alır.


192.168.50.11 - - [09/Jul/2016:13:22:52 +0000] "OPTIONS / HTTP/1.0" 200 180 "-" "-"
192.168.50.12 - - [09/Jul/2016:13:22:53 +0000] "OPTIONS / HTTP/1.0" 200 180 "-" "-"
192.168.50.11 - - [09/Jul/2016:13:22:54 +0000] "OPTIONS / HTTP/1.0" 200 180 "-" "-"
192.168.50.12 - - [09/Jul/2016:13:22:55 +0000] "OPTIONS / HTTP/1.0" 200 180 "-" "-"

Yukarıda da gördüğümüz gibi, HAProxy IP kayıt altına alınır ki bu da gerçek hayatta hiçbir işimize yaramaz. Gerçek bilgileri kaydetmek için aşağıdaki gibi /etc/apache2/apache2.conf dosyasında yenileme yaptık.


LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

Artık access.log içindeki istek kayıtları aşağıdaki gibi olacaktır.


192.168.50.1 - - [09/Jul/2016:13:24:24 +0000] "GET / HTTP/1.1" 200 117 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
192.168.50.1 - - [09/Jul/2016:14:42:44 +0000] "GET / HTTP/1.1" 200 117 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:47.0) Gecko/20100101 Firefox/47.0"

Kontrol için web sunucuları üzerinde sudo tail -f /var/log/apache2/access.log komutunu kullanabilirsiniz.


HAProxy kayıtları


Her iki HAProxy sunucularıni durdurup yeniden başlatırsanız, aşağıdaki kayıt bilgilerini /var/log/haproxy.log dosyasında görebilirsiniz.


# hap1

Jul 14 20:51:52 vagrant-ubuntu-trusty-64 Keepalived_vrrp[3026]: VRRP_Script(chk_haproxy) succeeded
Jul 14 20:51:53 vagrant-ubuntu-trusty-64 Keepalived_vrrp[3026]: VRRP_Instance(VI_1) Transition to MASTER STATE
Jul 14 20:51:54 vagrant-ubuntu-trusty-64 Keepalived_vrrp[3026]: VRRP_Instance(VI_1) Entering MASTER STATE
Jul 14 20:53:06 vagrant-ubuntu-trusty-64 Keepalived_vrrp[3026]: VRRP_Instance(VI_1) Received lower prio advert, forcing new election
Jul 14 21:01:36 vagrant-ubuntu-trusty-64 Keepalived_vrrp[3026]: VRRP_Script(chk_haproxy) failed
Jul 14 21:01:38 vagrant-ubuntu-trusty-64 Keepalived_vrrp[3026]: VRRP_Instance(VI_1) Received higher prio advert
Jul 14 21:01:38 vagrant-ubuntu-trusty-64 Keepalived_vrrp[3026]: VRRP_Instance(VI_1) Entering BACKUP STATE

# hap2

Jul 14 20:53:05 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Script(chk_haproxy) succeeded
Jul 14 20:53:06 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Instance(VI_1) Transition to MASTER STATE
Jul 14 20:53:06 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Instance(VI_1) Received higher prio advert
Jul 14 20:53:06 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Instance(VI_1) Entering BACKUP STATE
Jul 14 21:00:38 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Script(chk_haproxy) failed
Jul 14 21:01:18 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Script(chk_haproxy) succeeded
Jul 14 21:01:38 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Instance(VI_1) forcing a new MASTER election
Jul 14 21:01:39 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Instance(VI_1) Transition to MASTER STATE
Jul 14 21:01:40 vagrant-ubuntu-trusty-64 Keepalived_vrrp[2973]: VRRP_Instance(VI_1) Entering MASTER STATE

HAProxy GUI


Load balancer istatistik sayfası http://192.168.50.10/haproxy?stats adresinden admin:admin login bilgileri ile ulaşılabilir. Bilgiler sadece aktif olan master veya backup load balancer sunucusundan gelecektir. "Statistics Report for pid 8038" başlığındaki PID numarasını, her iki sunucu üzerinde $ ps aux | grep haproxy komutunu çalıştırarak alacağınız sonuç ile karşılaştırabilirsiniz. Bu şekilde hangi load balancerin aktif olduğunu anlayabilirsiniz.


Tarayıcıdan http://192.168.50.30/haproxy?stats adresine gidip admin:admin ile login olabilirsiniz.





Faydalı bilgiler