Многие современные приложения состоят из множества микросервисов, которые выполняются в контейнерах в распределённой системе, локально и в облаке. В этом контексте service mesh — инфраструктурный уровень, который отвечает за безопасность, взаимодействие и мониторинг микросервисов. Как дополнительный уровень влияет на задержки при передаче трафика?

В реальных приложениях API-вызов к микросервису может привести к вызову нескольких сервисов, например, для доступа к базе данных. Большая задержка не понравится пользователям и плохо скажется на SEO-оптимизации.

В этой статье мы сравним производительность разных реализаций service mesh. Ниже приводится базовая схема инфраструктуры service mesh: прокси перехватывают трафик и применяют набор заданных функций, например, для маршрутизации, контроля доступа, наблюдаемости, балансировки нагрузки. Control plane предоставляет политики и конфигурации для всех прокси в service mesh и следит за всеми сервисами. Он динамически обновляет прокси, когда меняются правила или окружения.

Типичная инфраструктура service mesh
Типичная инфраструктура service mesh

Мы сравнили базовые конфигурации service mesh, постаравшись подобрать эквиваленты, чтобы сравнение имело смысл. У нас также есть эталонный вариант без service mesh.
Затем мы добавили дополнительные функции service mesh и измерили их влияние на производительность. Мы сравнили следующие решения:

Репозиторий кода: https://github.com/ELCAIT/service_mesh_performance

Тестируемый сервис

Для бенчмаркинга мы создали простой микросервис Java REST API с использованием Spring Boot: counter-api.

Микросервис counter-api — это простой счётчик, который начинается с нуля и увеличивается на единицу с каждый запросом. Ответ поступает в JSON.

Генератор запросов

Для нагрузочного тестирования мы использовали Locust. Это приложение на Python, которое создаёт запросы с заданным значением запросов в секунду (Requests sent Per Second, RPS). Для развёртывания и настройки сервиса Locust внутри кластера Kubernetes используется образ Docker. Мы можем настроить Locust для отправки в сервис HTTP-запросов с заданной скоростью. Locust хорош тем, что можно развернуть в кластере несколько рабочих экземпляров, а измерения собирать в мастере, как на иллюстрации. Так мы получаем больше пропускной способности и гибкости при создании запросов.

Мастер-экземпляр Locust собирает метрики с рабочих экземпляров. Рабочие экземпляры Locust создают запросы к целевым сервисам и измеряют задержку.
Мастер-экземпляр Locust собирает метрики с рабочих экземпляров. Рабочие экземпляры Locust создают запросы к целевым сервисам и измеряют задержку.

Тестирование с открытым и замкнутым циклом

При открытом цикле исходящие запросы генерируются с учётом распределения Пуассона. Следующие запросы отправляются независимо от выполнения предыдущих. При замкнутом цикле новые запросы запускаются только после завершения предыдущих.

Каждый рабочий экземпляр Locust представляет отдельного пользователя. Рабочий экземпляр Locust не работает по принципу открытого цикла — частота запросов в секунду ограничена максимальной частотой ответов тестируемого сервиса. Пока ответ на предыдущий запрос не получен, следующий запрос не отправляется. Для тестирования используется всего несколько экземпляров Locust, поэтому вся система работает в замкнутом цикле.

Метрики

Мы определили несколько метрик, по которым будем сравнивать разные реализации service mesh.

Полная задержка запроса

Задержка между вызовом сервиса и получением ответа в приложении. Единица: миллисекунды.

Потребление ОЗУ и ЦП

Мы измерили потребление оперативной памяти и ресурсов процессора для тестируемого сервиса и sidecar’а.
Единицы ОЗУ: МиБ.
ЦП: милли ЦП (mCPU).

Как видно на рисунке, sidecar-прокси четырежды перехватывают трафик за HTTP-запрос:

Измеряется полное время RTT. Sidecar-прокси четыре раза перехватывают трафик.
Измеряется полное время RTT. Sidecar-прокси четыре раза перехватывают трафик.

Сравнение service mesh

Между реализациями есть различия, и их конфигурации по умолчанию не эквивалентны. Для адекватного сравнения мы постараемся их уравнять:

  • Повторы запросов и таймауты имеют разные дефолтные значения в разных реализациях. Мы отключили эти возможности для более корректного сравнения с эталонной версией без service mesh.

  • ЦП и ОЗУ, потребляемые sidecar-прокси, тоже различаются. Обычно sidecar-прокси потребляет от 50 до 150 mCPU. Лимит ЦП разнится от 325 (Linkerd) до 2000 (Istio) mCPU или может быть не задан (Consul). Мы установили лимит в 200 mCPU (конфигурация доступна здесьздесь и здесь).
    Во время сравнения этот лимит не был достигнут.

  • Функции безопасности и наблюдаемости по умолчанию по-разному настроены в разных service mesh. Для чистоты сравнения мы отключили функции mTLS и наблюдаемости.

Влияние разных возможностей service mesh

Наконец, мы добавляли разные возможности, чтобы измерить их влияние на производительность:

  • Только mTLS для всего трафика между сервисами.

  • mTLS + наблюдаемость по умолчанию.
    Мы добавили дефолтные конфигурации наблюдаемости и метрик. Обычно такие конфигурации не подходят для продакшена и представлены в руководствах по началу работы. Из-за ограничений по времени мы не меняли дефолтные конфигурации, чтобы сбалансировать разные конфигурации service mesh, поэтому можно измерить только влияние наблюдаемости, но нет смысла сравнивать разные реализации service mesh с включённой функцией наблюдаемости.

  • ТОЛЬКО ДЛЯ ISTIO И KUMA: Включена аутентификация через JWT. Мы использовали два подхода: аутентификация Istio JWT  и кастомный фильтр Envoy , настроенный в Istio вручную и в Kuma с помощью ProxyTemplate.

  • ТОЛЬКО ДЛЯ ISTIO: Open Policy Agent (OPA) добавлен в Istio в качестве стороннего поставщика авторизации и также настроен для аутентификации через JWT. Этот вариант стоит измерить, потому что sidecar-прокси приходится выполнять дополнительный запрос в локальной сети к точке принятия решения о доступе (Policy Decision Point, PDP) агента OPA для каждого запроса. Лишний запрос увеличит задержку.

Интеграции OPA с Envoy. Контейнер OPA добавляется к каждому pod'у как точка принятия решения о доступе. Sidecar-прокси Istio (Envoy) будет точкой реализации политики (Policy Enforcement Point, PEP).
Интеграции OPA с Envoy. Контейнер OPA добавляется к каждому pod'у как точка принятия решения о доступе. Sidecar-прокси Istio (Envoy) будет точкой реализации политики (Policy Enforcement Point, PEP).

Тестовое окружение

Для тестирования мы использовали кластер AWS Elastic Kubernetes Service (EKS).

  • Количество нод: 9.

  • Характеристики нод: инстанс Amazon t3.medium EC2.

  • Тестируемый микросервис counter-api развёртывается только на нодах 1 и 2.

  • Генератор запросов Locust развёртывается только на нодах с 3 по 9. Таким образом Locust и тестируемый микросервис не будут конкурировать за ресурсы.
    У Locust больше ресурсов (7 нод), чтобы он не создавал узких мест при измерениях. Тестируемый сервис настолько простой и лёгкий, что требуется несколько экземпляров Locust, чтобы нагрузить его.

Методология

Все запросы отправляются в микросервис counter-api.

  • Количество pod'ов counter-api: 4.

  • Значение ЦП на pod: 150 mCPU. Лимит ЦП: 325 mCPU.

  • Значение ОЗУ на pod: 128 МиБ Лимит памяти: 256 МиБ

Шаг 1. Вычисляем точку насыщения

Вычислив точку насыщения, мы поймём, стоит ли тестировать сервис при 500 RPS. Это близко к предельной нагрузке? Или это мало для сервиса?

У нас модель с замкнутым циклом и установленным ограничением по количеству запросов в секунду. Чтобы вычислить точку насыщения тестируемого сервиса, выполняем первый тест на всех вариантах service mesh.

Генератор запросов Locust сначала отправляет запросы на низкой скорости (RPS = 100). Со временем это число будет увеличиваться. Близко к точке насыщения значение RPS перестанет расти и может даже снизиться, если перегруженная система перестанет отвечать на какое-то время. Значение RPS в точку времени t в секундах рассчитывается как число ответов, полученных в точке t.

На графике приводятся значения RPS с течением времени. Поначалу значение RPS должно увеличиваться линейно, а близко к точке насыщения оно перестанет расти и даже начнёт колебаться.

Тест проводился в течение 70 секунд для каждой service mesh и эталона.

Примечание. В первом раунде мы взяли четыре экземпляра counter-api и два рабочих экземпляра Locust. Точка насыщения была измерена близко к 250 RPS. Медианные задержки, измеренные на ~25 RPS и ~250 RPS, существенно не отличались друг от друга. Мы предположили, что узким местом стал генератор Locust, у которого насыщение наступало раньше, чем у сервиса counter-api. Мы настроили Locust для сбора и логирования задержки каждого запроса. Конечно, это влияло на производительность, но нужно было собрать все необработанные данные, чтобы создать графики и метрики. Проблему можно было легко решить, добавив больше рабочих экземпляров Locust. Мы повторили тесты, использовав больше 20 рабочих экземпляров Locust.

Чтобы значение RPS не ограничивалось генератором запросов Locust, мы использовать много рабочих экземпляров Locust (pod'ов).

Мы увеличивали количество рабочих экземпляров Locust, пока точка насыщения системы не перестала подниматься. Так мы гарантировали, что Locust не будет узким местом.

Шаг 2. Тестирование

Мы тестировали разные решения в течение 70 секунд, каждый раз с разными значениями RPS, которые возрастали близко к точке насыщения.

Мы измеряли задержку ответа на каждый запрос. В первые 5 секунд мы не учитывали задержку, давая service mesh время на инициализацию и стабилизацию. Первые запросы действительно требовали гораздо больше времени, возможно, потому что некоторые компоненты ещё только запускались.

Для каждого раунда тестирования мы удаляли и заново устанавливали на нодах все инструменты бенчмаркинга, service mesh и приложения.

Результаты

1. Точка насыщения

Как видно на рисунке ниже, значение RPS возрастает линейно примерно до ~1500 RPS, а затем кривая начинает колебаться. Примерно на 1800 RPS мы видим точку насыщения. При 20 экземплярах Locust значения RPS не превышают 1750. При 25 экземплярах мы видим даже 2000 RPS. Как и при 30 экземплярах Locust. Значение RPS не возрастает при увеличении числа экземпляров Locust с 25 до 30. Получается, что при 30 экземплярах Locust точно перестаёт быть узким местом.

Сравнение точек насыщения
Сравнение точек насыщения

2. Сравнение service mesh (без mTLS, без наблюдаемости)

Мы протестировали сервис counter-api с разными значениями RPS: 100, 200, 400, 700, 1000, 1200, 1500, 1800. Нам не всегда удавалось достичь ожидаемого значения RPS, особенно 1800 RPS близко к точке насыщения некоторых реализаций service mesh.
Самые интересные результаты приводятся в сводной таблице ниже. Полные данные см. в репозитории GitHub. Все значение приводятся в мс.

Задержка в разных service mesh с базовой конфигурацией на разных значениях RPS. Фактические значения RPS отличаются от ожидаемых, особенно близко к точке насыщения. Значения задержек приводится в мс.
Задержка в разных service mesh с базовой конфигурацией на разных значениях RPS. Фактические значения RPS отличаются от ожидаемых, особенно близко к точке насыщения. Значения задержек приводится в мс.

Наблюдения

  • Нам не всегда удавалось достичь ожидаемого значения RPS. Это может быть связано с насыщением тестируемого сервиса, который какое-то время отвечает на запросы медленнее.

  • При низкой нагрузке (около 200 RPS) реализации service mesh увеличивают медианную задержку на 0,7–1,5 мс по сравнению с системой без service mesh. Средняя задержка почти не изменилась. Ниже приводятся графики кумулятивной функции распределения (CDF) и дополнительной кумулятивной функции распределения (CCDF). Дополнительная кумулятивная функция распределения особенно полезна для оценки хвоста распределения: 90, 99, 99,9 и 99,99 перцентили.

CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 100
CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 100
CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 200
CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 200
  • При средней нагрузке (около 700 RPS) медианная задержка увеличивается на 5–25 мс по сравнению с эталоном. Ниже приводятся графики кумулятивной функции распределения (CDF) и дополнительной кумулятивной функции распределения (CCDF).

CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 700
CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 700
  • При высокой нагрузке (около 1200 RPS) медианная задержка увеличивается на 10–50 мс по сравнению с эталоном. Ниже приводятся графики кумулятивной функции распределения (CDF) и дополнительной кумулятивной функции распределения (CCDF).

CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 1200
CDF и CCDF: сравнение service mesh в базовой конфигурации с эталоном. RPS = 1200
  • С увеличением значения RPS sidecar-прокси потребляет больше оперативной памяти и ЦП, как показано в приложении в репозитории GitHub. При этом sidecar всё же потребляет мало ресурсов, особенно по сравнению с сервисом counter-api. Istio и Linkerd потребляют больше всего ЦП. Возможно, поэтому у этих двух service mesh производительность лучше близко к точке насыщения.

  • Средняя задержка иногда вдвое больше медианной. Это связано с долгими запросами, число которых возрастает вместе с RPS при насыщении сервиса.

  • Система без service mesh показывает лучшую медианную задержку, а средняя задержка иногда лучше у системы с service mesh. Это объясняется тем, что service mesh повышает эффективность и, вероятно, балансировка нагрузки тоже работает лучше, что позволяет избежать запросов с большой задержкой. Действительно, балансировщик нагрузки в service mesh может выбирать экземпляр сервиса по наименьшему числу активных запросов. Это снижает риск насыщения сервиса при низком значении RPS. Без service mesh балансировщик нагрузки Kubernetes выполняет алгоритм round-robin по умолчанию, который может работать не так эффективно.

3. Сравнение service mesh (с mTLS, без наблюдаемости)

Подобные результаты и различия в задержке мы наблюдали со включённым mTLS. Мы видим, что функция mTLS особо не влияет на производительность во всех вариантах теста. Полный отчет см. в репозитории GitHub.

4. Влияние разных возможностей service mesh

Мы протестировали четыре реализации service mesh с разными конфигурациями, чтобы оценить влияние некоторых функций на производительность. В таблице ниже приводятся результаты для Istio. Для других service mesh мы получили похожие результаты. Их можно посмотреть в репозитории GitHub. Все значение приводятся в мс.

CDF и CCDF: сравнение Istio с разными конфигурациями. RPS = 200

CDF и CCDF: сравнение Istio с разными конфигурациями. RPS = 700
CDF и CCDF: сравнение Istio с разными конфигурациями. RPS = 700
CDF и CCDF: сравнение Istio с разными конфигурациями. RPS = 1200
CDF и CCDF: сравнение Istio с разными конфигурациями. RPS = 1200

Как видите, добавление функций не особенно влияет на производительность. Мы не заметили явного паттерна, разве что некоторый шум в измерениях. Исключение — аутентификация через токен JWT с Open Policy Agent (OPA), при которой скорость заметно падает.

Заключение

При невысокой нагрузке на сервис (RPS < 500) быстрее всего работает Linkerd. Это заметно на медианных задержках и на перцентиле 99,9. Это ожидаемо, ведь Linkerd давно известен как самая лёгкая и быстрая service mesh. Istio не может похвастаться такой славой, поэтому удивительно наблюдать, как она показывает низкую задержку, особенно на высоких значениях RPS. Минус Istio в том, что она потребляет в два-три раза больше ресурсов ЦП по сравнению с остальными service mesh.

У Consul результаты почти не отличаются от остальных до RPS >200, а затем он начинает отставать.
Самые интересные результаты мы наблюдали с нагрузкой от низкой до средней. Облачные кластеры Kubernetes отличаются эластичностью и масштабируемостью, так что увеличение нагрузки не должно приводить к такому насыщению сервиса, как в бенчмарках. Мы заметили, что service mesh добавляет к медианной задержке от 0,5 до 2 мс. На среднюю задержку это почти не влияет. Более того, при RPS <200 средняя задержка иногда бывает ниже при использовании service mesh. Это был неожиданный результат, но его можно объяснить эффективным управлением трафиком в service mesh, особенно возможностью выбирать экземпляр сервиса по наименьшему числу активных запросов. Это снижает риск насыщения сервиса при низком значении RPS.

Мы не заметили каких-то закономерностей в изменении задержки при включении разных функций. Можно сделать вывод, что оператор service mesh может спокойно добавлять функции, не боясь серьёзно увеличить задержку. Задержка существенно возросла только при включении Open Policy Agent в Istio. Это ожидаемо, ведь точка принятия решений о доступе OPA добавляет дополнительный переход в локальной сети pod'a.

Наконец, sidecar-прокси не потребляют слишком много ресурсов. Дополнительные функции service mesh лишь незначительно влияют на потребление ЦП и ОЗУ. Однако если ресурсы ЦП ограничены, лучше не выбирать Istio.

Дальнейшая работа

Измерения и методологию можно улучшить для дальнейшего тестирования. Во-первых, было бы интересно использовать другой генератор нагрузки, не Locust, — желательно с открытым циклом и на C. На Python код может быть не таким эффективным и к измерениям может добавляться шум. Во-вторых, можно было выполнить больше итераций при тестировании. Например, чтобы измерить отклонения медианных значений в одинаковых условиях тестирования. Тенденция должна остаться такой же, как в начальных тестах, но можно повысить точность значений, точно измерить шум и найти статистические значимые различия между реализациями service mesh.

Авторы: Дан Юссефи (Dahn Youssefi), Флоран Мартен (Florent Martin)

О возможностях service mesh

Service mesh — позволяет гибко управлять трафиком и безопасностью соединения между микросервисами, а грамотно управлять самой технологией может не каждый специалист.

Просто внедрить технологию недостаточно. Service mesh — на самом низком уровне радикально меняет инфраструктуру. Если в момент эксплуатации что-то идёт не так, это обычно приводит к катастрофе — полные даунтаймы системы, серьезные последствия. Очень важно понимать, как все устроено и как в эксплуатации быть уверенным в том, что все работает в штатном режиме, и в случае проблем быстро это исправлять.

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

9-11 декабря пройдет 3-й поток интенсива service mesh от Александра Лукьянченко, руководителя юнита PaaS в Авито, и Георга Гаала, Senior Infrastructure Engineer в Workato.

Вы на практике изучите принцип работы технологии, решите реальные бизнес-кейсы и научитесь искать причины проблем. После вы:

  • Будете точно понимать, как правильно подходить к внедрению разных частей service mesh, и как сделать так, чтобы не ронять всю систему.

  • Разберетесь как в эксплуатации быть уверенным в том, что все работает в штатном режиме, а в случае проблем быстро это исправлять.

Как будет проходить интенсив?

Вы не просто посмотрите на то, какие фичи есть у service mesh, а попробуете с ними поработать в специально разработанном приложении онлайн-кинотеатра, состоящего из нескольких микросервисов.

Мы будем внедрять service mesh с нуля, а также смотреть, как это корректно делать именно в таких условиях, когда у нас уже что-то есть.

Большинство компаний будут внедрять не с нуля, и нам это важно отработать. Также все кейсы мы берем из реальной практики и будем последовательно закрывать возникающие проблемы с помощью service mesh.

Количество мест ограничено, поэтому успейте подать заявку на участие.

Комментарии (0)