Знакомимся с Access Logs и фильтрами в Envoy и Istio service mesh

Не у всех есть необходимость в тонкой настройке access logging в Envoy, но если она всё-таки возникает, то могут понадобиться примеры, которых почему-то не очень много в документации. Поэтому мы сделали перевод статьи, где вы можете познакомиться с Envoy, узнать, как включить журнал доступа (access log) Envoy в Istio, и научиться настраивать фильтры.

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

8e9dbc9b7452615207eacc7a122d209b.png

1. Краткое введение в Envoy

Envoy — это высокопроизводительная прокси и коммуникационная шина L7, разработанная на языке C++ и предназначенная для крупных современных сервис-ориентированных архитектур. Это самостоятельный процесс, предназначенный для работы рядом с любым сервером приложений.

По своей сути Envoy является сетевым прокси L3/L4. Подключаемый механизм цепочки фильтров позволяет писать фильтры для выполнения различных задач TCP/UDP-прокси и вставлять их в основной сервер.

Envoy поддерживает дополнительный уровень фильтров HTTP L7; HTTP-фильтры могут быть подключены к подсистеме управления HTTP-соединениями, выполняющей различные задачи, такие как буферизация, ограничение скорости, маршрутизация/переадресация и т.д.

При работе в режиме HTTP Envoy поддерживает подсистему маршрутизации, способную направлять и перенаправлять запросы на основе path, авторитета, типа содержимого, значений времени выполнения и т.д. Эта функциональность наиболее полезна при использовании Envoy в качестве front/edge proxy, но также может быть задействована при построении сетки «service-to-service».

Envoy обладает многими другими высокоуровневыми возможностями, но перечисленные выше делают его идеальным для использования в качестве sidecar proxy в service mesh.

С этим учебным пособием связаны два основных понятия о Envoy:

1.1 Управление HTTP-соединениями

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

Envoy имеет встроенный фильтр сетевого уровня — HTTP connection manager, который преобразует необработанные байты в сообщения и события уровня HTTP (например, полученные заголовки, полученные данные тела, полученные трейлеры и т.д.). Он также обрабатывает функции, общие для всех HTTP-соединений и запросов, такие как регистрация доступа, генерация и отслеживание идентификаторов запросов, работа с заголовками запросов/ответов, управление таблицами маршрутов и статистика.

1.2 Логирование доступа

При использовании в сценарии service-mesh, например, в Istio, простейшим видом протоколирования является протоколирование доступа Envoy.

Прокси-серверы Envoy выводят информацию о доступе в стандартный вывод. Команда kubectl logs может вывести стандартный вывод контейнеров Envoy.

2. Небольшое введение в Istio (и Service Mesh)

2.1 Service Mesh

Service Mesh — это специализированный инфраструктурный слой, добавляемый к распределенным микросервисам. Он позволяет прозрачно добавлять такие возможности, как:

  • наблюдаемость

  • управление трафиком

  • безопасность.

И все это достигается без изменения кода приложения.

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

Service Mesh позволяет уменьшить боль от вышеупомянутых проблем и снизить нагрузку на команды разработчиков.

Кроме того service mesh может решать и более сложные операционные задачи, такие как A/B-тестирование, канареечное развертывание, ограничение скорости, контроль доступа, шифрование и сквозная аутентификация.

2.2 Istio

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

  • Безопасное взаимодействие между сервисами в кластере с помощью шифрования TLS, надежной аутентификации и авторизации на основе идентификационных данных.

  • Автоматическая балансировка нагрузки для трафика HTTP, gRPC, WebSocket и TCP.

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

  • Подключаемый уровень политик и API конфигурации, поддерживающий контроль доступа, ограничения скорости и квоты.

  • Автоматические метрики, журналы и трассировки для всего трафика в кластере, включая входящий и исходящий трафик кластера.

Istio масштабируемый и может использоваться для различных задач развертывания. Control plane Istio работает на базе Kubernetes. Вы можете добавлять в сетку приложения, развернутые в этом кластере, расширять сетку на другие кластеры или даже подключать ВМ или другие конечные точки за пределами Kubernetes.

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

2.3 Istio и Envoy

Istio использует Envoy в качестве прокси-серверов, развернутых в качестве sidecars. Эти прокси-серверы являются посредниками и контролируют все сетевые взаимодействия между микросервисами. Они также собирают и сообщают телеметрию обо всем сетевом трафике.

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

Хорошо, теперь, когда мы разобрались с номенклатурой service mesh и Istio, давайте поиграем с журналом доступа Envoy. Для этого мы воспользуемся Istio.

3. Подготовка локального кластера Kubernetes с помощью Istio

3.1 Подготовка локального кластера Kubernetes

Одним из самых простых способов запуска локального кластера Kubernetes является использование minikube. Следуйте инструкциям, приведенным в официальной документации по установке, а затем выполните следующие действия:

minikube start

3.2 Установка Istio

Загрузите Istio, установите, а затем включите istio-injection в пространстве имен по умолчанию, выполнив следующие команды:

curl -L https://istio.io/downloadIstio | sh -
# your version might differ
cd istio-1.16.1
# for easier usage
export PATH=$PWD/bin:$PATH
# here, we use the minimal profile so that the access log isn't enabled by default, which we want to do ourselves
istioctl install - set profile=minimal -y
# enable injection in the default namespace
kubectl label namespace default istio-injection=enabled

3.3 Развертывание тестовых приложений

Сначала развернем несколько примеров приложений для тестирования:

kubectl apply -f samples/sleep/sleep.yaml
export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
kubectl apply -f samples/httpbin/httpbin.yaml

Этот набор команд развернет два приложения: одно из них — команда «curl» в поде, которую мы будем использовать для отправки HTTP-запросов, а другое — HTTP-сервер, принимающий запросы, который мы будем использовать для демонстрации журналов доступа Envoy.

4. Журналы доступа Envoy в Istio

4.1 Включение журналов доступа

Далее включим журналы доступа.

Istio предлагает несколько способов включения журналов доступа. Рекомендуется использовать Telemetry API:

Сначала создадим файл telemetry.yaml со следующим содержимым:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: mesh-default
  namespace: istio-system
spec:
  accessLogging:
    - providers:
      - name: Envoy

Затем примените его:

kubectl apply -f telemetry.yaml

4.2 Тест

Сначала отправим запрос из sleep на httpbin:

kubectl exec "$SOURCE_POD” -c sleep — curl -sS -v httpbin:8000/status/418

Если мы проверим журнал httpbin, то увидим нечто похожее на следующее:

kubectl logs -l app=httpbin -c istio-proxy
[2020–11–25T21:26:18.409Z] "GET /status/418 HTTP/1.1" 418 - via_upstream - "-" 0 135 3 1 "-" "curl/7.73.0-DEV" "84961386–6d84–929d-98bd-c5aee93b5c88" "httpbin:8000" "127.0.0.1:80" inbound|8000|| 127.0.0.1:41854 10.44.1.27:80 10.44.1.23:37652 outbound_.8000_._.httpbin.foo.svc.cluster.local default

5. Фильтр журналов доступа Envoy

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

5.1 Задача

Представьте себе следующую ситуацию: в вашем приложении есть конечные точки, например, /status, /liveness и /readiness, которые вы не хотите логировать, поскольку там может быть множество запросов в минуту. Эти журналы логи статус чеков будут не очень хорошим решением с точки зрения использованием ресурсов для логирования.

Кроме того, вы можете настроить журналы доступа таким образом, чтобы различные запросы и ответы записывались в отдельные файлы журналов.

Можем ли мы этого добиться?

Да.

Envoy поддерживает несколько встроенных журналов доступа и фильтры расширений, регистрируемые во время выполнения программы. Теперь попробуем продемонстрировать функции фильтров журналов:

5.2 Загрузка Envoy и подготовка конфигурационного файла

Самый простой способ поиграть с конфигурацией Envoy — это запустить его локально, а не в качестве sidecar в составе Istio. Давайте сделаем следующее:

Установите Envoy:

brew update
brew install envoy

Скачать демонстрационную конфигурацию можно отсюда.

Затем запустим ее:

envoy -c envoy-demo.yaml

Затем откройте другую вкладку и убедитесь, что Envoy работает на порту 10000:

curl -v localhost:10000

Если мы вернемся на вкладку, где запущен Envoy, то увидим журнал доступа Envoy, который будет выглядеть следующим образом:

[2023–01–19T03:10:59.085Z] "GET / HTTP/1.1” 200–0 17304 747 467 "-” "curl/7.85.0” "4b35db72-baf8–4730–885e-3e628757f0e3” "www.envoyproxy.io" "34.143.223.220:443”

5.3 Фильтр журналов Envoy

Envoy предоставляет множество фильтров журналов, например, фильтр по status code, header filter (фильтр по заголовкам) и т.д. Более подробную информацию можно найти в официальной документации здесь.

Документация могла бы быть более подробной, в ней нет примеров использования каждого фильтра. Но это нам не помешает. После несложных поисков в Google (и на GitHub) попробуем этот кусок конфига:

...
          access_log:
          - name: Envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
            filter:
              header_filter:
                header:
                  name: :Path
                  string_match:
                    exact: /status
...

Понять это несложно: он работает по заголовку, и если путь чему-то соответствует, то фильтр это улавливает.

Если мы добавим этот кусок кода в файл envoy-demo.yaml и обратимся к URL /status:

curl -v localhost:10000/status

Будут вести такие журналы, как:

[2023–01–19T03:23:08.054Z] "GET /status HTTP/1.1” 404–0 3413 776 690 "-” "curl/7.85.0” "e507c86f-004f-4cba-b622–2004c7cf97c7” "www.envoyproxy.io" "34.126.184.144:443”

Но если мы обращаемся к URL, отличным от /status, например, /:

curl -v localhost:10000

Мы не получаем никаких журналов.

Хорошо, теперь фильтр журналов работает. За исключением того, что мы хотим отфильтровать журналы типа /status.

5.4 Подробнее о фильтрах журналов Envoy

Хорошо, давайте вернемся к официальной документации и немного поищем в Google.

Нетрудно заметить, что некоторые фильтры типа «and_filter» полезны. Давайте попробуем это сделать:

...
            filter:
              and_filter:
                filters:
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /status
                      invert_match: true
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /liveness
                      invert_match: true
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /readiness
                      invert_match: true
...

Мы можем использовать and_filter для объединения нескольких фильтров, а также инвертировать результат совпадения с помощью invert_match.

Эта конфигурация, обновленная в envoy-demo.yaml, будет показывать журналы только в том случае, если URL не соответствует /status, /liveness или /readiness.

Итак, мы достигли своей цели: мы можем использовать фильтры для управления  access logging Envoy. В приведенном выше примере мы использовали and_filter и header_filter для отсеивания определенных нежелательных записей в журнале. Если смешивать и сочетать все фильтры Envoy, то можно быстро добиться перенаправления определённых логов в определённые файлы или регистрировать только запросы 4xx и т.д.

Но (и это большое «но»), вышеописанная конфигурация предназначена только для локального использования или запуска Envoy как отдельного процесса.

Как поместить эти настройки фильтров в sidecar Envoy?

6. Telemetry API

Как уже говорилось ранее, Istio предлагает несколько способов включения журналов доступа. В разделе 4.1 мы использовали Telemetry API, поэтому давайте посмотрим, можно ли настроить Telemetry API для достижения цели фильтрации журналов.

Telemetry определяет, как генерируется телеметрия для рабочих нагрузок в сетке. В нем есть функция AccessLogging.Filter, в которой можно указать выражение в строке для достижения таких целей, как response.code >= 400 или connection.mtls && request.url_path.contains('v1beta3').

Согласно официальной документации (ссылка выше), выражение должно быть выражением CEL (подробнее об определении языка можно прочитать здесь.

Таким образом, если мы преобразуем написанные выше фильтры в CEL-выражение, то оно будет выглядеть следующим образом:

expression: "request.url_path != ‘/status’ && request.url_path != ‘/liveness’ && request.url_path != ‘/readiness’”

Если мы обновим файл telemetry.yaml со следующим содержимым:

apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: mesh-default
  namespace: istio-system
spec:
  accessLogging:
    - providers:
        - name: Envoy
      filter:
        expression: "request.url_path != '/status' && request.url_path != '/liveness' && request.url_path != '/readiness'"
And apply it:

И применим его:

kubectl apply -f telemetry.yaml

Тогда istio-proxy sidecar (Envoy) будет регистрировать только те записи, в которых URL-адрес запроса не является /status, /liveness или /readiness.

7. EnvoyFilter

EnvoyFilter предоставляет механизм для кастомизации Envoy. С помощью EnvoyFilter можно изменять значения определенных полей, добавлять специфические фильтры или даже добавлять совершенно новые listeners, кластеры и т.д. Использовать эту возможность следует осторожно, так как неправильная конфигурация может привести к дестабилизации всей сетки. В отличие от других сетевых объектов Istio, EnvoyFilters применяются аддитивно. Для данной рабочей нагрузки в конкретном пространстве имен может существовать любое количество EnvoyFilters. Порядок применения этих EnvoyFilters следующий: все EnvoyFilters в config root namespace, затем все соответствующие EnvoyFilters в workload«s namespace.

Мы можем использовать EnvoyFilter для настройки журналов доступа Envoy, хотя официально «Istio Telemetry API предоставляет первоклассный способ настройки журналов доступа и трассировки. Рекомендуется использовать этот способ».

Тем не менее давайте рассмотрим EnvoyFilter и то, как его использовать.

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

kubectl delete -f telemetry.yaml

Затем, следуя формату, приведенному в этой документации, мы можем создать файл envoyfilter.yaml со следующим содержимым:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: access-log
spec:
  configPatches:
  - applyTo: NETWORK_FILTER
    match:
      context: ANY
      listener:
        filterChain:
          filter:
            name: "Envoy.filters.network.http_connection_manager"
    patch:
      operation: MERGE
      value:
        typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
          access_log:
          - name: Envoy.file_access_log
            typed_config:
              "@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog"
              path: /dev/stdout
              format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_CODE_DETAILS% \"%RESP(GRPC-STATUS)% %RESP(GRPC-MESSAGE)%\" %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\"\n"
            filter:
              and_filter:
                filters:
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /status
                      invert_match: true
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /liveness
                      invert_match: true
                - header_filter:
                    header:
                      name: :Path
                      string_match:
                        exact: /readiness
                      invert_match: true

Мы создаем объект вида «EnvoyFilter», и его содержимое выглядит так же, как и конфигурационный файл Envoy.

Если мы применим его:

kubectl apply -f envoyfilter.yaml

Тогда istio-proxy sidecar  (Envoy) будет регистрировать только те записи, в которых путь URL запроса не является /status, /liveness или /readiness, как и в случае с Telemetry.

8. Разборка

minikube delete

Заключение

В этой статье:

  • Мы познакомились с Envoy, servise mesh и Istio.

  • Затем мы создали локальный кластер Kubernetes и установили в него Istio.

  • Мы включили журналы доступа Envoy в Istio через Telemetry и поиграли с конфигурациями Envoy, чтобы добиться фильтрации журналов.

  • Мы также рассмотрели два способа настройки фильтрации журналов для Envoy sidecar в Istio (Telemetry и EnvoyFilter).

Приходите изучать Service mesh в Слёрм. Вы научитесь:  

  • Внедрять эту технологию без костылей в архитектуре.

  • Выстраивать observability и сократите время на поиск и устранение проблем.

  • Унифицировать политики прокси Envoy.

  • Управлять входящим и исходящим трафиком в едином месте, а также тюнить систему.

Посмотреть программу и оставить заявку можно на нашем сайте.

© Habrahabr.ru