Load Balancing с помощью HAProxy на Vagrant Windows

Так как мне приходится работать большую часть времени на компьютерах под управлением Windows, статья будет предполагать установку Vagrant для Windows и дальнейшую утсановку HAProxy внутри образа Ubuntu. Обычно, разработчики работают под управлением систем Mac OS или Ubuntu, но и Винда сойдет. Само собой разумеется установку Vagrant для Windows я описывать не стану, это проще простого. Если вы не занкомы с Vagrant, почитайте статью в Wikipedia.

Поднимаем Ubuntu на Vagrant

Вся суть Vagrant сводится к тому, что пацаны взяли и написали враппер и драйвер для VirtualBox чтобы другим пацанам было легко заводить локальные виртуальные машины, не вникая в происхождение iso-файлов, VirtualBox и прочей мути. Посему, образ для виртуальной машины подтягивается из специально сформированного разработчиками Vagrant репозитория и ставится в “два клика”

vagrant init ubuntu/trusty64
vagrant up
vagrant init ubuntu/trusty64
vagrant up

Выглядит загрузка машины примерно так:
VagrantFromConsole

Стоит отдельно отметить файл конфигурации Vagrant, в нем необходимо настроить правильный проброс портов. В нашем случае:

config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 1936, host: 1936
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 1936, host: 1936

8080 – для веб сервера
1936 – для веб морды HAProxy где можно посмотреть статистику

Vagrant_File

Подключиться к виртуалке по SSH можно по адресу 127.0.0.1:2222
Login: vagrant
Pass: vagrant

UbuntuVagrant

Установка HAProxy на Ubuntu 14.04.3 LTS

Здесь необходимо краткоe вступление. HAProxy – это известный инструмент для балансировка нагрузки в продакшн системах. Решения от HAProxy используют такие известные игроки на рынке как GitHub, Stack Overflow, Reddit, Tumblr и Twitter.

HAProxy (High Availability Proxy) может обрабатывать просто огромное количество трафика. Подобно Nginx, HAProxy использует single-process, event-driven модель работы, т.е. никаких форков и чайлдов. HAProxy отличается низким потреблением трафика, простотой настройки, стабильностью и возможностью обрабатывать безумное количество конкурентных соединений.

В качестве Веб-сервера, мы будем использовать проcтое NodeJS приложение, которое будет бежать в системе и слушать порты 9000, 9001, 9002 а наш балансировщик нагрузки HAProxy будет равномерно распределять запросы междку всеми “серверами”.

Установка HAProxy:

sudo add-apt-repository -y ppa:vbernat/haproxy-1.5
sudo apt-get update
sudo apt-get install -y haproxy
sudo add-apt-repository -y ppa:vbernat/haproxy-1.5
sudo apt-get update
sudo apt-get install -y haproxy

Теперь давайте создадим NodeJS приложение:

vim /root/server.js
 
var http = require('http');
function serve(ip, port)
{
        http.createServer(function (req, res) {
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.write(JSON.stringify(req.headers));
            res.end();
        }).listen(port, ip);
        console.log('Server running at http://'+ip+':'+port+'/');
}
 
// Create three servers for
// the load balancer, listening on any
// network on the following three ports
serve('0.0.0.0', 9000);
serve('0.0.0.0', 9001);
vim /root/server.js

var http = require('http');
function serve(ip, port)
{
        http.createServer(function (req, res) {
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.write(JSON.stringify(req.headers));
            res.end();
        }).listen(port, ip);
        console.log('Server running at http://'+ip+':'+port+'/');
}

// Create three servers for
// the load balancer, listening on any
// network on the following three ports
serve('0.0.0.0', 9000);
serve('0.0.0.0', 9001);

Для удобства управления нашим NodeJS приложением мы будем использовать process manager PM2. Это поможет вам управлять приложением. Давайте установим PM2:

npm install pm2 -g
npm install pm2 -g

Теперь запуститe ваше приложение:

pm2 start server.js
pm2 start server.js

NodeJS

Некоторые команды, которые помогут вам управлять вашим NodeJS приложением:

pm2 list
pm2 stop     <app_name |id|'all'|json_conf>
pm2 restart  </app_name><app_name |id|'all'|json_conf>
pm2 delete   </app_name><app_name |id|'all'|json_conf>
pm2 reload all
pm2 logs
pm2 save</app_name>
pm2 list
pm2 stop     <app_name |id|'all'|json_conf>
pm2 restart  </app_name><app_name |id|'all'|json_conf>
pm2 delete   </app_name><app_name |id|'all'|json_conf>
pm2 reload all
pm2 logs
pm2 save</app_name>

Теперь когда “веб сервера” запущены и слушают на разных портах, можем заняться настройкой HAProxy. Для этого откройте файл /etc/haproxy/haproxy.cfg

Он должен выглядеть так:

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy
        group haproxy
        daemon
 
        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private
 
        # Default ciphers to use on SSL-enabled listening sockets.
        # For more information, see ciphers(1SSL). This list is from:
        #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3
 
defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http
global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # Default ciphers to use on SSL-enabled listening sockets.
        # For more information, see ciphers(1SSL). This list is from:
        #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http

В файле уже существуют две секции, “global” и “defaults“, в которых определены стандартные настройки, логгирование, режим работы и прочее.

Для того, что-бы балансировщик заработал как надо, мы добавим еще три секции:
frontend – где HAProxy слушает соединения на вход
backend – куда HAPoxy передает входящие соединения
stats – HAProxy веб морда для мониторинга и статистики

Секция frontend

frontend localnodes
    bind *:80
    mode http
    default_backend nodes
frontend localnodes
    bind *:80
    mode http
    default_backend nodes

Где:
bind *:80 – слушать на всех интерфейсах, на порту 80
mode http – слушать только для HTTP соединений
default_backend nodes – фронтенд, использует по умолчанию наш бэкенд

Секция backend

backend nodes
    mode http
    balance roundrobin
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server web01 127.0.0.1:9000 check
    server web02 127.0.0.1:9001 check
    server web03 127.0.0.1:9002 check
backend nodes
    mode http
    balance roundrobin
    option forwardfor
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server web01 127.0.0.1:9000 check
    server web02 127.0.0.1:9001 check
    server web03 127.0.0.1:9002 check

где:
mode http – переводить HTTP соединения
balance roundrobin – алгоритм балансировки
option forwardfor – HAProxy будет добавлять в заголовки X-Forwarded-For, что бы наше приложение могло получать настоящий адрес клиента, в противном случае, приложение будет показывать адрес балансировщика
option httpchk HEAD / HTTP/1.1\r\nHost:localhost – добавляем health check HAProxy, что-бы HAProxy проверял на доступность все наши сервера
server web01-3 127.0.0.0:9000-2 check – это три наши сервера, куда HAProxy распределяет трафик

Секция stats:

listen stats *:1936
        stats enable
        stats uri /
        stats hide-version
        stats auth someuser:password
listen stats *:1936
        stats enable
        stats uri /
        stats hide-version
        stats auth someuser:password

Рестартуем балансировщик:

sudo service haproxy restart
sudo service haproxy restart

Проверяем что он бежит в системе:

[email protected]:~$ ps aux | grep hap
haproxy   1365  0.0  0.3  29436  1856 ?        Ss   Oct19   0:13 /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -D -p /var/run/haproxy.pid
[email protected]:~$ ps aux | grep hap
haproxy   1365  0.0  0.3  29436  1856 ?        Ss   Oct19   0:13 /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -D -p /var/run/haproxy.pid

Теперь идем в браузере по адресу http://127.0.0.1:8080/
LocalNODE

Что-бы понять, что баланс работает, и HAProxy кидает нас по разным серверaм, измените строку:
res.end();

На:

res.end("\nI'am here right now "+ip+":"+port+"\n");
res.end("\nI'am here right now "+ip+":"+port+"\n");

Перезапустите вашу апликацию:

pm2 restart server
pm2 restart server

Смотрим результат в браузере, нажимайте F5 обновить страницу:
1

2

3

Посмотреть статистику можно по адресу http://127.0.0.1:1936/
Stats

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