Asterisk в виртуалке: почему хрипит звук и как настроить ВМ
Москва, Щёлковское шоссе, д. 92, корп. 7 · Пн–Пт 9:00–19:00 · +7 903 729-62-41
АйТи Фреш
Серверы и инфраструктура

Asterisk на Proxmox или ESXi: откуда берутся хрипы, заикания и обрывы и что крутить в настройках виртуальной машины

Автор: , директор ООО «АйТи-Фреш» · · ~20 мин чтения
Стеклянный куб с телефонной станцией на серверном шасси с трещиной от соседней ВМ — Asterisk хрипит в виртуалке
Голосу нужно немного ресурсов, но ровно в нужную миллисекунду.

Если Asterisk после переезда в виртуальную машину хрипит, почти всегда виноват не провайдер, а нехватка процессорного времени у ВМ (CPU steal). Проверьте vmstat и timing test, выделите 2 vCPU, тип host, отключите ballooning. Ниже команды и разбор по слоям.

Хрипит после переезда в виртуалку: виновата ли виртуализация

Звонок в пятницу вечером: «Мы перенесли АТС в виртуальную машину, и теперь музыка на удержании хрипит, как магнитофон с жёваной плёнкой. Сеть не трогали, коммутатор тот же». Через минуту следующая фраза, и я её могу произнести вместе с собеседником: «Значит, виноват провайдер». Провайдер, как правило, ни при чём, а вот про виртуальную машину стоит подумать, потому что именно она поменялась. Если вы строите телефонию внутри общего парка серверов и вам нужен IT-аутсорсинг для офиса, начинать диагностику надо с честного вопроса: кто и когда выдаёт процессорное время вашей АТС.

Голос в Asterisk устроен жёстче, чем кажется. Каждые 20 миллисекунд, то есть 50 раз в секунду, нужно прочитать пакет, при необходимости перекодировать, записать и отправить дальше. Опоздал на 30–40 мс, и в потоке образуется дырка, которую слушатель воспринимает как хрип, щелчок или «роботизированный» голос. Физический сервер с отдельным процессором опаздывает редко: ему никто не мешает. Виртуальная машина живёт иначе. Её ядра — это потоки гипервизора, а гипервизор имеет полное право посреди вашего 20-миллисекундного интервала отдать процессор соседу, например серверу 1С или резервному копированию.

Я разделяю причины на четыре слоя и проверяю их строго сверху вниз. Первый — внутренний таймер Asterisk: если он сломан или подменён неэффективным модулем, файлы играют рвано даже на пустой машине. Второй — планировщик гипервизора: нехватка процессорного времени (steal), ballooning памяти, неподходящий тип процессора. Третий — виртуальная сетевая карта и её аппаратные ускорения. Четвёртый — сеть офиса и оператора, о которой все вспоминают первой, а проверяют последней. Такой порядок экономит время: первые два слоя проверяются за пять минут двумя командами, и половина «проблем сети» заканчивается именно на них.

Самое полезное, что вы можете сделать до любых настроек, — определить, что именно хрипит. Музыка на удержании и объявления в очереди читаются с диска и проигрываются самим Asterisk, сеть в их генерации не участвует. Если хрипят они, виновата не сеть, а таймер или нехватка процессора. Если же внутренние звонки между телефонами чистые, а плохо слышно только собеседника из города, смотрите на транк и джиттер. Если рвутся и те и другие, значит, у вас несколько проблем сразу, и это тоже бывает чаще, чем хочется. Дальше я иду по слоям и по ходу показываю, как отличить один от другого командами, а не ощущениями.

Цифры и версии: Хрипит после переезда в виртуалку: виновата ли виртуализация — схема
Цифры и версии: Хрипит после переезда в виртуалку: виновата ли виртуализация. Открыть схему в полном размере

Таймеры Asterisk: проверяем, на чём играет музыка

Иногда админ часами крутит гипервизор, а проблема сидит в самом Asterisk. Для воспроизведения файлов, то есть музыки на удержании и объявлений в очереди, а также для ConfBridge и IAX2-транкинга Asterisk нужен внутренний источник времени, и модулей таймера несколько. По документации они выбираются по приоритету: сначала res_timing_timerfd (встроен в ядро Linux, нужно ядро от 2.6.25 и glibc от 2.8, работает очень эффективно), затем res_timing_kqueue (для BSD и macOS, на Linux не собирается), res_timing_dahdi (если в машине есть DAHDI-железо) и в конце res_timing_pthread. Последний переносимый, но самый неэффективный, и под нагрузкой он теряет точность. Если по какой-то причине у вас загрузился именно он, хрип под нагрузкой вам обеспечен.

Проверка занимает минуту. Посмотрите, какие модули таймера загружены, и прогоните встроенный тест. Результат теста — число тиков за секунду: при здоровом таймере и интервале 20 мс ожидайте около 50 тиков, но формат вывода и точные значения проверьте в своей версии, они зависят от сборки.

asterisk -rx "module show like timing"
asterisk -rx "timing test"

В первом выводе должен значиться res_timing_timerfd со статусом Running. Если вместо него висит pthread, значит timerfd не загрузился: частая причина — самосборное ядро или контейнер без нужных возможностей. Если тест показывает заметно меньше 50 тиков или цифры скачут от запуска к запуску на простаивающей машине, дело уже не в Asterisk, а в том, что ему не дают процессор, и мы переходим к следующему слою.

Отдельная история — сломанный модуль таймера. Я видел АТС, где после обновления загружался модуль DAHDI-таймера без самого железа, и таймер вёл себя странно. Лечится это просто: запретить лишний модуль в modules.conf директивой noload либо удалить файл .so из каталога модулей (обычно /usr/lib/asterisk/modules, путь проверьте у себя). Перезагрузка Asterisk после этого обязательна, горячей подменой таймера никто не занимается. Эту правку я делаю только тогда, когда вижу в выводе module show нежелательный модуль; профилактически выкидывать модули не нужно, это та самая «оптимизация», после которой утром не звонит ничего.

Теперь про то, чего таймер не объясняет. В виртуалке с недостатком процессорного времени timerfd честно срабатывает вовремя, но сам процесс Asterisk просто не успевает получить процессор и обработать событие. С точки зрения слушателя разницы нет: хрип тот же. Поэтому «timing test» у меня служит лакмусовой бумажкой: если число тиков упало, идём смотреть на steal; если тиков ровно 50, а хрип остался, то с большой вероятностью дело в сети или в кодеках. Разделять эти две ситуации важно по одной причине: лечение у них противоположное, и перепутать их значит потратить неделю не на ту настройку.

Таймер Asterisk не лечится джиттер-буфером и не лечится сетью. Если timing test показывает просадку тиков, сначала разбирайтесь с процессором ВМ, а уже потом с телефонами и провайдером.
Asterisk на Proxmox или ESXi: откуда берутся хрипы, заикания и обрывы и что крутить в настройках виртуальной машины — схема
Схема к статье. Открыть схему в полном размере

Планировщик гипервизора: steal, ballooning и тип CPU

Вот здесь начинается самое неприятное. Скажем прямо: в 8 случаях из 10 «хрипящая виртуальная АТС» — это CPU steal. Это процент времени, который виртуальный процессор хотел работать, но гипервизор не дал, потому что физическое ядро было занято кем-то другим. В гостевой системе он виден как колонка st в vmstat и как значение st в строке %Cpu(s) в top. Для голоса заметно уже при единицах процентов, а при 10–20 % звук хрипит стабильно. Самое весёлое, что загрузка CPU внутри ВМ при этом выглядит благополучно: 15 % занято, 85 % свободно, а звонки рвутся. Админ смотрит на график, пожимает плечами и идёт ругать провайдера.

Смотреть надо так, и непременно в момент жалоб, а не в тихое утро:

vmstat 1 30
top -b -n 1 | head -5

В выводе vmstat последняя колонка st — это steal в процентах за секунду. Если на живой АТС вы видите устойчиво 3–5 % и всплески до 15–20 %, виртуализация уже виновата, и никакие джиттер-буферы это не исправят. На самом хосте Proxmox запустите обычный top и посмотрите, чем заняты ядра, пока ваша АТС хрипит: чаще всего в этот момент работает бэкап, регламентное задание 1С или распаковка архива в соседней ВМ. Отдельно проверьте режим энергосбережения процессора. Для хоста с голосом я советую governor performance: команда cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor покажет текущий, а менять его нужно в соответствии с политикой вашего сервера, на разных платформах это делается по-разному.

Вторая беда называется ballooning. Это механизм, который отбирает у гостя память, когда она нужна хосту или соседней ВМ. Для файлового сервера это терпимо, для АТС вредно: возврат страниц памяти даёт задержки в самый неподходящий момент, а Asterisk, у которого память вдруг ушла в своп, начинает опаздывать с пакетами. В Proxmox для ВМ с голосом устройство ballooning отключают (Hardware → Memory → Ballooning Device выключить) и ставят фиксированный объём, 2–4 ГБ для офиса на 20–50 рабочих мест хватает с запасом. Экономия нескольких гигабайт под видом «пусть гипервизор разберётся» обходится дороже, чем плата за лишнюю планку памяти.

Третье — тип процессора. По умолчанию ВМ в Proxmox получает универсальную модель процессора (в PVE 8 это x86-64-v2-AES, в старых версиях и у перенесённых машин часто kvm64), которая скрывает от гостя часть современных инструкций, например AVX. Для шифрования SRTP и перекодирования это лишняя нагрузка. Тип host передаёт гостю реальные возможности хоста; минус в том, что ВМ нельзя спокойно переносить на хост с другой моделью процессора без проверки, поэтому в кластерах с разным железом выбирают конкретную общую модель. Само число vCPU я не раздуваю: 2 выделенных vCPU для АТС на 20–50 рабочих мест лучше, чем 8, которые делят ядра с 1С. Чем больше vCPU у ВМ, тем сложнее планировщику найти свободные ядра одновременно. Подробнее о том, как считать нагрузку на АТС в одновременных звонках, у нас есть отдельная статья про планирование ёмкости по числу звонков, и там видно, что голосу нужно мало, но стабильно.

Чтобы не писать длинный реферат, вот настройка ВМ в Proxmox одной командой. Номер ВМ у вас свой, синтаксис и значения параметров проверьте по документации вашей версии Proxmox VE, прежде чем применять на боевой машине:

qm set 105 --cpu host --cores 2 --sockets 1 --balloon 0 --memory 4096
qm config 105 | grep -E "cpu|cores|balloon|memory"

Изменения вступят в силу после полного выключения и запуска ВМ; перезагрузка изнутри гостя тут не помогает, и это одна из самых частых причин «мы всё поменяли, а ничего не изменилось».

Если у вас АТС в виртуалке хрипит, а причину никто не измерил, мы снимем steal, таймер и статистику каналов, покажем, какой слой виноват, и настроим ВМ под голос. Обсудить задачу
Цифры и версии: Планировщик гипервизора: steal, ballooning и тип CPU — схема
Цифры и версии: Планировщик гипервизора: steal, ballooning и тип CPU. Открыть схему в полном размере

Сеть виртуальной машины: virtio, offload и джиттер-буфер

Допустим, steal нулевой, таймер бодр, а внешнего собеседника всё равно слышно плохо. Тогда переходим к сети, но не к сети офиса, а сначала к виртуальной карте. Эмулируемая карта вроде e1000 тратит процессор на эмуляцию, а в нагруженном хосте это добавляет задержки. Для голоса я ставлю паравиртуальную virtio-net. Мастер Proxmox предлагает её по умолчанию, а вот у машин, перенесённых с другого гипервизора или собранных «как для Windows», внутри нередко остаётся e1000 — проверьте в настройках ВМ, а не на слово. Диск в этой связке — VirtIO SCSI, желательно с iothread: запись разговоров через MixMonitor и логи на медленном диске напрямую звук не ломают, но тормозят сам Asterisk, а ему и так нелегко.

Вторая тонкость — аппаратные ускорения сетевого стека: GRO, LRO, TSO и GSO. Они склеивают и режут пакеты в расчёте на большие потоки данных, а голос — это маленькие пакеты каждые 20 мс, и склейка в некоторых драйверах добавляет задержку или странности в статистике. Сначала посмотрите, что включено, потом попробуйте выключить на интерфейсе, который принимает RTP. Влияние зависит от драйвера и версии ядра, поэтому проверяйте эффект замером, а не верой:

ethtool -k eth0 | grep -E "generic-receive-offload|large-receive-offload|tcp-segmentation-offload|generic-segmentation-offload"
ethtool -K eth0 gro off lro off tso off gso off

После такой команды настройка живёт до перезагрузки интерфейса. Для постоянного эффекта её прописывают в конфигурации сети вашего дистрибутива (в Debian обычно через директиву post-up в описании интерфейса). Не пытайтесь «оптимизировать» заодно всё остальное: меняйте один параметр за раз и замеряйте.

Теперь про джиттер-буфер, потому что вокруг него больше всего мифов. Буфер сглаживает неравномерный приход пакетов из сети: пакеты пришли с интервалами 10, 35, 20, 5 мс, а в ядро Asterisk выходят ровно через 20. Это лечит сетевой джиттер, например на входящих транках от оператора. Он не лечит пропуски таймера из-за CPU steal, потому что стоит перед логикой Asterisk и ничего не знает о том, что процесс не получил процессор. Включается он функцией диалплана JITTERBUFFER, пишется функция только в режиме присваивания. Она допускает режимы fixed, adaptive и disabled; максимальный размер по умолчанию 200 мс, порог повторной синхронизации 1000 мс, а для adaptive есть ещё target_extra с умолчанием 40 мс. Для PJSIP это основной способ включить буфер: ставится на входящем канале транка до Queue или Dial.

[from-trunk]
exten => _X.,1,NoOp(Incoming from trunk)
 same => n,Set(JITTERBUFFER(adaptive)=default)
 same => n,Goto(queues,s,1)

Другие варианты записи по документации: Set(JITTERBUFFER(fixed)=default), Set(JITTERBUFFER(fixed)=200) и Set(JITTERBUFFER(adaptive)=200,,60), а выключается буфер так: Set(JITTERBUFFER(disabled)=). Если буфер задан и в драйвере канала, и функцией, приоритет у функции. Я начинаю с adaptive и значений по умолчанию, а fixed беру там, где нужна предсказуемая задержка. Сверьте контекст и имя точки входа со своим диалпланом: пример выше нужно подогнать, а не копировать. Если эхо и странные искажения идут не вместе с хрипом, а сами по себе, это другой разговор, и там нужны три настройки против эха в PJSIP, а не виртуальный процессор.

Как проверить, что джиттер вообще есть. В современных версиях Asterisk включите сбор статистики RTCP командой rtcp set stats on и смотрите pjsip show channelstats во время звонка: там видны потери и джиттер по каждому каналу. Для SIP-диалога удобен sngrep. Если сравнить вывод для внутреннего звонка и звонка через транк, картина обычно становится ясной за пять минут. Цифры считайте по своим наблюдениям: универсальных «хороших» значений джиттера я называть не буду, они зависят от кодека и оператора, а выдуманные пороги только вредят.

Разбор: клиника «Белый Квадрат» и хрип после переезда с железа

Стоматологическая клиника «Белый Квадрат»: 22 рабочих места, 4 администратора на телефоне, звонки пациентов — это деньги, потому что каждый недозвонившийся пациент записывается к соседям. Asterisk 20 жил на старом физическом сервере и не вспоминался годами. Потом железо решили списать, и АТС переехала в виртуальную машину Debian 12 на Proxmox VE 8. На том же хосте с двумя сокетами и 16 ядрами уже работали ВМ с 1С и файловый сервер. Сеть офиса не меняли: те же коммутатор, телефоны и транк. Через неделю пошли жалобы: музыка на удержании и в очереди хрипит, а пациенты говорят, что администраторов плохо слышно.

Первые подозрения, как водится, пали на провайдера и на сеть. Я попросил просто посмотреть vmstat в момент жалоб и сравнить с графиками 1С. Картина сложилась быстро. ВМ с Asterisk имела 2 vCPU, тип процессора kvm64 и включённый ballooning; ВМ с 1С получила вдвое больше vCPU, без лимитов, и ночью (резервные копии и регламентные задания) и в обед (отчёты бухгалтерии) забирала почти всё. В эти минуты steal на АТС доходил до 15–20 %, а в top внутри самой ВМ процессор казался почти свободным. Timing test показывал просадку тиков относительно 50, а в тихие часы всё было в порядке. Это и был диагноз: сеть чистая, виновата конкуренция за процессор.

Правки мы сделали за один рабочий день, вне часов приёма. Тип процессора сменили с kvm64 на host, зафиксировали 2 выделенных vCPU, отключили ballooning и задали фиксированные 4 ГБ памяти, а 1С-ВМ ограничили по количеству vCPU и приоритету. В настройках ВМ заменили эмулируемую карту e1000 на virtio-net, а внутри гостя на интерфейсе с RTP выключили GRO и LRO. На входящих транках включили JITTERBUFFER в режиме adaptive, как страховку от сетевого джиттера оператора. Governor на хосте переключили на performance. После перезапуска ВМ целиком (не перезагрузки из гостя) прогнали контрольные звонки по очереди из четырёх телефонов и сравнили с замерами «до».

Итог. Steal упал с 15–20 % до менее 1 % даже в обеденный пик 1С, timing test выдавал стабильные значения, хрип исчез и на удержании, и в очереди. Мы оставили неделю наблюдения: ежедневно смотрели статистику каналов и vmstat в часы нагрузки, и за семь дней жалоб не поступило. Общий срок: 1 рабочий день на работы плюс неделя наблюдения. Забавная деталь: пока шла диагностика, клиника уже обсуждала покупку нового коммутатора. Коммутатор экономит нервы и деньги, когда его не покупают без измерений. Такой кейс типичен для малого офиса, где вся ИТ-инфраструктура живёт на одном хосте, и надёжность этого хоста мы потом отдельно закрепили репликацией ВМ в Proxmox, чтобы падение железа не оставило администраторов без телефона.

Мониторинг и чек-лист: как не дать хрипу вернуться

Проблема с виртуальной АТС в том, что она чинится один раз, а ломается по-тихому, при следующем изменении на хосте. Кто-то добавил ещё одну ВМ, кто-то включил бэкап в рабочее время, кто-то обновил гипервизор, и настройки поехали. Поэтому после правок я ставлю наблюдение именно за теми величинами, которые мы разбирали, а не за общим «ЦП загружен на 40 %». В мониторинге на Zabbix для такой ВМ достаточно четырёх вещей: steal внутри гостя, steal и загрузка ядер на хосте, число активных звонков и статистика джиттера и потерь по каналам.

Порог тревоги по steal я ставлю грубо: предупреждение при устойчивых 3–5 %, авария при 10 % и выше. Тонкая настройка не нужна, потому что нормальным для АТС значением является ноль. Отдельно следите за временем: ВМ с плывущими часами в итоге создают странные сбои регистраций и TLS, поэтому в госте стоит держать chrony, а источник времени проверять командой cat /sys/devices/system/clocksource/clocksource0/current_clocksource; для KVM-гостей обычно это kvm-clock. На разных версиях ядра картина отличается, и если у вас что-то иное, сначала выясните причину, а потом уже правьте.

Метрики качества самих звонков стоит собирать отдельно от метрик сервера. Какие показатели считать и как их вытаскивать из Asterisk, мы разобрали в статье про метрики качества звонков: там видно, как связать жалобу «плохо слышно» с конкретным каналом и временем. Связка простая. Жалоба пришла в 12:40, график steal в 12:40 показывает пик, значит, виноват хост; steal ровный, а потери на транке растут — значит, сеть или оператор. Без такой связки вы будете каждый раз спорить с пользователями о том, что им послышалось.

Итоговый чек-лист для ВМ с голосом, который я прикладываю к проектам: не менее 2 выделенных vCPU, тип процессора host или конкретная модель, ballooning выключен, 2–4 ГБ памяти для 20–50 рабочих мест, virtio-net и VirtIO SCSI, governor performance на хосте, GRO и LRO выключены на RTP-интерфейсе по результатам замера. И правило размещения: не ставьте АТС на один хост с ВМ, которые ночью и в обед занимают процессор без лимитов. Если такого хоста нет, ограничьте соседей. Голосу нужно немного ресурсов, но ровно в нужную миллисекунду, и обеспечить это дешевле, чем объяснять директору, почему недозвонились до клиентов.

Порядок действий: Мониторинг и чек-лист: как не дать хрипу вернуться — схема
Порядок действий: Мониторинг и чек-лист: как не дать хрипу вернуться. Открыть схему в полном размере

Частые вопросы

Как понять, что хрип из-за виртуализации, а не из-за сети?

Проверьте, что именно хрипит. Музыка на удержании и объявления в очереди проигрываются самим Asterisk без участия сети, поэтому их хрип указывает на таймер или нехватку CPU. Затем посмотрите колонку st в vmstat 1 во время жалоб: устойчивые единицы процентов и всплески до 10–20 % говорят о виртуализации.

Лечит ли джиттер-буфер хрип от CPU steal?

Нет. JITTERBUFFER выравнивает неравномерный приход пакетов из сети и стоит до логики Asterisk, а пропуски из-за нехватки процессорного времени возникают уже внутри процесса. Включайте его для сетевого джиттера на транках, а steal лечите настройками ВМ и размещением на хосте.

Сколько vCPU и памяти выделять виртуальной АТС на 20–50 рабочих мест?

Обычно достаточно 2 выделенных vCPU и 2–4 ГБ памяти при выключенном ballooning. Больше vCPU не значит лучше: планировщику сложнее найти сразу несколько свободных ядер, если хост нагружен. Точные цифры зависят от кодеков, записи разговоров и числа одновременных звонков.

Какой тип процессора выбрать в Proxmox для Asterisk?

Тип host передаёт гостю реальные возможности процессора хоста и снимает лишнюю нагрузку, которую создаёт универсальная модель kvm64. Если ВМ переносят между хостами с разным железом, выбирайте конкретную общую модель. Синтаксис qm set и допустимые значения проверьте в документации своей версии.

Нужно ли выключать GRO и LRO в госте?

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

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи
АйТи-Фреш (ООО «АЙТИ-ФРЕШ») · г. Москва, Щёлковское шоссе, д. 92, корп. 7 · +7 903 729-62-41 · info@itfresh.ru · Пн–Пт 9:00–19:00