Как работают сети, часть 2: отказоустойчивость с teaming, режем соединения с Traffic Control, а так же tap-интерфейсы и Linux Bridge

Illustration of a person in a business suit opening their shirt to reveal a superhero costume underneath, alluding to a secret identity. Illustration of a person in a business suit opening their shirt to reveal a superhero costume underneath, alluding to a secret identity.

В статье Как работают сети: что такое свитч, роутер, DNS, DHCP, NAT, VPN и ещё с десяток необходимых вещей я рассказал про основные компоненты компьютерных сетей. Её прочтение необходимо перед тем, как браться за текст ниже.

За основу статьи взята настройка teaming в Linux. Настраивать его мы будем в виртуальном окружении, а значит, нам понадобится libvirt, который мы изучили в статье Основы виртуализации и введение в KVM.

На этот раз мы не будем вслепую полагаться на работу libvirt. Вместо этого мы попытаемся понять, что кроется за парой строчек простого XML-файлика, определяющего виртуалку или сети. В результате мы узнаем, как работают и для чего нужны Linux bridge, tap-интерфейсы и Linux Traffic Control, и как они используются для виртуализации.

Начнём мы с того, что такое teaming.

Зачем нужны teaming, bonding, link aggregation и port trunking?

Если не погружаться в полемику, рассмотр конкретной технической реализации и набора фич, то термины teaming, bonding, link aggreation и port trunking означают одно и то же: объединение нескольких физических сетевых интерфейсов в один логический, с целью увеличить пропускную способность и/или обеспечить отказоустойчивость.

Мы будем использовать термин "teaming", потому что именно он используется в современных весиях Linux.

Помимо teaming, в Linux есть bonding, на замену которому и пришёл teaming. Чем в контексте Linux teaming отличается от bonding можно почитать в документации от Red Hat — согласно табличке по ссылке, использовать teaming в новых окружениях особого смысла нет, но здесь я могу ошибаться. Максимально любопытному читателю предлагается прочитать документацию по bonding — мало ли, вдруг придётся работать именно с ним.

Я не буду погружаться в миллион деталей касательно того, как именно работает teaming. Хочешь ли ты уже сейчас знать, что teaming реализован в виде маленького шустрого драйвера для ядра Linux и набора пользовательских API для пущей гибкости и расширяемости? Наверное, нет. Но когда такое желание появится, в конце статьи будет восхитительный список ссылок, в том числе на подробнейшее описание внутреннего строения teaming в Linux.

Теперь разберёмся, что значит "увеличить пропускную способность" и "обеспечить отказоустойчивость".

Допустим, у сервера один NIC. Если он сгорит, то сервер останется без сети. А если у сервера два NIC, то сервер будет видеть их как два разных NIC, с двумя разными IP-адресами. В результате, одно и тоже соединение знает только об одном интерфейсе. Если сгорит один из двух, то все соединения, проходящие через сгоревший NIC, пропадут без шанса на реабилитацию.

А вот если использовать teaming, оба NIC будут спрятаны за одним программным интерфейсом, за который и отвечает teaming. Благодаря этому, если один из NIC сгорит или, например, его вырвут из сервера, то никакого перебоя в связи не будет, так как трафик автоматически пойдёт через оставшийся в живых интерфейс. С отказоустойчивостью разобрались.

Linux Teaming и отказоустойчивость

Теперь допустим, что у нас есть сервер на котором хранится куча файлов, которые постоянно скачивает кто угодно из локальной сети. Если в этот файловый сервер воткнут один NIC и два других сервера будут пытаться скачивать файлы одновременно, они будут делить пропускную способность этого NIC пополам. А если в файловом сервере будет два NIC, объединённых при помощи teaming, то каждый из скачивающих серверов будет наслаждаться максимальной (для одного NIC) скоростью скачивания.

Спускаемся с небес на землю

Очень важно сделать отступление и разъяснить пару моментов.

Во-первых, если сервер с одним NIC будет пытаться скачать файл с сервера, в котором два NIC, то скорость скачивания не удвоится — потому что скорость будет ограничена пропускной способностью NIC скачивающего сервера. Другое дело, если в оба сервера воткнуть по два NIC.

Во-вторых, если в сервер воткнуть два NIC и попытаться скачать большой файл, то от наличия второго NIC скорость скачивания не удвоится (но слегка увеличится, возможно). Преимущество будет во время параллельного скачивания нескольких файлов — тогда трафик пойдёт как надо, и оба файла будут качаться, теоретически, с максимальной скоростью, доступной каждому из NIC.

В-третьих, наличие teaming не решает все проблемы с отказоустойчивостью. Не забываем, что NIC подсоединены к роутеру (или свитчу, или к мосту, или к хабу). И если сгорит роутер (или свитч, или мост, или хаб), то даже пять NIC на сервере не спасут от падения сети. Поэтому нужно думать ещё и об отказоустойчивости роутеров (а так же свитчей, мостов и хабов).

Проверяем работу teaming

Теперь мы знаем, зачем нужен teaming. Проверять его работу в реальных условиях проблематично — нужны железки и провода. У меня под рукой есть только ноутбук с одним NIC, а значит, чтобы провести интересные тесты, придётся воспользоваться виртуализацией. Напомню, что максимально желательно заранее прочитать нашу статью про виртуализацию, чтобы не только разобраться в теме, но и настроить локальное окружение для работы с libvirt + KVM.

Запускаем виртуальную машину с CentOS через libvirt

  1. Скачиваем Centos iso для KVM

  2. Скачиваем к себе Kickstart файлик

  3. В файлике заменяем в самом конце PUBLIC_KEY на свой публичный SSH ключик.

  4. Запускаем новую виртуалку этой командой (не забудь обновить путь до Kickstart файла и до образа с Centos):

sudo virt-install —name teaming —ram 2048 —disk size=8,format=qcow2 —vcpus 2 \
       —location /tmp/CentOS-7-x86_64-Minimal-1708.iso \
       —initrd-inject /path/to/ centos-cfg-minimal.ks  \
       —virt-type kvm \
       —extra-args ks=file:/ centos-cfg-minimal.ks

Размер диска и ОЗУ на твоё усмотрение.

Ищем IP адрес при помощи sudo virsh net-dhcp-leases default и пытаемся зайти по SSH root пользователем. Получилось? Идём дальше.

Изучаем Linux Bridge

Все ресурсы, управляемые libvirt — виртуалки, сети, хранилища — описаны в XML файлах. Внося изменения в эти файлы, можно менять состояние ресурсов. Например, можно изменить настройки сетевого интерфейса нашей виртуалки.

Откроем XML виртуалки:

virsh edit teaming

И найдём там определение сетевого интерфейса:

...
<interface type='network'>
  <mac address='52:54:00:95:3f:7b'/>
  <source network='default'/>
  <model type='virtio'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
...

Здесь мы видим, в какую сеть "воткнут" этот интерфейс (а правильнее: в свитч какой сети) — <source network='default'/>, видим (и можем поменять) MAC-адрес этого интерфейса и остальные его параметры.

Убедимся в том, что этот интерфейс и правда воткнут в виртуальный свитч, из которого и состоит сеть libvirt. Чтобы узнать, какой именно свитч используется для сети мы можем посмотреть XML определение самой сети:

virsh net-dumpxml default

Получаем:

<network connections='1'>
  <name>default</name>
  <uuid>c6d5c8b9-3dbe-4be5-9ef9-25afce83e699</uuid>
  <forward mode='nat'>
    <nat>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='virbr0' stp='on' delay='0'/>
  <mac address='52:54:00:7e:33:23'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>

Вот определение свитча: <bridge name='virbr0' stp='on' delay='0'/>. Теперь мы знаем, что он называется virbr0. При помощи команды brctl посмотрим, какие интерфейсы к нему подключены:

brctl show virbr0
bridge name    bridge id            STP enabled    interfaces
virbr0         8000.5254007e3323    yes            virbr0-nic
                                                   vnet0

Два интерфейса — один принадлежит хосту, а другой нашей виртуалке (vnet0). У интерфейса хоста есть IP адрес внутри сети libvirt (192.168.122.1/24), и через этот интерфейс идёт весь трафик виртуалок во внешний мир. Ты можешь сам убедиться в этом, поигравшись с командами ip r и ip addr на хосте и внутри виртуалки, а также посмотрев результат traceroute до соседней виртуалки и, например, до mkdev.me.

Теперь посмотрим какие MAC-адреса у подсоединённых интерфейсов:

brctl showmacs virbr0
port no    mac addr        is local?    ageing timer
  1    52:54:00:7e:33:23    yes           0.00
  1    52:54:00:7e:33:23    yes           0.00
  2    52:54:00:95:3f:7b    no         126.91
  2    fe:54:00:95:3f:7b    yes           0.00
  2    fe:54:00:95:3f:7b    yes           0.00

Результат, конечно, сбивает с толку. Почему адрес NIC хоста выводится дважды, а NIC виртуалки — трижды? И почему в одном случае NIC виртуалки начинается с "52", а в двух других — с "fe"? И почему в первом случае "is local?" равно "no" и выводится какой-то ageing timer? Сейчас попробуем разобраться.

Чтобы понять, почему MAC-адреса дублируются, нам понадобится другая утилита для работы с Linux Bridge — bridge. С её помощью мы сможем посмотреть Forwarding Database (fdb). fdb — это, грубо говоря, таблица маршрутизации на втором уровне сети. Обычно мы имеем дело с маршрутизацией на третьем уровне, или, если так понятней, на уровне IP-адресов. Но свитч чаще всего относится ко второму уровню и оперирует MAC адресами. Посмотрим fdb для моста virbr0:

bridge fdb show br virbr0
01:00:5e:00:00:01 dev virbr0 self permanent
01:00:5e:00:00:fb dev virbr0 self permanent
52:54:00:7e:33:23 dev virbr0-nic vlan 1 master virbr0 permanent
52:54:00:7e:33:23 dev virbr0-nic master virbr0 permanent
fe:54:00:95:3f:7b dev vnet3 vlan 1 master virbr0 permanent
52:54:00:95:3f:7b dev vnet3 master virbr0
fe:54:00:95:3f:7b dev vnet3 master virbr0 permanent
33:33:00:00:00:01 dev vnet3 self permanent
01:00:5e:00:00:01 dev vnet3 self permanent
33:33:ff:95:3f:7b dev vnet3 self permanent
33:33:00:00:00:fb dev vnet3 self permanent

Мы проигнорируем 01:00:5e:00:00:01, 01:00:5e:00:00:fb, а также все строки, начинающиеся с 33:33. В отдельной статье я расскажу про IPv6 и чем он отличается от IPv4, после чего станет понятно, зачем нужны эти бесконечно дублирующиеся записи. Пока что сделаем вид, что их вовсе нет:

52:54:00:7e:33:23 dev virbr0-nic vlan 1 master virbr0 permanent
52:54:00:7e:33:23 dev virbr0-nic master virbr0 permanent
fe:54:00:95:3f:7b dev vnet3 vlan 1 master virbr0 permanent
52:54:00:95:3f:7b dev vnet3 master virbr0
fe:54:00:95:3f:7b dev vnet3 master virbr0 permanent

Так же выкинем строчки, ответственные за сам свитч, оставив только записи, релевантные для нашей виртуалки:

52:54:00:95:3f:7b dev vnet3 master virbr0
fe:54:00:95:3f:7b dev vnet3 vlan 1 master virbr0 permanent
fe:54:00:95:3f:7b dev vnet3 master virbr0 permanent

У Linux Bridge (да и не только у него) есть способность добавлять записи в fdb динамически, "обучаясь" на проходящем трафике. Пришли фреймы от MAC-адреса 52:54:00:95:3f:7b? Добавим его автоматом, судя по всему он подключён к свитчу. А если от него долго не будет вестей, то так же автоматом и удалим. Именно это и означают колонки "is local?" и "ageing" — первая показывает, настроен ли интерфейс в свитче статически (~= воткнут в него напрямую), а вторая показывает сколько прошло времени с последнего контакта с этим интерфейсом.

Команда bridge fdb не показывает ageing, но мы видим маршрут, который был автоматически добавлен:

52:54:00:95:3f:7b dev vnet3 master virbr0

В случае статически настроенных интерфейсов ageing не имеет смысла, потому что свитч всегда знает, что они подключены. За подключение интерфейсов виртуалки к свитчу отвечает libvirt (а на самом деле — qemu), поэтому особого смысла в динамическом добавлении маршрутов нет — свитч и так знает всё, что нужно про соединение с виртуалкой. Поэтому мы можем вырубить learning полностью:

brctl setageing virbr0 0

Теперь у нас всего две записи для интерфейса виртуалки:

port no    mac addr        is local?    ageing timer
  1    52:54:00:7e:33:23    yes           0.00
  1    52:54:00:7e:33:23    yes           0.00
  2    fe:54:00:95:3f:7b    yes           0.00
  2    fe:54:00:95:3f:7b    yes           0.00

Внимательный читатель уже давно кричит в экран: "почему MAC-адрес виртуалки и MAC-адрес в свитче разные???". Это отличный вопрос.

Как убедиться, что libvirt и правда использует QEMU? Заходим в /var/log/libvirt/qemu, ищем лог с названием виртуалки и видим примерно такое.

tap-интерфейсы

И правда, если зайти в виртуалку и посмотреть MAC-адрес её NIC, мы увидим 52:54:00:95:3f:7b, в то время как brctl showmacs показывает fe:54:00:95:3f:7b. Разница лишь в первых двух символах — fe вместо 52. Дело в том, что для каждой виртуалки libvirt создаёт на хосте специальный интерфейс — tap-интерфейс.

tap-интерфейс — это такой виртуальный интерфейс, работающий на уровне ядра Linux. Вместо того, чтобы принимать и передавать данные на физическом уровне, tap-интерфейс получает данные программно и программно же их передаёт.

Если сильно упрощать, то tap-интерфейс — это файловый дескриптор, в который, в нашем случае, Linux bridge отправляет Ethernet фреймы. QEMU считывает их из этого дескриптора и отправляет в виртуалку.

Не знаешь, что такое файловый дескриптор? Не беда. Мы об этом расскажем в отдельной статье. Подпишись на нашу рассылку (форма внизу), чтобы узнать о её появлении первым.

Для виртуалки это выглядит так, будто бы фреймы пришли на физический интерфейс — тот самый, у которого MAC-адрес 52:54:00:95:3f:7b. Это очень важно: tap-интерфейс — это реализация виртуализации сетевого интерфейса, и виртуалка ничего не знает об этой реализации. Виртуалка считает, что у неё полноценный физический интерфейс.

Нам сейчас не очень важно, как именно и когда создаётся tap-интерфейс. libvirt на то и существует, чтобы спрятать от нас множество мелких деталей до тех пор, пока у нас не появится необходимость в них разбираться. Тем не менее, давай выполним на хосте пару команд, просто чтобы знать, куда смотреть в будущем.

Во-первых, давай глянем полный конфиг виртуалки. Когда мы смотрели интерфейс виртуалки через virsh edit, мы наблюдали статичный постоянный конфиг. Но конфиг уже запущенной виртуалки слегка отличается, так как в процессе запуска libvirt делает разные полезные штуки — например, создаёт tap-интерфейс и связь между ним и виртуалкой. Давай посмотрим, как выглядит конфиг интерфейса уже запущенной виртуалки:

virsh dumpxml teaming
...
<interface type='network'>
  <mac address='52:54:00:95:3f:7b'/>
  <source network='default' bridge='virbr0'/>
  <target dev='vnet0'/>
  <model type='virtio'/>
  <alias name='net0'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
...

Здесь уже виден наш tap-интерфейс: <target dev='vnet0' />. tap-интерфейсы существуют на хосте, и мы можем посмотреть их список командой ip tuntap:

virbr0-nic: tap UNKNOWN_FLAGS:800
vnet0: tap vnet_hdr

И так как это полноценный (хоть и виртуальный) интерфейс, мы увидим его в списке интерфейсов хоста:

ip link
11: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:7e:33:23 brd ff:ff:ff:ff:ff:ff
12: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc fq_codel master virbr0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 52:54:00:7e:33:23 brd ff:ff:ff:ff:ff:ff
62: vnet0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master virbr0 state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether fe:54:00:95:3f:7b brd ff:ff:ff:ff:ff:ff

В конце будет пара ссылок на статьи, в которых можно узнать больше про tap и tun. tun — это как tap, только не на втором уровне сети, а на третьем.

Как ограничить скорость интернета в libvirt?

Продемонстрировать отказоустойчивость сервера с настроенным teaming будет весьма несложно. Но вот как показать повышенную пропускную способность?

Откроем редактирование XML-определения виртуалки: virsh edit teaming, найдём там определение интерфейса виртуалки и добавим блок <bandwidth>:

<interface type='network'>
  <mac address='52:54:00:95:3f:7b'/>
  <source network='default'/>
  <model type='virtio'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
  <bandwidth>
    <inbound average='128'/>
    <outbound average='128'/>
  </bandwidth>
</interface>

Теперь скорость соединения будет ограничена 128kbs. Мы можем задать скорость скачивания и прочие детали при помощи различных атрибутов как для входящих, так и для исходящих соединений. О всех доступных опциях лучше всего читать в документации libvirt. А пока проверим, что ограничение и правда действует. Сначала перезапустим виртуалку:

virsh destroy teaming && virsh start teaming

Зайдём туда по SSH и попробуем скачать какой-нибудь файл через, например, wget:

wget http://centos.schlundtech.de/7/isos/x86_64/CentOS-7-x86_64-Minimal-1708.iso

Смотрим на процесс скачивания и видим, что скорость и правда ограничена. Но как это работает?

Что такое Linux Traffic Control?

Traffic control позволяет детально управлять пакетами, которые проходят через сетевое устройство. Можно указать скорость обработки очереди пакетов, можно фильтровать и отмечать пакеты и выкидывать их, если они не соответствуют нашим требования. При желании можно сделать так, чтобы, например, у SSH соединения сохранялась всегда одна и та же скорость, даже если через тот же интерфейс идёт скачивание крупных файлов. В общем, полный набор инструментов для работы с сетевыми пакетами — была б фантазия для чего их применить.

Quality of Service (QoS) — синоним Traffic Control.

Как и все хорошие вещи в этой жизни, управление сетевыми пакетами проходит в ядре Linux — через packet scheduler. А чтобы настроить его работу, существует утилита tc. Traffic Control в Linux — штука очень гибкая, комплексная, мощная. Постараемся рассмотреть основные компоненты контроля трафика, а затем посмотрим как их настроил libvirt.

Контроль трафика основан на старых добрых очередях, обработка которых контролируется через Queueing Disciplines (qdisc). У каждого сетевого интерфейса есть одна очередь по-умолчанию, к которой не применяется никаких особенных правил — пакеты входят в очередь, а затем достаются оттуда как можно быстрее. Если в сети есть неполадки, то пакеты могут какое-то время проваляться в очереди — таким образом избежав утраты данных при передаче.

Является ли qdisc очередью? После изучения многих источников, можно сказать, что термины "queue" и "qdisc" взаимозаменяют друг друга. В основе всего лежит очередь как структура данных, а qdisc — это некие правила обработки этой очереди.

Но от одной FIFO очереди толку мало. Поэтому Linux позволяет строить сложные деревья из очередей, с разными правилами обработки пакетов от разных источников, для разных портов.

qdisc's бывают двух типов — без классов (classless qdisc) и с классами (classful qdisc). qdisc's без классов просто пропускают через себя трафик (с какой-нибудь обработкой, типа добавления задержки выпуска пакета из очереди). qdisc с классами содержат в себе другие очереди и классы, и как правило фильтруют пакеты и распихивают их по нескольким очередям сразу.

Не волнуйся, если не до конца поймёшь всё, что мы увидим дальше. Внимательно изучи материалы по Linux Traffic Control в конце статьи, а затем вернись к примерам выше — тогда всё станет ясно.

Что нас интересует сейчас, так это взаимосвязь между <bandwith> в определении интерфейса виртуалки и настройками контроля траффика на хосте. Как я уже сказал, за настройку отвечает утилита tc, при помощи которой мы можем легко увидеть, что именно libvirt нам настроил:

tc qdisc show dev vnet0
qdisc htb 1: root refcnt 2 r2q 10 default 1 direct_packets_stat 0 direct_qlen 1000
qdisc sfq 2: parent 1:1 limit 127p quantum 1514b depth 127 divisor 1024 perturb 10sec
qdisc ingress ffff: parent ffff:fff1 ————————

У нашего tap-интерфейса три qdisc. У каждого интерфейса есть две "ненастоящие" qdisc — root и ingress. Ненастоящие они из-за того, что сами по себе они ничего не делают, а являются точками к которым подключаются другие qdisc.

При этом, к ingress нельзя прикрепить ничего, кроме фильтрации пакетов и опционального отказа от пакета. Реализовано это так, потому что у нас нет настоящего контроля за входящими данными — мы не можем влиять на уровне OS на скорость входящего трафика. В том числе поэтому не существует настоящей защиты от DDoS. Всё, что мы можем — это как можно быстрее и раньше блокировать трафик или помечать его для последующей обработки — именно этим и занимается ingress qdisc, которая стоит в самом начале обработки входящего трафика. Мы не добавляли никакой фильтрации для входящего трафика, поэтому ingress qdisc выглядит скучно.

Две остальные qdisc связаны друг с другом. Во главе стоит qdisc htb 1:, родителем которого является root. Ниже неё по дереву находится qdisc sfq 2: — это видно по parent 1:1. htb и sfq — это отдельные типы очередей, встроенные в Linux. По ссылкам объяснение их работы вместе с картинками. Если вкратце, то htb позволяет ограничивать скорость трафика, а sfq пытается равномерно распределить выходящие пакеты. При этом htb — это classful qdisc, а sfq — classless (man tc докажет).

Теперь глянем классы:

tc -g class show dev vnet0
+—-(1:1) htb prio 0 rate 1024Kbit ceil 1024Kbit burst 1599b cburst 1599b

Вот он, наш ограничитель скорости — мы видим те самые заветные 1024Kbit.

И снова — libvirt спрятал от нас сложную и дико полезную часть Linux, позволив несколькими человекопонятными строчками XML настроить Linux packet scheduler для tap-интерфейса виртуальной машины.

Добавляем второй интерфейс

Пора перейти к teaming. Но для этого нам нужен второй интерфейс, который очень легко добавить при помощи virsh:

virsh attach-interface teaming network default —persistent

Ты можешь убедиться в наличии нового интерфейса либо посмотрев XML виртуалки, либо зайдя в неё и выполнив ip addr, либо выполнив ту же самую команду на хосте, на котором появился новый tap-интерфейс. DHCP-сервер даже любезно выдал новому интерфейсу IP-адрес — хоть он нам и не нужен.

Время настроить teaming!

Устанавливаем и настраиваем teaming

Заходим по SSH (или при помощи Spice) в виртуалку и отключаем одно из соединений:

nmcli con
NAME                UUID                                  TYPE            DEVICE
Wired connection 1  31e82585-4c0c-4a30-ba4c-622d66a48e68  802-3-ethernet  ens9
eth0                b21fbb15-ae30-4a65-9ec3-37a6b202ab5d  802-3-ethernet  eth0
nmcli con delete 'Wired connection 1'

Если ты зашёл в виртуалку по SSH через IP-адрес интерфейса, соединение которого выключил, то тебе придётся зайти заново — ведь teaming ещё не настроен.

Ставим teamd: yum install teamd NetworkManager-team -y. Подгружаем модуль: modprobe team. Убеждаемся, что всё в порядке: lsmod | grep team.

У меня после установки NetworkManager-team вылеза ошибка Error: Could not create NMClient object: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: Method "GetManagedObjects" with signature "" on interface "org.freedesktop.DBus.ObjectManager" doesn't exist. Ребут помог.

Дальше у нас есть на выбор несколько вариантов настройки teaming:

  1. Путём ручного редактирования network-scripts
  2. При помощи nmcli - здесь нам пригодится man nmcli-examples
  3. При помощи nmtui

Первые два способа вынуждают нас запоминать разные последовательности команд, в то время как текстовый интерфейс nmtui позволит сделать всё быстро и просто. Но быстро и просто можно сделать и самому, поэтому посмотрим, как сделать всё через nmcli:

$> nmcli con add type team ifname team0 con-name team0
Connection 'team0' (e6d71407-0c9f-4871-b6d4-60f62124809e) successfully added.
$> nmcli con add type team-slave ifname ens9 con-name team0-slave0 master team0
Connection 'team0-slave0' (b8dbe2f1-7d93-4c86-9c62-8470d44e1caf) successfully added.

Теперь у нас есть team интерфейс с одним настоящим интерфейсом и IP-адресом. Теперь, если ты зашёл в виртуалку по SSH, выйди и зайди снова используя IP team интерфейса — иначе потерешь соединения, выполняя следующую команду:

nmcli con delete eth0

Нам нужно убрать любые настроенные соединения у используемых для team интерфейсов. Добавляем второй интерфейс:

nmcli con add type team-slave ifname eth0 con-name team0-slave1 master team0
Connection 'team0-slave1' (d11ba15b-d13d-4605-b958-fe6d093d4417) successfully added.

Посмотрим на наш новый интерфейс и его адрес командой ip addr:

ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master team0 state UP qlen 1000
    link/ether 52:54:00:54:f4:75 brd ff:ff:ff:ff:ff:ff
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master team0 state UP qlen 1000
    link/ether 52:54:00:54:f4:75 brd ff:ff:ff:ff:ff:ff
4: team0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 52:54:00:54:f4:75 brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.71/16 brd 192.168.255.255 scope global dynamic team0
       valid_lft 2859sec preferred_lft 2859sec
    inet6 fe80::d865:f90b:50c3:e3d0/64 scope link
       valid_lft forever preferred_lft forever

team0 выводится точно так же, как любой другой интерфейс, у него есть IP-адрес, MAC-адрес и даже свой qdisc. Мы уже немного знаем про Traffic Control, поэтому строчки типа qdisc pfifo_fast и qlen 1000 нас не удивляют. Ещё обратим внимание на то, что у все трёх интерфейсов теперь одинаковые MAC-адреса — это поведение по-умолчанию, но его можно изменить при желании.

Теперь немного поиграем с настройками нашего team. Teaming поддерживает разные режимы работы, называемые runners. Самый простой — это roundrobin. Из названия понятно, что интерфейсы, в которые будут идти пакеты, будут обрабатываться по кругу. Есть ещё, например, activebackup — в этом случае используется только один интерфейс, и если он упадёт, трафик автоматически пойдёт на следующий доступный. man teamd.conf описывает

остальные доступные режимы. Примеры определения runners можно глянуть /usr/share/doc/teamd/example_configs/ (если такой папки нет, то попробуй locate example_configs). Мы же попробуем activebackup.

Цель упражнения такая: настроить activebackup, в котором новый интерфейс будет основным, а медленный интерфейс (ограниченный при помощи tc) — бэкапом. А затем мы запустим в виртуалке скачку крупного файла и "выдернем" новый, быстрый интерфейс из сервера.

nmcli con edit team0
nmcli> describe team.config

Последняя команда покажет примеры использования опции team.config:

Examples: set team.config { "device": "team0", "runner": {"name": "roundrobin"}, "ports": {"eth1": {}, "eth2": {}} }
          set team.config /etc/my-team.conf

При грамотном использовании man и прочих доступных в системе информационных средств нам не нужно будет хаотично гуглить что попало. Мы можем просто внимательно изучить локальную документацию и найти подробнейшие описания с кучей примеров! Ещё один пример конфигурации мы может обнаружить в man teamd.conf:

{
  "device": "team0",
  "runner": {"name": "activebackup"},
  "link_watch": {"name": "ethtool"},
  "ports": {
    "eth1": {
      "prio": -10,
      "sticky": true
    },
    "eth2": {
      "prio": 100
    }
  }
}

Создадим файлик teaming-demo.conf с похожим содержимым, но обновленным для наших интерфейсов:

{
  "device": "team0",
  "runner": {"name": "activebackup"},
  "link_watch": {"name": "ethtool"},
  "ports": {
    "ens9": {
      "prio": 100
    },
    "eth0": {
      "prio": 10
    }
  }
}

Здесь вы задали режим activebackup и выдали интерфейсу ens9 (быстрому) больший приоритет. Подгрузим эти настройки:

nmcli con mod team0 team.config ./teaming-demo.conf

При помощи teamdctl team0 state убедимся, что настройки применились:

teamdctl team0 state
setup:
  runner: roundrobin
ports:
  ens9
    link watches:
      link summary: up
      instance[link_watch_0]:
        name: ethtool
        link: up
        down count: 0
  eth0
    link watches:
      link summary: up
      instance[link_watch_0]:
        name: ethtool
        link: up
        down count: 0

Упс, ещё не применились! Дело в том, что конфиг загружается при создании team-интерфейса. Воспользуемся этим как возможностью проверить, выдержат ли наши настройки ребут, и перезапустим виртуалку. Если всё прошло успешно, то после перезапуска мы сможем зайти в виртуалку по SSH, выполнить teamdctl team0 state и увидеть такое:

setup:
  runner: activebackup
ports:
  ens9
    link watches:
      link summary: up
      instance[link_watch_0]:
        name: ethtool
        link: up
        down count: 0
  eth0
    link watches:
      link summary: up
      instance[link_watch_0]:
        name: ethtool
        link: up
        down count: 0
runner:
  active port: ens9

Время вырвать интерфейс!

Проверяем работу teaming

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

Пара слов о правильной автоматизации

Прежде чем перейти к итогам, снова поговорим о реальной жизни. Пара вещей, которые нужно держать в голове:

  • Руками teaming настраивать каждый раз необязательно. Его можно настроить, например, при помощи Kickstart
  • nmcli, tc и другие утилиты очень полезны для быстрой настройки и дебага, но в реальном окружении нужно всё автоматизировать при помощи подходящих инструментов — например, Chef/Puppet/Ansible.

Подводим итоги

Изначально я думал, что это будет небольшая статья про teaming в Linux. Но как только я начал её писать, сразу понял, что придётся рассказать о многих других вещах.

Мы только что вживую потрогали свитчи, погрузившись в такие детали, как fdb.

Мы узнали, что такое tap-интерфейсы, и какую роль они играют в виртуализации сетей в Linux.

Мы посмотрели Linux Traffic Control, и теперь мы знаем чуть больше о Linux networking stack. Теоретически, вооружившись tc мы теперь сможем выстраивать очень хитрые цепочки обработки сетевого трафика.

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

Всю дорогу мы использовали libvirt и теперь знаем, насколько сильно он облегчает работу. За парой строчек XML-конфигурации скрывается сложная и гибкая настройка множества встроенных в Linux инструментов, работающих на самых разных уровнях. И в сумме эти инструменты и предоставляют виртуализацию, на которой, напомню, построены все современные облачные платформы.

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

Дополнительное чтение

В ходе подготовки к этой статьи были прочитаны сотни статей, страниц документации и отрывков книг. Мы покрыли множество разных тем, но ни в одну не копнули по-настоящему глубоко. Ниже как раз ссылки для углублённого изучения материала:

  • man tc, man teamd.config, man bridge, man brctl - в первую очередь лучше смотреть документацию, которая уже доступна на машине.
  • teamd infrastructure specification расскажет о внутреннем устройстве teaming в Linux простыми словами создателей teamd
  • Configure Network Teaming — документация по teaming от RedHat
  • Linux Bridge and Virtual Networking — в этой статье автор показывает, как создать Linux bridge и подключить к нему пару интерфейсов самостоятельно, без помощи libvirt.
  • Tap Interfaces and Linux Bridge — тот же автор, но на этот раз про tap-интерфейсы, в картинках и с очень хорошими объяснениями
  • Tun/Tap interface tutorial — статья 2010 года, в которой показано создание новых интерфейсов кодом на C
  • QEMU Networking — хорошие объяснения различных видов сетей в QEMU
  • VLAN filter support on bridge — анонс поддержки VLAN для Linux Bridge
  • Proper isolation of Linux bridge — о практическом применение VLAN в Linux Bridge, очень доступно и в картинках
  • Linux Advanced Routing & Traffic ControlHOWTO — огромная мини-книжка про Traffic Control , с очень доступными объяснениями и иллюстрациями (в виде ASCII,правда)
  • Differentiated Service on Linux HOWTO — иллюстрированное объеснение почти всех нужных типов qdisc. Посмотри там про HTB и sfq, чтобы мгновенно понять, как они работают.