Трассировка и мониторинг в Istio: микросервисы и принцип неопределенности

    Принцип неопределенности Гейзенберга гласит, что нельзя одновременно измерить положение объекта и его скорость. Если объект движется, то у него нет местоположения. А если местоположение есть – значит у него нет скорости.



    Что касается микросервисов на платформе Red Hat OpenShift (и под управлением Kubernetes), то благодаря соответствующему софту с открытым кодом они могут одновременно рапортовать как о своей производительности, так и об исправности. Старика Гейзенберга это, конечно, не опровергает, но зато устраняет неопределенность при работе с облачными приложениями. Istio позволяет легко организовать отслеживание (трассировку) и мониторинг таких приложений, чтобы держать все под контролем.

    Определяемся с терминологией


    Под трассировкой (Tracing) мы понимаем логирование системной активности. Звучит довольно общо, но на самом деле одно из основных правил здесь в том, чтобы сбрасывать данные трассировки в соответствующее хранилище, не заботясь об их форматировании. А вся работа по поиску и анализу данных возлагается на их потребителя. В Istio используется система трассировки Jaeger, реализующая модель данных OpenTracing.

    Трассами (Traces, и слово «трассы» здесь используется в значении «следы», как например, в баллистической экспертизе) мы будем называть данные, которые полностью описывают прохождение запроса или единицу работы, как говорится, «от и до». Например, всё, что происходит с момента, когда пользователь жмет кнопку на веб-странице, и до момента возврата данных, включая все задействованные при этом микросервисы. Можно сказать, что одна трасса полностью описывает (или моделирует) прохождение запроса туда и обратно. В интерфейсе Jaeger трассы раскладывается на составляющие по оси времени, вроде того, как цепь можно разложить на отдельные звенья. Только вместо звеньев трасса состоит из так называемых span’ов.

    Span – это интервал от начала выполнения единицы работы до ее завершения. Продолжая аналогию, можно сказать, что каждый span представляет собой отдельное звено цепи. Span может иметь (или не иметь) один или несколько дочерних span’ов. Как следствие, span самого верхнего уровня (root span) будет иметь ту же общую продолжительность, что и трасса, к которой он относится.

    Мониторинг – это, собственно, само наблюдение за вашей системой – глазами, через UI или средствами автоматизации. В основе мониторинга лежат данные трассировки. В Istio мониторинг реализован средствами Prometheus и имеет соответствующий UI. Prometheus поддерживает автоматический мониторинг с использованием оповещений Alerts и Alert Managers.

    Оставляем зарубки


    Чтобы трассировка стала возможной, приложение должно создать коллекцию span’ов. Потом их надо экспортировать в Jaeger, чтобы тот в свою очередь создал визуальное представление трассировки. Среди прочего эти span’ы маркируют имя операции, а также временны метки ее начала и завершения. Передача span’ов выполняется путем переадресации предназначенных для Jaeger заголовков HTTP-запросов от входящих запросов к исходящим запросам. В зависимости от используемого языка программирования для этого может потребоваться небольшая модификации исходного кода приложений. Ниже приводится пример кода на Java (при использовании фреймворка Spring Boot), который добавляет заголовки B3 (Zipkin-style) к вашему запросу в конфигурационном классе Spring:


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


    Если вы используете Java, то код можно не трогать, а просто добавить несколько строк в POM-файл Maven и задать переменные окружения. Вот какие строки надо добавить в файл POM.XML, чтобы внедрить Jaeger Tracer Resolver:


    А соответствующие переменные среды задаются в Dockerfile:


    Всё, теперь всё настроено, и наши микросервисы начнут генерировать данные трассировки.

    Смотрим в общих чертах


    В состав Istio входит простенькая контрольная панель на основе Grafana. Когда все настроено и работает на платформе Red Hat OpenShift PaaS (в нашем примере Red Hat OpenShift и Kubernetes развернуты на minishift), эта панель запускается следующей командой:

    open "$(minishift openshift service grafana -u)/d/1/istio-dashboard?refresh=5⩝Id=1"
    

    Панель Grafana позволяет быстро оценить работу системы. Фрагмент этой панели показан на рисунке ниже:


    Здесь видно, что микросервис customer вызывает микросервис preference v1, а тот в свою очередь вызывает микросервисы recommendation v1 и v2. На панели Grafana есть блок Dashboard Row для высокоуровневых метрик, таких как общее количество запросов (Global Request Volume), доля успешных запросов (success rates), ошибки 4xx. Кроме того, там есть представление Server Mesh с графиками для каждого сервиса и блок Services Row для просмотра подробных сведений по каждому контейнеру для каждого сервиса.

    Теперь копнем поглубже


    При грамотно настроенной трассировке Istio, что называется, прямо из коробки позволяет углубиться в анализ производительности системы. В Jaeger’овском UI можно просматривать трассировки и видеть, как далеко и глубоко они уходят, а также визуально локализовать узкие места в производительности. При использовании Red Hat OpenShift на платформе minishift запуск Jaeger UI выполняете следующей командой:

    minishift openshift service jaeger-query --in-browser
    


    Что можно сказать про трассировку на этом скрине:

    • Она разбивается на 7 span’ов.
    • Общее время выполнения составляет 6.99 ms.
    • На микросервис recommendation, который является последним в цепочке, тратится 0.69 ms.

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

    А теперь усложним задачу и запустим два экземпляра микросервиса recommendation:v2 командой oc scale --replicas=2 deployment/recommendation-v2. Вот какие у нас после этого будут pod’ы:


    Если теперь переключиться обратно в Jaeger и развернуть span для сервиса recommendation, мы увидим, на какой pod маршрутизируются запросы. Таким образом, мы может легко локализовать тормоза на уровне конкретного pod‘а. Смотреть при этом надо на поле node_id:


    Куда и как всё ходит


    Теперь переходим в интерфейс Prometheus и вполне ожидаемо видим там, что запросы между второй и первой версиями сервиса recommendation делятся в отношении 2:1, строго по количеству работающих pod’ов. Причем этот график будет динамически меняться при масштабировании pod’ов вверх-вниз, что будет особенно полезно при Canary Deployment (мы подробнее рассмотрим эту схему развертывания в следующий раз).


    Всё только начинается


    На самом деле сегодня мы, что называется, лишь слегка затронули кладезь полезной информации о Jaeger, Grafana и Prometheus. В общем-то это и была наша цель – направить вас в нужном направлении и приоткрыть перспективы Istio.

    И помните, все это уже встроено в Istio. При использовании определенных языков программирования (например, Java) и фреймворков (например, Spring Boot) всё это можно реализовать, совершенно не трогая сам код приложений. Да, код придется слегка модифицировать, если вы используете другие языки, в первую очередь имеются в виду Nodejs или C#. Но поскольку отслеживаемость (читай, «трассировка») является одним из обязательных условий при создании надежных облачных систем, вам в любом случае придется править код, есть у вас Istio или нет. Так почему бы не потратить усилия с большей пользой?

    Хотя бы для того, чтобы всегда отвечать на вопросы «где?» и «как быстро?» со 100% определенностью.

    Хаос-инжиниринг в Istio: так и было задумано


    Умение ломать вещи помогает сделать так, чтобы они не ломались


    Тестирование софта – вещь не только сложная, но и важная. В то же время тестирование на корректность (например, возвращает ли функция верный результат) – это одно, а тестирование в условиях ненадежной сети – это совсем другая задача (часто считается, что сеть всегда работает без сбоев, и это первейшее из восьми заблуждений относительно распределенных вычислений). Одна из сложностей при решении этой задачи заключается в том, как имитировать сбои в системе или вносить их намеренно, выполняя так называемый fault injection. Это можно делать путем модификации исходного кода самого приложения. Но тогда вы будете тестировать уже не свой изначальный код, а его версию, которая специально имитирует сбои. В результате вы рискуете угодить в смертельные объятия fault injection и столкнуть с гейзенбагами – сбоями, которые исчезают при попытке их обнаружить.

    А теперь мы покажем, как Istio помогает справиться с этими сложностями на раз-два.

    Как всё выглядит, когда всё отлично


    Рассмотрим следующий сценарий: у нас есть два pod’а для нашего микросервиса recommendation, который мы взяли из учебника по Istio. Один pod помечен как v1, а другой – как v2. Как видим, пока всё работает отлично:


    (Кстати, число справа – это просто счетчик вызовов для каждого pod’а)

    Но нам ведь нужно отнюдь не это, верно? Что ж, попробуем всё сломать, совершенно не трогая исходный код.

    Устраиваем перебои в работе микросервиса


    Ниже приведен yaml-файл для правила маршрутизации Istio, которое в половине случаев будет выдавать сбой (ошибку сервера 503):


    Обратите внимание, мы явно прописываем, что в половине случаев должна возвращаться ошибка 503.

    А вот как будет выглядеть скриншот запущенной в цикле команды curl после того, как мы активируем это правило, чтобы имитировать сбои. Как видим, половина запросов возвращает ошибку 503, причем вне зависимости от того, на какой pod – v1 или v2 – они уходят:


    Для восстановления нормальной работы достаточно удалить это правило, в нашем случае командой istioctl delete routerule recommendation-503 -n tutorial. Здесь Tutorial – это имя проекта Red Hat OpenShift, в котором работает наш учебник по Istio.

    Вносим искусственные задержки


    Искусственные ошибки 503 помогают протестировать систему на устойчивость к сбоям, но способность прогнозировать и обрабатывать задержки должна впечатлить вас еще больше. Да и задержки в реальной жизни случаются чаще, чем сбои. Медленно работающий микросервис – это яд, от которого страдает вся система. Благодаря Istio можно протестировать код, относящийся к обработке задержек, никак его при этом не меняя. Для начала мы покажем, как это сделать в случае искусственно введенных задержек сети.

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

    Вот как выглядит правило, которое… Хотя знаете что? Istio так прост, а этот yaml-файл так понятен, что всё в этом примере говорит само за себя, просто взгляните:


    В половине случаев у нас будет возникать 7-секундная задержка. И это вовсе не то же самое, как если бы мы вставили в исходный код команду sleep, поскольку Istio реально задерживает запрос на 7 секунд. Поскольку Istio поддерживает трассировку Jaeger, эта задержка отлична наблюдается в Jaeger’оском UI, как показано на скрине ниже. Обратите внимание на долгий запрос в правом верхнем углу диаграммы – его длительность составляет 7.02 секунды:


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

    Не отступать и не сдаваться


    Другая полезная для хаос-инжиниринга функция Istio – это повторные обращения к сервису заданное число раз. Смысл здесь в том, чтобы не прекращать попытки, когда первый запрос заканчивается ошибкой 503 – и тогда, возможно, в N-надцатый раз нам повезет. Может быть, сервис просто ненадолго прилег по той или иной причине. Да, эту причину надо бы раскопать и устранить. Но это потом, а пока что попробуем сделать так, чтобы система продолжала работать.

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

    Стоп, погодите! Мы же только что это делали.

    Вот этот файл сделает так, что сервис recommendation-v2 будет в половине случаем выдавать ошибку 503:


    Очевидно, что часть запросов будет заканчиваться неудачей:


    А теперь задействуем Istio-функцию Retry:


    Это правило маршрутизации делает три повтора с двухсекундным интервалом и должно сократить (а в идеале и вовсе убрать с радара) ошибки 503:


    Резюмируем: мы сделали так, что Istio, во-первых, генерит ошибку 503 для половины запросов. А во-вторых, тот же Istio выполняет три попытки повторно связаться с сервисом при возникновении ошибки 503. В результате все работает просто отлично. Таким образом, используя функцию Retry, мы выполнили свое обещание не отступать и не сдаваться.

    И да, мы опять сделали это, совершенно не трогая код. Всё, что нам понадобилось, – это два правила маршрутизации Istio:


    Как не подвести пользователя или семеро одного не ждут


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

    В Istio можно задать таймаут выполнения запроса. Если сервис превышает этот таймаут, возвращается ошибка 504 (Gateway Timeout) – опять же все это делается через конфигурацию Istio. Но нам придется добавить в исходный код сервиса команду sleep (а затем, конечно, выполнить rebuild и redeploy), чтобы имитировать медленную работу сервиса. Увы, иначе не получится.

    Итак, мы вставили трехсекундный sleep в код сервиса recommendation v2, пересобрали соответствующий образ и сделали редеплой контейнера, а теперь добавим таймаут с помощью следующего правила маршрутизации Istio:


    На скрине выше видно, что мы бросаем попытки связаться с сервисом recommendation, если не получаем ответа в течение одной секунды, то есть еще до того, как возникнет ошибка 504. После применения этого правила маршрутизации (и добавления трехсекундного sleep’а в код сервиса recommendation:v2), мы получим вот что:


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

    А теперь всё вместе


    Внести немного хаоса с помощью Istio – это отличный способ протестировать ваш код и надежность вашей системы в целом. Шаблоны fallback, bulkhead и circuit breaker, механизмы создания искусственных сбоев и задержек, а также повторные вызовы и таймауты будут очень полезны при создании отказоустойчивых облачных систем. В сочетании с Kubernetes и Red Hat OpenShift эти инструменты помогут уверенно встретить будущее.
    Red Hat
    Программные решения с открытым исходным кодом

    Похожие публикации

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

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое