Когда пакеты выстраиваются в очередь на выходе интерфейса, порядок их передачи не случаен. Подсистема Traffic Control в ядре Linux даёт полный контроль над этим порядком, и от качества настройки зависит, будет ли канал справедливо делиться между сервисами или один тяжёлый поток поглотит всю полосу, увеличив задержки остальных в десятки раз. Тема кажется низкоуровневой, но на практике именно настройка очередей разделяет продакшн, где VoIP звучит чисто, и продакшн, где звонки рвутся при запуске бэкапа. Дисциплина очереди, qdisc, решает, кто попадает в линию первым, кто ждёт, а кто выбрасывается. В Linux за эту работу отвечает подсистема Traffic Control, и её центральный элемент, очередь с дисциплиной, бывает простой, как pfifo_fast, и сложной, как иерархия классов HTB с листьями fq_codel. От выбора и настройки дисциплины зависит, получит ли интерактивный SSH-сеанс приоритет перед фоновым бэкапом, уложится ли VoIP в задержку и не удушит ли один поток весь канал.
Разберём классификацию qdisc, иерархию классов, настройку HTB, борьбу с bufferbloat через fq_codel и Cake, а также работу с приоритетами на аппаратных очередях.
Классификация дисциплин и выбор по умолчанию
Дисциплины делятся на бесклассовые и классовые. Бесклассовые, pfifo_fast, bfifo, pfifo, sfq, fq_codel, cake, не знают о классах и работают с общим потоком пакетов. Классовые, htb, hfsc, prio, cbq, drr, строят дерево классов, в каждом из которых может жить своя дочерняя дисциплина, и пакеты распределяются по классам через фильтры. По умолчанию на Ethernet-интерфейсах стоит pfifo_fast: три полосы с приоритетами, где TOS-биты решают судьбу пакета, но на нагруженных каналах это приводит к bufferbloat, когда большие очереди забивают буфер и растут задержки всего трафика. Классическая картина: запускается rsync или бэкап, очередь драйвера наполняется пакетами по 1500 байт, ping на том же интерфейсе показывает задержку в сотни миллисекунд вместо единиц, а SSH-сессия начинает лагать. pfifo_fast не умеет отличать интерактивный трафик от потокового, и это его главное ограничение.
Посмотреть текущую дисциплину и статистику можно стандартными командами:
tc qdisc show dev eth0
tc -s qdisc show dev eth0
Вывод показывает тип, параметры и счётчики переданных и отброшенных пакетов, и по нему сразу видно, страдает ли очередь от переполнения.
Иерархия классов и фильтры
Классовая дисциплина требует трёх вещей: корневую дисциплину, классы внутри неё и фильтры, направляющие пакеты в классы. Корневая дисциплина создаётся командой:
tc qdisc add dev eth0 root handle 1: htb default 30
Здесь handle 1: задаёт идентификатор корня, default 30 говорит, куда попадут пакеты, не попавшие ни в один фильтр. Классы добавляются под корнем:
tc class add dev eth0 parent 1: classid 1:10 htb rate 100mbit ceil 100mbit
tc class add dev eth0 parent 1:10 classid 1:20 htb rate 50mbit ceil 80mbit
tc class add dev eth0 parent 1:10 classid 1:30 htb rate 30mbit ceil 100mbit
Класс 1:10 это корневой класс с полной полосой, 1:20 и 1:30 делят её между гарантированным rate и пределом ceil. Фильтры вешаются на корень и направляют трафик:
tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip dport 22 0xffff flowid 1:20
tc filter add dev eth0 protocol ip parent 1: prio 2 u32 match ip sport 80 0xffff flowid 1:30
Первый фильтр с приоритетом 1 забирает SSH в класс 1:20, второй с приоритетом 2 забирает веб в класс 1:30, всё остальное уходит в default 1:30. Порядок приоритетов фильтров важен: меньше число, выше приоритет, и первый сработавший забирает пакет.
HTB гарантии и пределы полосы
HTB, Hierarchical Token Bucket, стал стандартом де-факто для шейпинга. Механика проста: каждый класс имеет ведро токенов, токены пополняются со скоростью rate до предела ceil, пакет тратит токены по своему размеру, и если токенов нет, пакет ждёт в очереди класса. Ключевые параметры класса: rate гарантированная полоса, ceil максимальная полоса при заимствовании у родителя, burst и cburst размеры всплеска в байтах, quantum куант обслуживания за один проход, и prio приоритет заимствования. Настройка burst критична: слишком маленький режет пропускную способность на коротких пакетах, слишком большой размывает границы полосы. Рабочая формула для burst - не меньше MTU, а лучше в районе десятка килобайт для гигабитных каналов. Параметр quantum по умолчанию равен MTU, и на высокоскоростных интерфейсах его увеличение до 60000-120000 байт уменьшает накладные расходы на переключение между классами. Настройка burst критична: слишком маленький режет пропускную способность на коротких пакетах, слишком большой размывает границы полосы. Рабочая формула для burst - не меньше MTU, а лучше в районе десятка килобайт для гигабитных каналов.
Иерархия позволяет строить arbre приоритетов: корневой класс делит полосу между детьми пропорционально их rate, дети заимствуют у родителя до ceil, и заимствование работает только если у родителя есть свободные токены. Это даёт предсказуемое поведение: гарантированная полоса всегда доступна, избыток раздается честно, и ни один класс не может захлебнуть канал сверх своего ceil. Типичная схема для офисного шлюза: корень 1000mbit, дети - Voice 100mbit ceil 200mbit, Video 300mbit ceil 500mbit, Data 400mbit ceil 1000mbit, Management 50mbit ceil 100mbit, и default класс на остаток. Это даёт предсказуемое поведение: гарантированная полоса всегда доступна, избыток раздается честно, и ни один класс не может захлебнуть канал сверх своего ceil.
Bufferbloat борьба с задержкой через fq_codel
Проблема bufferbloat в том, что большие очереди в драйверах и оборудовании накапливают пакеты и растут задержки. fq_codel, Flow Queue Controlled Delay, решает её двумя механизмами. Первое, хеширование потоков: входящие пакеты распределяются по множеству подочередей по хешу пяти кортежей, и каждый поток получает свою очередь. Второе, управляемая задержка: codel следит за минимальной задержкой в очереди, и если минимум превышает целевое значение 5 миллисекунд в течение интервала, начинает выбрасывать пакеты с растущей частотой. Алгоритм не использует абсолютный размер очереди, а работает с временем пребывания пакета, что делает его независимым от полосы канала. Настройка целевой задержки target и интервала interval позволяет адаптировать поведение под специфику канала: на спутниковых ссылках target повышают до 20-50ms, в датацентрах оставляют 5ms. Результат: интерактивные тонкие потоки проходят мгновенно, толстые потоки теряют пакеты и снижают окно TCP, и задержка в очереди стабилизируется у целевого значения.
Настройка fq_codel как листа класса HTB:
tc qdisc add dev eth0 parent 1:20 handle 20: fq_codel
tc qdisc add dev eth0 parent 1:30 handle 30: fq_codel
Параметры целевой задержки и интервала можно менять, но умолчания target 5ms и interval 100ms работают хорошо на большинстве каналов. На аппаратных очередях с множеством_TX-колец fq_codel работает внутри каждого кольца, а мультикольцевые драйверы вроде mq_prio распределяют пакеты по кольцам до fq_codel.
Cake единая дисциплина для шейпинга и AQM
Cake, Common Applications Kept Enhanced, объединяет шейпинг полосы и AQM в одной дисциплине. Она не требует отдельного HTB: полоса задаётся параметром bandwidth, а внутренние тины Diffserv (Best Effort, Voice, Video, и др.) получают свои приоритеты и веса. Cake умеет изолировать хостов по адресам, что спасает на шлюзах, где один активный хост не должен убить остальных. Включение на интерфейсе выглядит лаконично:
tc qdisc add dev eth0 root cake bandwidth 100mbit besteffort
Параметр besteffort выбирает схему тин, а опции diffserv и triple-isolate управляют изоляцией по DSCP и парам адресов. Cake поддерживает режимы triple-isolate изоляция по паре IP-адресов, dual-dsthost только по получателе, dual-srchost только по отправителю, а также diffserv с распределением по тинам EF, AF4x, AF3x, AF2x, AF1x, BE. Для домашнего шлюза с несколькими пользователями triple-isolate лучший выбор: каждый хост получает справедливую долю независимо от числа соединений. Cake особенно силен на каналах с переменной полосой, например LTE, где параметр autorate-ingress позволяет дисциплине подстраиваться под измеренную полосу. Внутренний алгоритм оценивает пропускную способность по подтверждениям TCP и пакетам ECN, и автоматически сужает или расширяет окно шейпинга. Для проводных каналов с фиксированной полосой autorate не нужен, но не мешает.
Приоритезация на аппаратных очередях
Современные драйверы сетевых карт выставляют несколько TX-колец, и распределение пакетов по ним делает qdisc mq_prio или taprio. mq_prio создаёт несколько очередей с приоритетами и отдаёт их драйверу, который маппит их на железные кольца. taprio добавляет временное разделение: расписание тайм-слотов для каждого приоритета, что даёт детерминированную задержку для трафика высокого приоритета, полезное для TSN. Расписание задаётся базовым временем base-time, длительностью цикла cycle-time и массивом записей с длительностью и маской приоритетов для каждого слота. Это аппаратное оффлоадинг планирования, и драйвер должен поддерживать HW_OFFLOAD флаг, иначе taprio работает в программном режиме с большими джиттерами. Базовая конфигурация mq_prio:
tc qdisc add dev eth0 root handle 100: mq_prio num_tc 3 map 2 2 1 0 2 1 0 0 queues 1@0 1@1 1@2 hw 1
Здесь num_tc 3 задаёт три класса трафика, map переводит приоритет skb в класс, queues описывает количество очередей на класс и их смещения в драйвере, а hw 1 просит аппаратное оффлоадинг. Для проверки, что оффлоад сработал, смотрят ethtool -K eth0 и вывод tc qdisc show.
Практический порядок настройки очередей на боевом интерфейсе
Настройка очередей на боевом интерфейсе укладывается в шесть шагов:
- снять базу через tc -s qdisc show и зафиксировать задержки и потери до изменений; запустить iperf3 в несколько потоков и параллельно ping, чтобы увидеть bufferbloat в чистом виде;
- выбрать архитектуру: простой fq_codel на корне для борьбы с bufferbloat, либо HTB с fq_codel листьями для гарантий по классам, либо Cake для шлюзов; для датацентров с известными классами трафика HTB плюс fq_codel листья золотой стандарт, для шлюзов с разнородными пользователями Cake с triple-isolate;
- построить дерево классов под профиль трафика: гарантированные полосы для критичного, best-effort для фонового, изоляция хостов при необходимости; классы лучше создавать с запасом в rate 10-20 процентов ниже физической полосы, чтобы оставить пространство для служебного трафика и заимствования;
- прописать фильтры u32 или flower с действиями mirred для зеркалирования, если нужно дублирование; flower с действиями goto chain позволяет строить читаемые цепочки классификации вместо монолитных u32 правил, и его рекомендуют для новых конфигураций;
- прогнать нагрузочный тест с iperf3 и ping параллельно, замеряя задержки под нагрузкой каждого класса; важно тестировать не только чистую полосу, но и сценарии конкуренции: одновременный запуск Voice, Video и Data классов, и проверка, что Voice не получает джиттер сверх бюджета;
- зафиксировать конфигурацию в systemd-networkd или скриптах инициализации и добавить мониторинг счётчиков отброшенных пакетов. В systemd-networkd секция [TC] позволяет описывать qdisc и классы декларативно, и при перезагрузке конфигурация поднимается автоматически. Мониторинг dropped и overlimits в tc -s qdisc show должен быть в алертинге: рост overlimits говорит о том, что класс упирается в ceil, а рост dropped - о переполнении листьев AQM.
Итог у дисциплин очередей зрелый: pfifo_fast уходит в прошлое, fq_codel и Cake закрывают bufferbloat за минуту настройки, HTB даёт предсказуемые гарантии полос, а аппаратные очереди с mq_prio и taprio переносят приоритезацию в железо. Грамотная сборка из этих кирпичей превращает хаотичную очередь в управляемый конвейер, где задержка каждого класса известна и ограничена.