LVS + OpenVZ

Доброго часу доби, шановні читачі!
У цій статті я хочу розповісти вам про технології балансування навантаження, трохи про відмовостійкості і як все це подружити з контейнерами в OpenVZ. Будуть розглянуті основи LVS, режими роботи та налаштування зв'язки LVS c контейнерами в OpenVZ. Стаття містить в собі як теоретичні аспекти роботи даних технологій, так і практичну частину — передача трафіку від балансувальника всередину контейнерів. Якщо це вас зацікавило — ласкаво просимо!


Для початку — посилання на публікації по цій темі на Хабре:
Докладний опис роботи LVS. Краще не скажеш
Стаття про контейнери OpenVZ

Короткий виклад матеріалу вище:
LVS (Linux Virtual Server) — технологія, базирующая на IPVS (IP Virtual Server), яка присутня в ядрах Linux версії 2.4.x і новіше. Є якимось віртуальним свічом 4 рівня.


На даній картинці зображений LVS (сам балансувальник), віртуальний адресу, до якого відбуваються обігу (VIP) 192.168.1.100 і 2 сервера, виступаючих в ролі бек-ендів: 192.168.1.201 і 192.168.1.202
В загальному випадку працює все так — ми робимо точку входу (VIP), куди приходять всі запити. Далі трафік перенаправляється до своїх бэкэндам, де відбувається його обробка і відповідь клієнту безпосередньо (у схемі NAT — відповідь через LVS назад)
З методами балансування можна ознайомитися тут: посилання. далі в статті вважаємо, що балансування Round-Robin
Режими роботи:
Трохи докладніше про кожному:

1) Direct
У даному режимі роботи обробка запиту відбувається за наступним сценарієм:
Клієнт відправляє пакет в мережу адресою VIP. Цей пакет ловиться сервером LVS, в ньому підміняється MAK адреса призначення (і тільки він!!) на мак адресу одного з серверів бек-енду і пакет відправляється назад у мережу. Його отримує сервер бек-енду, обробляє і відправляє запит безпосередньо клієнтові. Клієнт отримує відповідь на свій запит фізично з іншого сервера, але підміни не бачить і радісно шарудить. Уважний читач звичайно обуриться — як зможе Real Server обробити запит, який прийшов на іншу IP-адресу (в пакеті адреса призначення — все той же VIP)? І буде правий, тому що для коректної роботи слід адреса (VIP) куди-небудь у бек-енду розмістити, найчастіше це вішається на loopback. Таким чином відбувається найбільша афера в цій технології.

2) NAT
Найпростіший режим роботи. Запит від клієнта приходить на LVS, LVS перенаправляє запит бек-енду, той обробляє запит, відповідає назад на LVS і той відповідає клієнту. Ідеально впишеться в схему, де у вас є шлюз, через який йде весь трафік мережі. Інакше робити НАТ в частині мережі буде вкрай неправильно.

3) Tunnel
Є аналогом першого способу, тільки трафік від LVS до бэкендов інкапсулюється в пакет. Саме його ми і будемо налаштовувати нижче, тому зараз всіх карт я не розкрию :)

Практика!

Встановимо сервер з LVS. Налаштовуємо на CentOS 6.6.
На чистій системі робимо
yum install piranha

За собою вона потягне апач, пхп і потрібний нам ipvsadm. Апач і пхп ставляться для доступу до адміністративної веб-морді, яку я раджу вам один раз подивитися, базово все налаштувати і більше не заходити туди :)
Після установки всіх пакетів робимо
/etc/init.d/piranha-gui ; /etc/init.d/httpd start

задаємо пароль для доступу до адмінки:
piranha-passwd

Після цього заходимо за адресою IP_LVS:3636/, вводимо логін(piranha) і пароль кроку вище і потрапляємо в адмінку:
Так сказати адмінка

Для нас зараз цікаві дві вкладки — GLOBAL SETTINGS і VIRTUAL SERVERS
Переходимо на GLOBAL SETTINGS і виставимо режим роботи Tunneling
Маленький ліричний відступ про OpenVZЯк ви вже напевно знаєте, якщо працювали з OpenVZ, користувачеві надається вибір з двох типів інтерфейсів — venet і veth. Принципова різниця між ними в тому, що veth це по суті віртуальний мережевий інтерфейс для кожної віртуальної машини зі своїм мак адресою. Venet — якийсь величезний свіч 3 рівня, до якого підключені всі ваші машини.
Більш докладно можна почитати ось тут
Порівняльна таблиця інтерфейсів з посилання вище:


Так склалося, що у мене на роботі повсюдно використовується venet, тому налаштування виконується саме на ньому.
Відразу скажу — налаштувати LVS-Direcrt для цього типу інтерфейсу мені не вдалося. Все зупиняється на тому, що нода з віртуальними машинами отримує трафік, але не знає, у яку машину його відправити. Про це я трохи докладніше зупинюся при пробросе трафіку всередину контейнера

На вкладці VIRTUAL SERVERS створюємо один Virtual Server з адресою 192.168.1.100 і порт 80
Там же на вкладці REAL SERVER поставимо два бек енду з адресами 192.168.1.201 і 192.168.1.202 з портами 80
Подробиці на скріншотах під спойлером
Скріншоти налаштування


Вкладку MONITORING SCRIPTS залишимо без змін, хоча на ній можна налаштувати дуже гнучку роботу перевірки доступності вузлів. За замовчуванням це просто запит типу GET / HTTP/1.0 і перевірка, що нам відповів саме веб-сервер.
Можна виконувати довільні скрипти, наприклад такі як під спойлером.
Скрипти перевірки різних сервісівНаприклад мускуль
#!/bin/sh
CMD=/usr/bin/mysqladmin

IS_ALIVE=`timeout 2s $CMD-h $1-P $2 ping | grep-c "alive"`

if [ "$IS_ALIVE" = "1" ]; then
echo "UP"
else
echo "DOWN"
fi

І перевірка в LVS вважається вдалою, якщо скрипт повернув UP і не вдалою, якщо DOWN. Сервера він виводить за результатами тесту
Шматок конфига для цієї перевірки
expect = "UP"
use_regex = 0
send_program = "/opt/admin/mysql_chesk.sh %h 9005"



Зберігаємо конфіг, закриваємо віконце і переходимо в звичну нам консоль. Якщо раптом вам стало цікаво, що ми там нагенерили, то конфіг служби лежить в /etc/sysconfig/ha/lvs.cf
Дивимося поточні параметри:
ipvsadm-L-n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.1.100:80 wlc

Не густо. Це тому що наші кінцеві ноди не підняті! Установка і створення контейнерів OpenVZ пропускаю, вважаємо що у вас чарівним чином з'явилися два контейнери з адресами 192.168.1.201 і 192.168.1.202, а всередині будь-якої веб-сервер на 80 порту :)
Знову дивимося на вивід команди:
ipvsadm-L-n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.1.100:80 wlc
-> 192.168.1.201:80 Tunnel 1 0 0 
-> 192.168.1.202:80 Tunnel 1 0 0 

Ідеально! Те, що потрібно. Якщо раптом ми вимкнемо веб сервер на одній з нод, то наш балансувальник чесно не буде слати туди трафік, поки ситуація не виправиться.
Давайте детальніше поглянемо на наш конфіг для LVS:
/etc/sysconfig/ha/lvs.cfserial_no = 4
primary = 192.168.1.25
service = lvs
network = tunnel
debug_level = NONE
virtual habrahabr {
active = 1
address = 192.168.1.100 eth0:1
port = 80
send = «GET / HTTP/1.0\r\n\r\n
expect = «HTTP»
use_regex = 0
load_monitor = none
scheduler = ps
protocol = tcp
timeout = 6
reentry = 15
quiesce_server = 0
server test1 {
address = 192.168.1.202
active = 1
weight = 1
}
server test2 {
address = 192.168.1.201
active = 1
weight = 1
}
}

Найбільш цікаві параметри тут це timeout і reentry. У цій конфігурації якщо наш бекенд не відповість протягом 6 секунд — ми туди нічого відправляти не будемо. Як тільки наш поганець стане відповідати нам протягом 15 секунд — можемо відправляти туди трафік.
Є ще quiesce_server — якщо сервер повертається до ладу, то всі лічильники сполук скидається в нуль і з'єднання починають розподілятися як після запуску служби.
У LVS є свій механіз Актив-Пасив, який в рамках даної статті не розглядається, і мені не дуже подобається. Я б порекомендував використовувати Pacemaker, оскільки у нього є вбудовані механізми перекидання служби pulse (яка як раз і відповідає за весь механізм)
Але повернемося до реальності.
Наші машинки бачаться, балансувальник готовий відправляти на них трафік. Зробимо на LVS
chkconfig pulse on

і спробуємо звернутися наприклад курлом до нашого VIP:

Результат
curl-vv http://192.168.1.100
* Rebuilt URL to: http://192.168.1.100/
* About to connect() to 192.168.1.100 port 80 (#0)
* Trying 192.168.1.100...
* Adding handle: conn: 0x7e9aa0
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7e9aa0) send_pipe: 1, recv_pipe: 0
* Connection timed out
* Connect Failed to 192.168.1.100:80; Connection timed out
* Closing connection 0
curl: (7) connect Failed to 192.168.1.100:80; Connection timed out





Давайте розбиратися! Причин може бути кілька, і одна з них — iptables на lvs. Хоч він і займається перекиданням трафіку, але порт повинен бути доступний. Озброїмося tcpdump'ом і поліземо на LVS.
Запускаємо і бачимо:
tcpdump-i any host 192.168.1.100
tcpdump: verbose output suppressed, use-v or-vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 байт
13:36:09.802373 IP 192.168.1.18.37222 > 192.168.1.100.http: Flags [S], seq 3328911904, win 29200, options [mss 1460,sackOK,TS val 2106524 ecr 0,nop,wscale 7], length 0
13:36:10.799885 IP 192.168.1.18.37222 > 192.168.1.100.http: Flags [S], seq 3328911904, win 29200, options [mss 1460,sackOK,TS val 2106774 ecr 0,nop,wscale 7], length 0
13:36:12.803726 IP 192.168.1.18.37222 > 192.168.1.100.http: Flags [S], seq 3328911904, win 29200, options [mss 1460,sackOK,TS val 2107275 ecr 0,nop,wscale 7], length 0

Запити прийшли, що ж з ними сталося далі?
Там же
tcpdump-i any host 192.168.1.201 or host 192.168.1.202
tcpdump: verbose output suppressed, use-v or-vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 байт
13:37:08.257049 IP 192.168.1.25 > 192.168.1.201: IP 192.168.1.18.37293 > 192.168.1.100.http: Flags [S], seq 1290874035, win 29200, options [mss 1460,sackOK,TS val 2121142 ecr 0,nop,wscale 7], length 0 (/ip-proto-4)
13:37:08.257538 IP 192.168.1.201 > 192.168.1.25: ICMP 192.168.1.201 protocol 4 unreachable, length 88
13:37:09.255564 IP 192.168.1.25 > 192.168.1.201: IP 192.168.1.18.37293 > 192.168.1.100.http: Flags [S], seq 1290874035, win 29200, options [mss 1460,sackOK,TS val 2121392 ecr 0,nop,wscale 7], length 0 (/ip-proto-4)
13:37:09.256192 IP 192.168.1.201 > 192.168.1.25: ICMP 192.168.1.201 protocol 4 unreachable, length 88

А трафік не ходить… Біда! Йдемо на наші ноди з OpenVZ, заходимо всередину виртуалок і дивимося там трафік. Запити від LVS до них дійшли, але не можуть бути оброблені — protocol 4 це у нас IP-in-IP
Включаємо підтримку тунелів для виртуалок
На ноди:
modprobe / ip
Бачимо у висновку lsmod | grep / ip модулі

Не забуваємо внести їх в автозавантаження —
cd /etc/sysconfig/modules/
echo "#!/bin/sh" > / ip.modules
echo "/sbin/modprobe / ip" >> / ip.modules
chmod +x / ip.modules

Дозволяємо нашим віртуальним машинам мати тунельні інтерфейси:
vzctl set 201 --feature / ip:on save --
vzctl set 202 --feature / ip:on save --

Після цього рестартим контейнери.
Тепер нам необхідно додати на цей tun0) інтерфейс адреса всередині контейнерів. Так і зробимо:
ifconfig tunl0 192.168.1.100 netmask 255.255.255.255 broadcast 192.168.1.100

Чому так?
Direct, Tunnel і адресиЗагальне в цих двох методів є те, що в кінцевій системі (бекенд) додаються VIP адреси для коректної роботи. Чому так? Відповідь проста: клієнт звертається до фіксованого адресою і чекає від нього відповідь. Якщо йому відповість хтось з іншої адреси, то клієнт вважатиме таку відповідь помилкою і просто проігнорує її. Уявіть, що ви просите покликати до телефону милу дівчину Оксану, а вам відповідає сиплий голос Валентина Яковича.
Для Direct етапи обробки пакета вибудовуються в ланцюжок:
Клієнт робить ARP запит до адресою VIP, отримує відповідь, формує запит із даними, відправляє його на VIP, LVS ці пакети зловив, поміняв там MAK, віддав назад у мережу, мережеве обладнання доставило по маку пакет бэкенду, той став його розгортати починаючи з другого рівня. МАК мій? Так. А мій IP? Так. Обробив і відповів з source VIP (пакет то йому призначений) і на destination клієнту.
Для Tunnel ситуація майже аналогічна, тільки без підміни МАК, а повноцінний инкапсулированный трафік. Бекенд отримав пакет призначений конкретно йому, а всередині запит на адресу VIP, який бекенд повинен обробити і відповісти.

Тепер запустивши tcpdump ми побачимо довгоочікувані запити!
tcpdump-i any host 192.168.1.18
tcpdump: verbose output suppressed, use-v or-vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 байт
14:03:28.907670 IP 192.168.1.18.38850 > 192.168.1.100.http: Flags [S], seq 3110076845, win 29200, options [mss 1460,sackOK,TS val 2516581 ecr 0,nop,wscale 7], length 0
14:03:29.905359 IP 192.168.1.18.38850 > 192.168.1.100.http: Flags [S], seq 3110076845, win 29200, options [mss 1460,sackOK,TS val 2516831 ecr 0,nop,wscale 7], length 0

192.168.1.18 — це клієнт.
Запит дійшов до машини! Всім пляцків! Але зупинятися рано, продовжимо. Чому нам ніхто не відповідає? Вся справа в хитрющей налаштування ядра, яке перевіряє зворотний шлях до джерела — .
Вимкнемо цю перевірку для нашого інтерфейсу всередині контейнера:
echo 0 > /proc/sys/net/ipv4/conf/tunl0/.

Перевіряємо:
tcpdump-i any host 192.168.1.18
tcpdump: verbose output suppressed, use-v or-vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 байт
14:07:03.870449 IP 192.168.1.18.39051 > 192.168.1.100.http: Flags [S], seq 89280152, win 29200, options [mss 1460,sackOK,TS val 2570336 ecr 0,nop,wscale 7], length 0
14:07:03.870499 IP 192.168.1.100.http > 192.168.1.18.39051: Flags [S.], seq 593110812, ack 89280153, win 14480, options [mss 1460,sackOK,TS val 3748869 ecr 2570336,nop,wscale 7], length 0

Відповіді! Відповіді! Але дива все ще не відбувається. Вибач, Маріо, твоя принцеса в іншому замку. Щоб піти в інший замок, спершу все запишемо:
Відключаємо перевірку. і додамо інтерфейс. Всередині контейнерів:
echo "net.ipv4.conf.tunl0.. = 0" >> /etc/sysctl.conf
echo "ifconfig tunl0 192.168.1.100 netmask 255.255.255.255 broadcast 192.168.1.100" >> /etc/rc.local

І на ноди:
echo "net.ipv4.conf.venet0.. = 0" >> /etc/sysctl.conf

Рестартимся, щоб підтвердити, що все правильно.
В результаті рестарту контейнера повинна бути така картина:
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN 
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host 
valid_lft forever preferred_lft forever
2: venet0: <BROADCAST,POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN 
link/void 
inet 127.0.0.1/32 scope host venet0
inet 192.168.1.202/32 brd 192.168.1.202 scope global venet0:0
3: tunl0: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN 
link/ / ip 0.0.0.0 brd 0.0.0.0
inet 192.168.1.100/32 brd 192.168.1.100 scope global tunl0

cat /proc/sys/net/ipv4/conf/tunl0/.
0



А принцеса захована в venet, як це не сумно. Технологія цього пристрою накладає наступні обмеження:
Venet drop ip-packets from the container with a source address, and in the container with the destination address, which is not corresponding to an ip address of the container.
Тобто наша нода не приймає пакети, які йдуть з лівим сорсом. І тепер головний милицю — додамо цю адресу контейнеру! Нехай їх буде дві адреси у кожної машинки!
Ілюстрація такого рішення

На ноди виконаємо:
vzctl set 201 --ipadd 192.168.1.100 --save
vzctl set 202 --ipadd 192.168.1.100 --save
і тут же на обох ноди
ip ro del 192.168.1.100 dev venet0 scope link

Отримаємо звичайно ж варнинг, що така адреса вже існує в сітці! Але балансування вимагає жертв.
Навіщо нам видаляти роуты — щоб ми не вели мовлення в мережу ця адреса та інші машини про нього не знали. Тобто формально всі вимоги дотримані — відповідь з машинки йде з адресою 192.168.1.100, таку адресу у неї є. Працюємо!
Для спрощення роботи хочу порекомендувати механізм mount скриптів OpenVZ, але в чистому вигляді він нам не допоможе, т. к. рауса адрес додається після операції mount, а скрипти start виконуються всередині контейнера.
Рішення прийшло з форуму OpenVZ
Робимо два файли (приклад для одного контейнера):
cat /etc/vz/conf/202.mount

#!/bin/bash
. /etc/vz/start_stript/202.sh &
disown 
exit 0

cat /etc/vz/start_stript/202.sh

#!/bin/bash
_sleep() {
sleep 4
status=(`/usr/sbin/vzctl status 202`)
x=1
until [ $x == 6 ] ; do
sleep 1
if [ ${status[4]} == "running" ] ; then
ip ro del 192.168.1.100 dev venet0 scope link
exit 0
else
x=`expr $x + 1`
fi
done
}
_sleep

І робимо виконуваними:
chmod +x /etc/vz/start_stript/202.sh
chmod +x /etc/vz/conf/202.mount


Рестартим контейнер для перевірки, і тепер настала мить — відкриваємо 192.168.1.100 иииии… ПЕРЕМОГА!

Ще кілька коротких нотаток:
1) найстрашніше, що буває з цієї балансуванням — коли адреса, дбайливо повішений всередину контейнера або на lo (для режиму роботи Direct) починає віщати в мережу. Для запобігання цього сценарію вам допоможе два інструменти — тести налаштувань і arptables. Інструмент схожий з iptables, але для ARP запитів. Я активно ним користуються для своїх цілей — ми забороняємо певним арпам потрапляти в мережу.
2) Дане рішення не рівня Enterprise, т. к. рясніє милицями і вузькими місцями. Якщо у вас є можливість — використовувати NAT, Direct і тільки потім Tunnel. Це пов'язано з тим, що, наприклад, в Direct — якщо бекенд активний виведення ipvsadm, то вам трафік він отримає. Тут же він може його і не отримати, хоча порт вважається доступним і пакети туди полетять.
4) В нормальній віртуалізації (KVM, VmWare та інші) — проблем не виникне, як і не виникне з використанням veth пристроїв.
5) Для діагностики будь-яких проблем з LVS — використовуйте tcpdump. І просто так теж використовуйте :)

Спасибі за увагу!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.