...

пятница, 11 мая 2018 г.

Мониторинг сетевого трафика на серверах в облаке

В 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("%s\n", 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 в отдельном потоке. Через равные интервалы времени, определяемые в файле конфигурации, этот поток проверяет количество переданных данных в единицу времени. При достижении граничных условий он просто останавливает запись и закрывает программу, делая соответствующие пометки в логе.

Используя подобный подход для снятия и централизованного анализа сетевого трафика, нужно учитывать некоторые ограничения, которые он накладывает:

  1. Мы фактически удваиваем нагрузку на линию связи, создаваемую пользователями измеряемых систем. Хотя front-end трафик отдельных приложений обычно не велик (мне почти всегда хватает лимита в 2 Mbps), нужно заранее позаботиться о проверке свободной полосы пропускания из облака к серверу сбора данных, расположенного в вашем дата-центре.
  2. Если сетевой jitter между ivTAPclient и ivTAPsrv превышает порог необходимой точности измерений, вы не сможете использовать это решение.
  3. На front-end сервере, где установлено приложение ivTAPclient нужно выключить network offload. Иначе собранный вами материал будет существенно искажён.
  4. Трафик, снятый со SPAN-порта, попадает в приёмную очередь пакетов сетевого адаптера (Rx). Трафик ivTAPsrv можно добавить только в исходящую очередь (Tx). Я так и не смог решить задачу перенаправления пакетов ivTAPsrv в Rx queue. Средствами libpcap/winpcap она не решается, нужно переписывать сетевой драйвер. С другой стороны, это означает, что если на сервере используются стандартные сетевые драйвера, то и сторонний сервис сбора статистики примет данные из обоих очередей. Д
  5. Если между пользователем и front-end сервером стоит reverse-proxy (например, на балансировщике нагрузки), то в анализируемом трафике вы не увидите реальных TCP-сессий пользователей. Соответственно, будет невозможно измерить их сетевые метрики (см. Рисунок 5)


Рисунок 5

Безопасность


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

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

Перспективы и выводы


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

Прежде всего, нужно добавить средства мониторинга и оповещения о нештатных состояниях на ivTAPsrv, избежав при этом избыточного усложнения логики клиентской части приложения. В частности, хотелось бы избежать необходимости обратной связи от ivTAPsrv к ivTAPclient. Для этого будет достаточно периодически встраивать в поток передаваемых данных контрольные пакеты. В них указать объём считанной информации и время посылки этого контрольного пакета. Таким образом, на сервере ivTAPsrv можно было бы обнаруживать нештатные отключения агентов ivTAPclient, оценивать временную погрешность или потерю UDP-пакетов статистики. Можно также создать web-интерфейс управления для ivTAPsrv, не утяжелив его чрезмерно. Да и сам код, наверняка нуждается в свежем взгляде и оптимизации в соответствии с промышленными стандартами программирования.

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

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий