В Mars IS я отвечаю за мониторинг производительности приложений. Главный принцип, на котором основывается мониторинг производительности заключается в детальном централизованном анализе сетевого трафика между конечными пользователями и серверами, расположенными в наших дата-центрах. Он осуществляется в режиме реального времени и позволяет видеть объективную картину производительности как отдельных пользователей, так и приложений в целом. Подробнее о наших методах мониторинга и анализа можно прочитать здесь.
Однако, мир не стоит на месте и наша компания, следуя общему тренду, начинает активно пользоваться облачными решениями. В частности, многие из приложений перемещаются на сервера в облачном дата-центре. Это перемещение вызывает определённые сложности в моей области и даже ставит под угрозу перспективу её существования.
В этой статье я хочу поделиться тем, как мне удалось разрешить проблему централизованного сбора и анализа сетевого трафика на серверах, расположенных в облачных дата-центрах. Думаю, результат может быть полезен и в других областях ИТ, где есть необходимость централизованного сбора и анализа сетевого трафика.
Базовая конфигурация мониторинга
Интересующий нас трафик снимается со SPAN-портов граничных коммутаторов, стоящих между удалёнными пользователями и серверами дата-центра (см. Рисунок 1)
Рисунок 1
SPAN-порт отражает весь пользовательский трафик по заданным VLAN на сервер сбора статистики. Там происходит распознавание отдельных приложений, пользовательских операций и самих пользователей, а также измеряется множество параметров их производительности. Собранные данные агрегируются и передаются на сервера для последующего анализа. Эти же сервера предоставляют доступ к статистике командам поддержки и отвечают за создание оповещений о неприемлемой производительности того или иного приложения.
Конфигурация для мониторинга для серверов в облаке
Описанная выше система мониторинга удобно настраивается до тех пор, пока у нас есть доступ к сетевой инфраструктуре дата-центра. Метод перестаёт работать, как только приложение переезжает в облако. Установка физических серверов сбора статистики в облаке и настройка там SPAN-портов представляет собой дорогостоящую и трудно согласуемую задачу. Между тем, команды поддержки предпочитают иметь дело с единой системой мониторинга как для приложений внутри дата-центра, так и тех, что расположены в облаке.
Оценив стандартные методы решения проблемы, предоставляемые сторонними компаниями, я понял, что меня не устраивает ни один из них. Стандартно предлагается собрать конструкцию, показанную на рисунке 2.
Рисунок 2
На виртуальные front-end серверы устанавливаются агенты vTAP (virtual network tap device), которые считывают и отсылают весь трафик на сервер сбора статистики посредством GRE-туннелей. Есть другое стандартное решение, основанное на средствах самой виртуальной среды (например, с помощью технологии Hyper-V). Это допустимые подходы, но они весьма далеки от оптимального решения.
Во-первых, нужно ставить дополнительный сервер сбора статистики на виртуальную машину в облаке, в то время как существующие серверы сбора статистики внутри наших дата-центров имеют существенный запас неиспользованной мощности. Конечно, можно было-бы терминировать GRE-туннели прямо на них, но нет гарантии, что собранный трафик не переполнит ёмкость линии связи между нашим дата-центром и облаком. Хотя решения и дают возможность фильтровать статистику на агентах vTAP, нет удобных механизмов контроля скорости создаваемого потока. То есть, ошибка в конфигурации фильтра может привести к плачевным последствиям для всей инфраструктуры.
Во-вторых, нужно создавать отдельный туннель на каждый сервер приложения. Управление большим количеством GRE туннелей представляет дополнительную задачу и потребует значительного внимания при поддержке.
В-третьих, удивляют системные требования для установки агентов vTAP. Один из ведущих производителей требует 4Gb RAM и забирает одно ядро CPU. Эти требования не сопоставимы со сложностью решаемой задачи. Помимо этого, нужно позаботиться о
В-четвёртых, ежегодно нужно платить за каждый агент vTAP. Поэтому стоимость решения становится слишком высокой для массового внедрения.
Наш метод
Сопоставив всё это с нашей текущей ситуацией, я решил действовать в соответствии со своим любимым правилом: «Не нравится, как есть – сделай как надо». Потратив около двух недель на разработку, мне удалось создать свой аналог vTAP – лёгкий, удобный, а главное, способный контролировать создаваемый поток данных. Я назвал его – ivTAP: Intellectual Virtual TAP device.
Принцип работы ivTAP показан на рисунке 3.
Рисунок 3
На каждый из серверов, где необходимо анализировать сетевую статистику устанавливается клиентская часть приложения – ivTAPclient. Она делает довольно простую работу. С помощью сетевого драйвера BPF и библиотеки libpcap/winpcap сканирует сетевые пакеты с заданным фильтром и пересылает их серверной части приложения – ivTAPsrv, предварительно упаковав их в канал UDP. Каждый канал контролируется на предмет превышения предельно допустимой скорости.
Серверная часть приложения извлекает пакеты из канала UDP и «подсовывает» их в любой из уже использующихся интерфейсов SPAN портов. Сервер сбора сетевой статистики получает и обрабатывает эти пакеты наравне с теми, которые были сняты со SPAN порта коммутатора обычным путём.
В результате получился продукт, решающий поставленную задачу и удовлетворяющий нашим требованиям:
- Продукт не создаёт дополнительную угрозу ни для серверов приложений, ни для системы сбора сетевой статистики: не потребляет значительных ресурсов (CPU<1%; RAM <=512Mb) и не создаёт неконтролируемых выбросов нагрузки на линии связи WAN;
- Данный метод снятия статистики не добавляет значимую ошибку в измерения производительности. В каждый момент времени максимальная ошибка определяется значением network jitter + 10ms на стороне ivTAPclient;
- Программа работает устойчиво в течении продолжительного времени;
- ivTAPclient запускается как обычный сервис на серверах Windows и не зависит от конфигурации среды сервера front-end;
- Трафик, отражённый ivTAP, воспринимается сервером сбора статистики точно так же, как и полученный штатным путём через SPAN-порт;
- Программа не нарушает никаких лицензионных соглашений: все манипуляции производятся на уровне операционной системы, не касаясь сервиса сбора и обработки сетевых пакетов;
- При создании программы использовались только те библиотеки, которые допускают бесплатное коммерческое использование – LGPL, MIT, Apache v2.
Вот перечень инструментов и библиотек, которые я использовал для создания ivTAP:
— Eclipse Java EE IDE. Собственно, на чём и создавался продукт;
— Winpcap/libpcap;
— Библиотека jnetpcap. Основа проекта, реализующая доступ к libpcap/winpcap из Java;
— Библиотека kohsuke.args4j. Обрабатывает один-единственный аргумент запуска программы;
— Бесплатный сервис-wrapper для Windows, чтобы с продуктом было удобно работать;
Для того, чтобы дальнейший материал был Вам полезен, рекомендую прерваться на этом месте и ознакомиться с описанием библиотеки jnetpcap. Не вижу смысла переписывать здесь базовые принципы работы с этой библиотекой, а также приводить полный листинг программы. Будет более разумно ограничиться демонстрацией нескольких наиболее важных и интересных выдержек. Сразу оговорюсь: я не профессиональный программист. Заранее прошу прощения, если какой-либо фрагмент кода вызовет негодование со стороны уважаемых читателей.
В целом, концепция выглядит, как показано на рисунке 4:
Рисунок 4
Клиентскую часть программы реализует класс :IVTAP. Первое, что он делает в void main() – проверяет наличие ключа "–l" в аргументах запуска. Этот ключ используется один только раз для подготовки первого запуска программы. Он выводит имена всех сетевых адаптеров, доступных на данном front-end сервере. Для дальнейшей работы нам понадобится выбрать тот адаптер, с которого мы хотим собирать статистику и указать его в файле конфигурации программы. Метод представляет собой лёгкую модификацию примера, приведённого на сайте jnetpcap (см. Листинг 1).
private static void listDevices() {
List<PcapIf> alldevs = new ArrayList<PcapIf>(); // Will be filled with NICs
StringBuilder errbuf = new StringBuilder(); // For any error msgs
int r = Pcap.findAllDevs(alldevs, errbuf);
System.out.println("List available interfaces and exit");
if (r == -1 || alldevs.isEmpty()) {
System.err.printf("Can't read list of devices, error is %s", errbuf.toString());
return;
}
Iterator<PcapIf> itrdev = alldevs.iterator();
while(itrdev.hasNext()) {
PcapIf device = (PcapIf)itrdev.next();
StringBuilder sb = new StringBuilder();
sb.append(device.getName());
sb.append(";");
sb.append((device.getDescription() != null) ? device.getDescription() : "No description available");
sb.append("; MAC address:");
try {
if (device.getHardwareAddress() != null) {
byte[] mac = device.getHardwareAddress();
for (int i = 0; i < mac.length; i++) {
sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));
}
} else {
sb.append("No MAC address");
}
} catch (IOException e) {
System.err.printf("Can't read MAC address");
}
sb.append("; IP address(es):");
List<PcapAddr> addrs = device.getAddresses();
Iterator<PcapAddr> itraddr = addrs.iterator();
while(itraddr.hasNext()) {
PcapAddr pcapAddr = (PcapAddr)itraddr.next();
sb.append(pcapAddr.getAddr().toString());
}
System.out.printf("%sn", sb.toString());
}
return;
}
Листинг 1
В результате получаем перечень имён устройств, доступных для снятия статистики с их MAC и IP адресами. Выбираем интересующее нас устройство и указываем его в файле конфигурации ivTAPclient.properties:
sIntName=\Device\NPF_{7B767B766-A093-46CD-8000-EEEEEFFFFF88}
filterString=tcp port 80
srvAddr=<ivTAPsrv IP address>
srvPort=< ivTAPsrv UDP channel port number>
speedLimit=2000000
bandwidthCheckInterval=10
bandwidthBreachIntervals=6
Листинг 2
Помимо этого, файл конфигурации содержит строку BPF фильтра для интересующего нас трафика, адрес и порт серверной части приложения ivTAPsrv, предельно допустимую скорость передачи (в битах в секунду), интервал проверки этой скорости (в секундах), а также, предельно допустимое количество последовательных нарушений speedLimit, требуемое для остановки программы во избежание переполнения линии связи WAN. В данном примере программа завершит свою работу, если в течение шести измерений подряд, сделанных с 10-секундным интервалом скорость передачи будет выше 2 Mbps.
Программа предусматривает создание бесконечного цикла записи сетевых пакетов:
pcapIn.loop(-1, jpacketHandler, "ivTAP");
Создание экземпляра pcapIn:Pcap ничем особенным не примечательно, за исключением параметра timeout. Я выставил его в 10 миллисекунд, что даёт дополнительную погрешность, описанную выше. В идеале, было бы хорошо вообще обойтись без таймаута, но это может конфликтовать с базовыми принципами libpcap/winpcap. В документации написано, что установка нуля переводит Pcap в режим, когда данные не передаются пока не будет заполнен весь буфер записи. Величина этой задержки спорна и взята скорее из моих представлений о работе системы как наименьшая из безопасных.
Перед созданием бесконечного цикла записи надо позаботиться об описании обработчика каждого записанного пакета (см. Листинг 3).
public class PHandler<T> implements PcapPacketHandler<String> {
//Avoid excessive instantiations within endless loop
private Tcp tcp = new Tcp(); // Preallocate a Tcp header
private Ip4 ip = new Ip4(); // Preallocate a IP header
private int size;
@SuppressWarnings("unused")
private T user;
private static Logger log = Logger.getLogger(PHandler.class.getName());
public PHandler(T user) {
this.setUser(user);
}
public void setUser(T user) {
this.user = user;
}
@Override
public void nextPacket(PcapPacket packet, String user) {
if (packet.hasHeader(ip) && packet.hasHeader(tcp)) {
if (log.isLoggable(Level.FINE)) {
log.fine("Received packet len=" + String.valueOf(packet.getCaptureHeader().wirelen()) +
" source_IP=" + FormatUtils.ip(ip.source()) +
" source_port=" + String.valueOf(tcp.source()) +
" destination_IP=" + FormatUtils.ip(ip.destination()) +
" destination_port=" + String.valueOf(tcp.destination()));
}
//preparing to send
size = packet.size();
ByteBuffer byteBuffer = ByteBuffer.allocate(size);
packet.transferTo(byteBuffer);
IVTAP.bytesTransferred += size;
if (IVTAP.bytesTransferred >= 8000000000000000000L) {
IVTAP.bytesTransferred = 0L;
}
//sending UDP
byteBuffer.flip();
try {
IVTAP.udpChannel.send(byteBuffer, IVTAP.dstaddr);
} catch (IOException e) {
log.log(Level.SEVERE, "Exception: ", e);
}
}
}
}
Листинг 3
Всё просто: читаем пакет и посылаем его по udpChannel в сторону серверной части приложения ivTAPsrv. С серверной стороны, наоборот: получаем пакет по UDP, отправляем в заданный сетевой интерфейс функцией sendPacket (см. Листинг 4).
try {
DatagramSocket listener = new DatagramSocket(bindsocketaddr);
try {
DatagramPacket udppacket = null;
System.out.println("waitnig for packets...");
byte[] message = new byte[65536];
udppacket = new DatagramPacket(message, message.length);
while (true) {
try {
listener.receive(udppacket);
if (pcapOut.sendPacket(udppacket.getData(),0,udppacket.getLength()) == -1) {
System.err.println(pcapOut.getErr());
if (pcapOut.getErr().equals("send: Message too long")) {
System.err.println("Disable TCP segmentation offload at the source interface");
}
return;
}
} catch (IOException e) {
e.printStackTrace();
}
}
} finally {
listener.close();
}
} catch (SocketException e) {
e.printStackTrace();
}
Листинг 4
Ещё один важный момент, который надо учесть – это создание обработчиков завершения работы программы. Поскольку на клиентской и серверной частях приложения используются бесконечные циклы, нужно прописать ShutdownHook для соответствующих потоков и закрыть там все открытые устройства, сокеты и дочерние потоки.
Функционал контроля нагрузки на линию связи реализован на клиентской части приложения ivTAPclient в отдельном потоке. Через равные интервалы времени, определяемые в файле конфигурации, этот поток проверяет количество переданных данных в единицу времени. При достижении граничных условий он просто останавливает запись и закрывает программу, делая соответствующие пометки в логе.
Используя подобный подход для снятия и централизованного анализа сетевого трафика, нужно учитывать некоторые ограничения, которые он накладывает:
- Мы фактически удваиваем нагрузку на линию связи, создаваемую пользователями измеряемых систем. Хотя front-end трафик отдельных приложений обычно не велик (мне почти всегда хватает лимита в 2 Mbps), нужно заранее позаботиться о проверке свободной полосы пропускания из облака к серверу сбора данных, расположенного в вашем дата-центре.
- Если сетевой jitter между ivTAPclient и ivTAPsrv превышает порог необходимой точности измерений, вы не сможете использовать это решение.
- На front-end сервере, где установлено приложение ivTAPclient нужно выключить network offload. Иначе собранный вами материал будет существенно искажён.
- Трафик, снятый со SPAN-порта, попадает в приёмную очередь пакетов сетевого адаптера (Rx). Трафик ivTAPsrv можно добавить только в исходящую очередь (Tx). Я так и не смог решить задачу перенаправления пакетов ivTAPsrv в Rx queue. Средствами libpcap/winpcap она не решается, нужно переписывать сетевой драйвер. С другой стороны, это означает, что если на сервере используются стандартные сетевые драйвера, то и сторонний сервис сбора статистики примет данные из обоих очередей. Д
- Если между пользователем и front-end сервером стоит reverse-proxy (например, на балансировщике нагрузки), то в анализируемом трафике вы не увидите реальных TCP-сессий пользователей. Соответственно, будет невозможно измерить их сетевые метрики (см. Рисунок 5)
Рисунок 5
Безопасность
Вопросы безопасности применяемого решения не раз поднимались в ходе наших внутренних дискуссий. С одной стороны, мы фактически дублируем пользовательский трафик, в котором потенциально может содержаться важная коммерческая информация. Таким образом, имея доступ к отражённому трафику, злоумышленник потенциально может её расшифровать. С другой стороны, этот отражённый поток проходит по внутренним защищённым линиям связи. Если предположить, что злоумышленник имеет к ним доступ, то в его арсенале уже есть более простые и универсальные методы получения желаемой информации.
В любом случае, существует множество относительно простых способов зашифровать передаваемые данные слегка модифицировав алгоритм упаковки пакетов в канал UDP. Их следует применять если мы по каким-либо причинам не доверяем наши линиям связи.
Перспективы и выводы
Несмотря на серию успешных внедрений, я не собираюсь останавливаться на достигнутом. Есть ряд необходимых улучшений, которые помогут перевести эту разработку в категорию enterprise-приложений.
Прежде всего, нужно добавить средства мониторинга и оповещения о нештатных состояниях на ivTAPsrv, избежав при этом избыточного усложнения логики клиентской части приложения. В частности, хотелось бы избежать необходимости обратной связи от ivTAPsrv к ivTAPclient. Для этого будет достаточно периодически встраивать в поток передаваемых данных контрольные пакеты. В них указать объём считанной информации и время посылки этого контрольного пакета. Таким образом, на сервере ivTAPsrv можно было бы обнаруживать нештатные отключения агентов ivTAPclient, оценивать временную погрешность или потерю UDP-пакетов статистики. Можно также создать web-интерфейс управления для ivTAPsrv, не утяжелив его чрезмерно. Да и сам код, наверняка нуждается в свежем взгляде и оптимизации в соответствии с промышленными стандартами программирования.
Опыт создания этого продукта позволил мне по-новому взглянуть на свои возможности и на то, что предлагают клиентам крупные компании. Иногда правильный выбор заключается в том, чтобы, потратив сравнительно немного времени и узнав что-то новое, создать нужное решение самому вместо того, чтобы покупать менее удобное решение расходуя значительные ресурсы на внедрение и поддержку.
Автор: DaniilKochetov