Практики

Отказоустойчивый VoIP-сервер в облаке

Облачная структура Vo-IP сервера
Статья для специалистов, желающих получить знания об архитектуре отказоустойчивых VoIP-систем, с примерами конфигураций.

Мы в Программные технологии реализовали более десятка VoIP-проектов, например, cистему унифицированных коммуникаций.

Хотим обобщить свой опыт и поделиться с вами. Данная статья рассчитана на специалистов, желающих получить знания об архитектуре отказоустойчивых VoIP-систем. Базовое знание FreeSWITCH, Asterisk или аналогов желательно, но не является обязательным. В статье мы приведем примеры реальных конфигураций для организации кластера.

Введение в VoIP

Если слова FreeSWITCH, Asterisk, SIP, RTP, WebRTC для вас не пустой звук, можете смело пропускать эту часть.

Мир VoIP стоит на двух больших слонах: SIP и RTP. Эти 2 протокола были разработаны в конце прошлого века и пришли к нам из мира телефонии. SIP — командный протокол. Отвечает за выбор кодеков, начало/конец звонка, управление звонком (hold/transfer/etc) и имеет огромное количество расширений, в том числе для отсылки текстовых сообщений, нотификации об оставленных голосовых сообщениях и т.д.

IP-телефоны разных производителей могут поддерживать свой собственный набор расширений. Например, весьма распространен BLF (Busy Lamp Field) — поле светодиодов на настольном телефоне, которые можно настроить так, что они будут показывать статус телефона другого сотрудника.

RTP — медиапротокол. Отвечает за передачу аудио- и видеоданных. Использует внутри себя различные кодеки для кодирования медиаданных.

Оба протокола по умолчанию реализованы поверх UDP. TCP как опция есть почти у всех, но UDP лучше подходит по целому ряду причин. Также для обоих протоколов доступно шифрование.

В архитектуру VoIP заложена идея о том, что SIP- и RTP-сервер — это два разных сервера, поэтому встречаются реализации серверов, поддерживающих только SIP или только RTP. Тем не менее FreeSWITCH и Asterisk — это open source сервера, поддерживающие оба протокола и многое другое. Выбор между ними во многом зависит от личных предпочтений, требований и задач интеграции, но оба позволяют из коробки получить офисную мини-АТС.

Нельзя не упомянуть и WebRTC. Де факто это стандарт для голосовых звонков в web. WebRTC под капотом использует SRTP (secure RTP), оставляя реализацию командного слоя на откуп JS-коду. Для p2p-звонков нам нужно всего лишь передать второй стороне свой адрес и некие параметры звонка, что можно сделать на базе любого протокола. Если же нам нужна интеграция с SIP-сервером, то обычно используется протокол SIP over WebSocket. Реализация — sipjs.com.

FreeSWITCH поддерживает как SIP over WebSocket, так и собственный альтернативный протокол, реализованный модулем mod_vertoo. Он разработан специально для интеграции с WebRTC и решает неприятные проблемы несовместимости WebRTC и SIP, приводящие к задержкам 1-5 секунд перед созданием звонка. Впрочем, эти проблемы можно решать и по-другому, на стороне JS, но это тема для отдельной статьи.

Как и в случае обычных аналоговых звонков, VoIP требует наличия АТС, чтобы пользователи могли найти друг друга по некому номеру телефона и чтобы обойти проблему отсутствия белых IP у клиентов. После того, как клиенты нашли друг друга и договорились о кодеках, необходимо установить соединение для потока медиаданных. В зависимости от требований проекта и конфигурации сети, поток данных может идти напрямую от клиента к клиенту, либо через сервер.

Мир VoIP обычно тяготеет к пробросу трафика через сервер. Это решает проблему отсутствия белых IP у клиентов и позволяет централизованно подслушивать/записывать любой разговор.

WebRTC по умолчанию предлагает прямое соединение между клиентами, но это не работает в случае симметричного файрвола, когда у обоих клиентов нет белого IP. В таких случаях нам нужен медиапрокси-сервер STUN/TURN. Для коммерческих проектов вам придется устанавливать и оплачивать свой собственный прокси-сервер, чтобы ваше WebRTC based-приложение работало. Бесплатно браузеры предоставляют только услуги STUN сервера — распознавание внешнего IP-клиента, а вот проксирование трафика (TURN) — уже платно.

Примечание:

Есть технология, позволяющая в теории устанавливать соединение двум клиентам без белых IP c небольшой помощью сервера, но в наших проектах такой необходимости не возникало. Называется эта технология «симметричный NAT». Если верить документации, Freeswitch поддерживает симметричный NAT. Но мы советуем тщательно протестировать перед использованием в production.

Масштабирование SIP-траффика с помощью OpenSIPs

FreeSWITCH и Asterisk позволяют обрабатывать на одной машине огромное количество одновременных звонков — 500-1000. Но что делать, если нам надо больше? Или если в ТЗ сразу заложена необходимость облака с балансировкой нагрузки?

Тут на помощь приходит SIP-балансировщик нагрузки. Мы выделяем одну машину под балансировщик, который на уровне SIP выбирает конечный узел и перенаправляет туда обработку медиатрафика. Через балансировщик не проходит RTP media-трафик, поэтому он способен выдержать значительно большее количество одновременных звонков. Фактически после создания звонка нагрузка исчезает.

Выбор обычно идёт между OpenSIPs и Kamailio. Оба проекта имеют общего предка и схожую структуру модулей. Сравнение и выбор конкретного решения мы оставим для отдельной статьи, а пока расскажем об OpenSIPs.

Многие уже успели поработать с FreeSWITCH и/или Asterisk, в сети достаточно how-to примеров, но настройка SIP-балансировщика в OpenSIPs или Kamailio — это совершенно другое.

FreeSWITCH и Asterisk требуют всего лишь задать список пользователей и довольно простых правил роутинга звонков. Более того, есть бесплатные админпанели, которые позволят вам сконфигурировать всё в веб-интерфейсе.

В противовес, OpenSIPs и Kamailio заставляют пользователя разобраться с протоколом SIP хотя бы на начальном уровне и вручную прописать, что делать с каждым SIP-сообщением.

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

В программе вы указываете, как обрабатывать каждое входящее SIP-сообщение и что отправлять в ответ. Впрочем, основная работа возложена на подключаемые модули, и надо лишь настроить большой switch, когда и какой модуль звать.

Если же вы хотите добавить много кастомной логики, то в какой-то момент может оказаться проще вынести бизнес-логику в FreeSWITCH (Asterisk) и/или отдельный сервер, который и будет управлять роутингом звонков, очередями и т.д.

Простейший скрипт для балансировки нагрузки выглядит следующим образом:

  • Если входящее SIP-сообщение не первое, и уже известно куда его роутить, передаем управление модулю dialog.
  • Иначе вызываем модуль balancer для поиска менее загруженной ноды, проставляем её адрес в target и передаем управление модулю dialog.
  • Обрабатываем ошибки:
  • Выбранная нода может вернуть код ошибки (аналогично http-кодам), и нам надо прописать, что делать в каждом случае — сбросить звонок или попробовать выбрать другую ноду.
  • Модуль balancer может вернуть ошибку и сказать, что свободных нод нет.

Очень важный момент:

Когда вы пишете программы обработки для OpenSIPs, помните, что вы должны обработать как сообщения, идущие снаружи в ваше облако, так и сообщение, идущее из вашего облака наружу. Поэтому зачастую программа бьется на 2-3 огромных If-блока в зависимости от направления SIP-пакета. Пример:

Для краткости мы вырезали всю обработку ошибок, полную версию можно найти здесь.

route { if (!has_totag()) { #Первое сообщение в диалоге, запоминаем его в базе и не выходим, надо найти куда его роутить record_route(); } else { #Не первое сообщение, роутим по уже проторенной дороге и выходим loose_route(); t_relay(); exit; }

// Ищем свободный узел. 1 - вес нашего запроса, call-ресурс. 
// Можно сделать сложную логику и выделять под разные звонки разный набор машин
load_balance("1","call");
if ($retcode<0) {
    //нет свободных узлов
    sl_send_reply("500","Service full");
    exit;
}

xlog("Selected destination is: $du\n");

if (!t_relay()) {
    sl_reply_error();
}

}

Особенности OpenSIPs:

  • Значения заголовков не меняются в процессе выполнения программы. Если вы что-то присвоите и потом попробуете вывести в лог, выведется первоначальное значение.
  • Есть 2 разных типа пользовательских переменных, и они несовместимы. Часть модулей использует одни, часть другие.
  • Многие функции ничего не возвращают, а просто делают что-то полезное, а что именно, можно узнать только полностью прочитав документацию и иногда — исходники. Например, load_balance() проставляет значение переменной $du. А record_route() сохраняет в заголовке сообщения $du, но при этом вызывается раньше load_balance(). Явное нарушение причинно-следственной связи. Предполагаем, что заголовки считаются в самом конце, и record_route() скорее проставляет некий флажок или ссылку.
  • Stateless-парадигма. SIP позволяет все необходимые для роутинга данные хранить прямо в заголовках сообщений. Клиент же обязан их копировать при ответе, что позволяет серверу быть stateless. Тем не менее, по необходимости можно сохранять в базе таблицу роутинга и не оповещать весь мир о внутренней структуре своего кластера, но это добавляет нагрузку на базу и задержки. Выбор за вами.

Отказоустойчивость и масштабирование VoIP-систем

Отказоустойчивость и масштабирование — связанные вещи. Невозможно продумать архитектуру отказоустойчивости без учёта схемы масштабирования. Но можно реализовать только отказоустойчивость в самом простом варианте. Для начала, немного теории.

На уровне протокола VoIP поддерживает возможность восстановления звонка даже при отказе сервера.

  • SIP failure. По умолчанию SIP использует протокол UDP, который в отличие от TCP не устанавливает постоянного соединения. Но клиент перерегистрируется примерно каждую минуту (настраивается). Фактически на каждый пакет от клиента может отвечать новый сервер. Единственное ограничение — IP-адрес ответного пакета не должен меняться, и нам надо где-то хранить состояние звонков пользователя (если они есть в данный момент), чтобы любой сервер мог работать с этими данными.
  • RTP failure. Если у нас по каким-то причинам отказал RTP-сервер, который также использует UDP-протокол, мы не можем легко восстановить медиапоток, но мы можем послать через SIP-протокол команду пересоздания потока (reinvite). В идеале клиент заметит всего лишь выпадение нескольких секунд аудио. Под вопросом остается реализация обнаружения падения. Решения из коробки нет.

Рассмотрим разные варианты архитектур.

Нужен VoIP-проект? Напишите нам и мы оценим его бесплатно.

Резервирование SIP-сервера

Детали реализации сильно отличаются в зависимости от используемого сервера, не меняется только одно — необходимость в Virtual (floating) IP, который мы можем динамически переназначить на другой сервер.

Задача решается на уровне shell-скриптов, которыми мы проверяем, работает ли сервер, и если нет, переназначаем IP на другой. Главная проблема — баланс между ложными срабатываниями и долгим ожиданием перед переключением.

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

Масштабирование SIP-серверов

Правильно реализованное масштабирование заодно решает и проблему отказоустойчивости. Умер один сервер — возьмем другой. Главная проблема — SIP-клиент игнорирует пакеты, посланные с другого IP. Пример «Как работать не будет »:

Клиент B просто проигнорирует входящий звонок (invite), потому что его source IP отличается от сервера, на котором клиент зарегистрирован.

Как работать будет? В зависимости от конкретного проекта, имеющегося железа и наших возможностей, проблему можно решать 3 способами.

Подмена IP-адреса пакетов

Самый простой вариант — поставить ещё более простой (и быстрый) load balancer, который подменит source IP на свой для всех исходящих пакетов. И спрятать все SIP-сервера за него. Но нам нужно настроить все SIP-сервера на использование общей базы данных.

Очевидный плюс — простота настройки. Очевидные минусы:

  • Single Poing of Failure в виде load balancer.
  • Может не хватить пропускной способности load balancer.

Проброс команды Invite

Настраиваем ферму SIP-серверов с разными IP-адресами, DNS в режиме round robin. Клиенты цепляются случайным образом к одному из множества серверов.

Программируем SIP-сервер пересылать invite нужному серверу (на котором зарегистрирован другой клиент), чтобы уже он от своего имени переслал команду клиенту.

Плюс — всё замечательно масштабируется. Небольшой минус — SIP register действительно хорошо масштабируется, а вот SIP invite и прочие команды, относящиеся к звонку, масштабируются чуть хуже, потому что с большой вероятностью будут проходить через 2 узла. Впрочем, register случается значительно чаще.

Аксмор

Расскажите нам о вашей задаче — подумаем, как можно ее решить

1

Первый разговор — чтобы понять, сможем ли мы вам помочь.

2

Вместе с нашим СТО и архитектором обсудим вашу задачу.
Ответим на ваши вопросы.

3

Оценим проект.
Вы получите коммерческое предложение, включающее технические рекомендации и оценку рисков.

Имя*
Email*
Телефон
Кратко о проекте

Защищено Yandex Smartcaptcha: Уведомление об условиях обработки данных

Контакты

Напишите нам на почту sales@axmor.ru
или позвоните +7 (383) 363-10-24