LXC на службу QoS (замінюємо ifb на veth)

Напевно кожен, хто налаштовував пріоритет трафіку в Linux стикався з тим, що можливості ifb для управління вхідним трафіком досить мізерні. Далі я розповім про деяких типових проблем при побудові QoS, і про те, як ці проблеми вдалося вирішити з допомогою контейнерів.

Чому такі проблеми?
Так вийшло, що керувати швидкістю мережевого потоку ми можемо тільки напрямок «на вихід». Те, що вже прийшло до нас на інтерфейс, вже пройшло безліч вузьких місць, і здавалося б, немає сенсу відкидати або затримувати цей трафік. Але, так як більшість протоколів (TCP і ті, що побудовані поверх UDP, або мають свою реалізацію) мають механізми управління вихідним потоком, який може враховувати актуальну пропускну здатність клієнта, і змінювати швидкість відправлення. У такій ситуації має сенс управляти потоком, який йде до клієнта на нашій стороні. Механізмів реалізують це багато, я розгляну частина з них.

Типові ситуації і проблеми
По одному інтерфейсу в інтернет і в локальну мережу, ніяких тунелів


Найпростіша і безпроблемна ситуація. Тут і далі, ми приймемо як даність (для спрощення): сам шлюз трафік не генерує, він просто його передає.

Отже, тут все просто, навішуємо черзі на обидва інтерфейсу, і управляємо швидкістю до клієнтів (вхідна), на інтерфейсі, який і дивиться в локальну мережу (на його вихідному потоці). Виходить, відповідно, на зовнішньому інтерфейсі. Все працює, нічого цікавого, йдемо далі.

Додамо ще один внутрішній інтерфейс
Тепер не все так добре. Як поділити «вхідний» потік між внутрішніми мережами? Черги сусідніх інтерфейсів нічого не знають про реальну навантаження на канал. Тут можна поділити всю швидкість навпіл і віддати по половині на кожну мережу. У результаті друга мережа не користується каналом, а друга хоче максимум, то отримає лише половину від нього… Печаль, але нам допоможе ifb (або його майже померлий конкурент imq).

Ідея ifb полягає в тому, що ми розміщуємо псевдоинтерфейс перед нашим реальним, і починаємо таким чином керувати «вхідним» потоком реального інтерфейсу. На жаль, в цій бочці занадто багато дьогтю. Виявляється, ifb на стільки особливий, що механізми маркування та фільтрації утиліти iptables застосувати на ньому не можна, можна тільки з допомогою tc. Чим поганий же tc? А тим, що в плані фільтрації і маркування він вміє дуже мало і синтаксис вже зовсім інший.

Як би там не було погано з tc, поставлене завдання він зможе вирішити цілком непогано, поставимо його на вхідний інтерфейс, а правила з внутрішніх ми приберемо, вони там вже не потрібні.

Тепер з'єднаємо два офісу за допомогою тунелю
Ось тепер все стає зовсім погано. У нас з'являється новий інтерфейс, який працює як звичайний, але крім усього іншого, він «непомітно» використовує пропускну здатність іншого інтерфейсу.

Тепер ми не можемо ефективно розподілити трафік, ifb нам тут теж не допоможе. Залишається рішення в «лоб»: виділити для тунелю фіксовану смугу, і керувати вмістом тунелю як звичайним інтерфейсом (навісивши на нього ifb і звичайні черги).

Всі «не погано», тільки даремно можемо втрачати швидкість віддану в тунель або в звичайну мережу (як подивитися).

LXC нам допоможе!
Взагалі, головна думка в застосуванні netns, але застосувати контейнер простіше (хоч він і витрачає більше ресурсів, в основному дискового простору).

Отже: нам потрібна проміжна ланцюжок інтерфейсів між «зовнішніми» та «внутрішніми» інтерфейсами. Саме на цій ланцюжку можна з легкістю зробити QoS всього трафіку, і максимум, що ми від нього втратимо, це 4,5% (оверхед порахуйте самі, тут я врахував IPSec\GRE) на туннелировании та шифрування для гарантії, що всі отримають задану смугу, приймемо, що весь трафік який приходить\йде до нас, приходить з тунелю).

Думаю, що створити контейнер на LXC можуть всі, я розгляну тільки деякі зокрема, які нам можуть знадобиться.

Отже, в конфігурації контейнера нам знадобиться:

Додати наш реальний зовнішній інтерфейс в контейнер (ми «продавим» його в контейнер, таким чином він «зникне» з основного простору імен):

lxc.network.type = phys
lxc.network.flags = up
lxc.network.link = enp3s6

Або аналогічно для vlan:


lxc.network.type = vlan
lxc.network.flags = up
lxc.network.link = enp2s0
lxc.network.vlan.id = 603

Додамо інтерфейс, через який будемо зв'язуватися з основною системою (скрипт підніме інтерфейс в основній системі, конфігурацію треба задати заздалегідь):


lxc.network.type = veth
lxc.network.flags = up
lxc.network.veth.pair = route0
lxc.network.name = eth1
lxc.network.hwaddr = 02:b2:30:41:30:25
lxc.network.script.up = /usr/bin/nmcli up connection route0

Додамо автоматичний запуск і зупинку інтерфейсів основної системи, при запуску\зупинці контейнера:


lxc.hook.pre-start = /var/lib/lxc/route0/pre-start.sh
lxc.hook.post-stop = /var/lib/lxc/route0/post-stop.sh

/var/lib/lxc/route0/pre-start.sh

#!/bin/sh
/usr/bin/nmcli connection down vlan603 >/dev/null 2 > &1
exit 0


/var/lib/lxc/route0/post-stop.sh

#!/bin/sh
/usr/bin/nmcli up connection vlan603 >/dev/null 2 > &1
exit 0


Додамо можливість використовувати tun\tap інтерфейсів:


lxc.hook.autodev = /var/lib/lxc/route0/autodev.sh

/var/lib/lxc/route0/autodev.sh

#!/bin/bash
cd ${LXC_ROOTFS_MOUNT}/dev
mkdir net
mknod net/tun c 10 200
chmod 0666 net/tun


Дозволимо управлінням деякими параметрами маршрутизації (хто знає як зробити більш «вузькі» дозволу, прошу підказку):


lxc.mount.auto = proc:rw sys:ro

При необхідності, зробіть контейнер автозапускаемым.

В основній системі нам знадобиться профіль для інтерфейсу route0, який з'явиться при старті контейнера, я припускаю, що у вас використовується NetworkManager.

Що потрібно налаштувати ще:

  • Налаштувати в основній системі ip адреса інтерфейсу route0, наприклад: 192.168.20.22/30 і маршрут за замовчуванням 192.168.20.21
  • У контейнері треба налаштувати ip адреса інтерфейсу eth1, наприклад: 192.168.20.21/30 і маршрут до внутрішньої мережі 192.168.21.0/24 via 192.168.20.22
  • У контейнері треба налаштувати «зовнішній» інтерфейс, маскардинг і форвардінг.
  • В основній системі нам знадобиться швидше за все тільки форвардінг.
  • Все, що має ходити у зовнішній мережі (включаючи тунелі), розміщуємо в контейнері.
Тепер ми просто налаштуємо черзі на вихідних потоках інтерфейсів, що зв'язують основну систему з контенером. Тепер, проблеми з визначенням призначення трафіку немає, і ми можемо задіяти всю доступну смугу від нашого провайдера (з урахуванням % на оверхед від тунелів).

p.s. Епоха «тазиків», які під Linux роздають інтернет стрімко йде, вартість апаратних маршрутизаторів стає все привабливішою, а їх міць і гнучкість вже мало чим поступається «великим» братам, то ж Mikrotik вирішить цю «проблему» без особливих заморочок.
Джерело: Хабрахабр

0 коментарів

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