При взаимодействии микросервисов потенциально узким местом является сетевой балансировщик, распределяющих трафик по рабочим узлам. Чаще всего для выполнения балансировки на уровне приложения используется nginx или haproxy, однако в ряде случаев (например, при необходимости трассировки распределенных запросов) для этого цели лучше подходят такие решения как Envoy или Traefik. Но среди проектов CNCF есть еще один балансировщик BFE, разработанный первоначально в baidu, который кроме всех обычных возможностей балансировщика уровня приложения, также предоставляет большие возможности для мониторинга и трассировки. В этой статье мы рассмотрим основные возможности BFE и возможности использования с Prometheus и Kubernetes.

BFE написан на Go и основан на использовании механизма плагинов (модулей) для расширения функциональности, что позволяет сократить конвейер обработки через исключение неиспользуемых модулей, а также дает возможность создавать собственные расширения или поддержку различных протоколов прикладного уровня. Изначально проект разрабатывался в рамках baidu, но сейчас доступен как Open Source проект. В документации подробно описана организация кода и разобрана реализация HTTP и HTTP/2 и на этой основе может быть добавлена реализация любого L7-протокола. Также в документации можно найти информацию о разработке модулей расширения, которые могут подписываться на 9 разных фазах обработки запроса:

  • HandleAccept: сразу после установки соединения клиента с сервером

  • HandleHandshake: после согласования шифрования и установки сессии SSL/TLS.

  • HandleBeforeLocation: перед выполнением правила анализа местоположения.

  • HandleFoundProduct: после обнаружения продукта (из набора правил).

  • HandleAfterLocation: после обнаружения кластера (внутри продукта).

  • HandleForward: после обнаружения подкластера, но до отправки запроса на endpoint.

  • HandleReadResponse: после получения ответа от endpoint.

  • HandleRequestFinish: после пересылки ответа клиенту.

  • HandleFinish: после закрытия соединения от клиента к серверу.

Существующие встроенные модули позволяют выполнять мониторинг, контроль доступа, преобразования заголовков и трассировку observability. Также поддерживается кэширование ответов или https-сессий (для этого используется внешний сервер Redis). Кроме прочего BFE позволяет анализировать статистику обращений (например, время ответа) или логи сервера.

В BFE сейчас поддерживаются протоколы HTTP, HTTPS, SPDY/ HTTP2, WebSocket, gRPC, планируется добавить поддержку HTTP/3. Поскольку анализ запросов выполняется на уровне протокола, при маршрутизации могут использоваться заголовки запроса.

Архитектурно BFE состоит из нескольких компонентов:

  • Data Plane отвечает за пересылку и модификацию трафика (также позволяет считывать логи с веб-сервера для дальнейшей передачи данных в аналитическую панель)

  • Control Plane: API Server (предоставляет API для регистрации сервисов и управления конфигурацией), Conf-Agent (отслеживает изменение конфигурации и уведомляет другие компоненты), Dashboard (интерфейс для диагностики и управления конфигурацией).

deploy_architecture.png
deploy_architecture.png

Встроенные модули функционально похожи на модули nginx:

  • mod_rewrite - позволяет изменять URI по заданным условиям (например, значениям заголовков)

  • mod_header - позволяет выполнять изменения заголовков

  • mod_redirect - пересылка запросов на основе правил

  • mod_geo - использование геолокации пользователя для определения внутренних переменных

  • mod_tags - добавляет тэги по правилам анализа содержания запроса

  • mod_logid - создает идентификаторы для сессий

  • mod_userid - генерация id для пользователя

  • mod_trust_clientip - маркировка запросов от указанных ip-адресов как доверенных

  • mod_doh - использование DNS над HTTPS

  • mod_compress - сжатие ответов

  • mod_static - обработка статических файлов

  • mod_markdown - автоматическое преобразование markdown в html

  • mod_auth_basic, mod_auth_jwt, mod_auth_request - контроль доступа на основе HTTP-заголовков (Basic Auth, JSON Web Token, внешняя авторизация)

  • mod_block - блокировка запросов по набору правил

  • mod_prison - ограничение количества запросов

  • mod_http_code - статистика по кодам ответа

  • mod_trace - трассировка запросов

Подробное описание настройки BFE и создания модулей расширения может быть найдена в этом репозитории.

Для установки BFE мы будем использовать Docker-контейнер (но также можно установить бинарный файл под Windows/Linux/MacOS или собрать проект из исходных кодов):

docker run -d --name endpoint1 nginx
docker run -itd --name bfe --link endpoint1:endpoint1 -p 8080:8080 -p 8443:8443 -p 8421:8421 bfenetworks/bfe

Порты 8080 и 8443 будут использоваться для публикации API, порт 8421 предоставляет доступ к API.

Конфигурация BFE размещена в контейнере в файле /bfe/conf/bfe.conf и включает несколько секций:

  • [Server] - определяет номера портов и настройки таймаутов, ограничения по размеру запросов, а также расположение файлов конфигурации и список активных модулей

  • [HttpsBasic] - настройки шифров и сертификатов-ключей для https

  • [SessionCache] - конфигурация сервера кэширования (Redis) для сессий TLS

В секции Server перечислены конфигурации для настройки именованных кластеров (ClusterConf), правил маршрутизации (RouteRuleConf), соотнесения хостов и меток (HostRuleConf). Также в /bfe/conf собрана конфигурация для каждого модуля в виде data-файлов (JSON).

Конфигурация из bfe.conf файла требует выполнение перезапуска BFE, однако связанные конфигурации (каталоги server_data_conf и cluster_data_conf), а также конфигурация некоторых модулей может быть обновлена динамически, без необходимости разрыва существующих соединений.

Выполним публикацию нашего nginx, для этого сначала промаркируем наш хост (внутри контейнера BFE он будет доступен по имени endpoint1, но также при определении таблицы хостов могут использоваться ip-адреса) с использованием тэга (в данном случае тэг может быть "endpoints"), а также свяжем тэг с нашей системой (my_product), добавим в /bfe/conf/server_data_conf/host_rule.conf:

{
    "Version": "1",
    "DefaultProduct": "my_product",
    "Hosts": {
        "endpoints":[
            "endpoint1"
        ]
    },
    "HostTags": {
        "my_product":[
            "endpoints"
        ]
    }
}

Затем определим кластер и добавим в /bfe/conf/server_data_conf/cluster_conf.data ключ в Config:

{
    "Version": "1",
    "Config": {
        "endpoints": {
            "BackendConf": {
                "TimeoutConnSrv": 2000,
                "TimeoutResponseHeader": 50000,
                "MaxIdleConnsPerHost": 0,
                "RetryLevel": 0
            },
            "CheckConf": {
                "Schem": "http",
                "Uri": "/",
                "Host": "endpoint1",
                "StatusCode": 200,
                "FailNum": 10,
                "CheckInterval": 1000
            },
            "GslbBasic": {
                "CrossRetry": 0,
                "RetryMax": 2,
                "HashConf": {
                    "HashStrategy": 0,
                    "HashHeader": "Cookie:UID",
                    "SessionSticky": false
                }
            },
            "ClusterBasic": {
                "TimeoutReadClient": 30000,
                "TimeoutWriteClient": 60000,
                "TimeoutReadClientAgain": 30000,
                "ReqWriteBufferSize": 512,
                "ReqFlushInterval": 0,
                "ResFlushInterval": -1,
                "CancelOnClientClose": false
            }
        }
    }
}

Здесь мы определяем таймауты для доступа к бэкэнду, способ проверки доступности бэкэнда, конфигурацию подкластеров (могут использоваться для балансировки нагрузки), при этом здесь можно также использовать sticky session для привязки запросов одного пользователя к одному подкластеру.

Теперь выполним привязку подходящих запросов к кластеру (все запросы будем отправлять в кластер endpoints), при этом в условиях мы также могли использовать проверку с использованием встроенных функций (например, req_host_in для проверки соответствия имени хоста).

{
    "Version": "1",
    "ProductRule": {
        "my_product": [
           {
              "Cond": "default_t()",
              "ClusterName": "endpoints"
           }
        ]
    }
}

Осталось определить подкластеры для кластера endpoints (не менее одного) и привязать хосты к подкластерам (/bfe/conf/cluster_conf/gslb.data):

{
    "Clusters": {
        "endpoints": {
            "GSLB_BLACKHOLE": 0,
            "endpointsSubcluster1": 100
        }
    },
    "Hostname": "",
    "Ts": "0"
}

И последнее - добавим связь между хостами и подкластерами (/bfe/conf/cluster_conf/cluster_table.data):

{
    "Config": {
        "endpoints": {
            "endpointsSubcluster1": [
               {
                    "Addr": "endpoint1",
                    "Name": "instance1",
                    "Port": 80,
                    "Weight": 10
               }
            ]
        }
    },
    "Version": "1"
}

Схема обработки трафика в общем виде выглядит в нашем случае подобным образом:

Traffic(1).png
Traffic(1).png

Проверим корректность нашей конфигурации (внутри контейнера):

cd /bfe
bin/bfe -t

Для перезагрузки выполним запрос через API (внутри контейнера):

curl http://localhost:8421/reload/server_data_conf
curl http://localhost:8421/reload/gslb_data_conf

BFE экспортирует информацию о текущем состоянии, которая включает не только общую статистику запросов и результаты проверок состояния доступности, но и статистику по каждому модулю (доступно через http://localhost:8421/monitor). Например, запрос о состоянии балансировки возвращает JSON с описанием доступных кластеров и endpoint'ов:

curl http://localhost:8421/monitor/bal_table_status
{
  "Balancers":{
    "endpoints":{
      "SubClusters":{
        "GSLB_BLACKHOLE":{
          "BackendNum":0
        },
        "endpointsSubcluster1":{
          "BackendNum":1
        }
      },
      "BackendNum":1
    }
  },
  "BackendNum":1
}

Также можно получить статистику по времени ответа backend за прокси:

curl http://localhost:8421/monitor/proxy_delay
{
  "Interval":60,
  "KeyPrefix":"proxy_delay",
  "ProgramName":"",
  "CurrTime":"2023-06-04 11:43:30",
  "Current":{
    "BucketSize":1,
    "BucketNum":10,
    "Count":0,
    "Sum":0,
    "Ave":0,
    "Counters":[
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ]
  },
  "PastTime":"2023-06-04 11:41:21",
  "Past":{
    "BucketSize":1,
    "BucketNum":10,
    "Count":2,
    "Sum":725,
    "Ave":362,
    "Counters":[
      2,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ]
  }
}

Метрики могут быть получены не только в формате JSON, но также в OpenMetrics (совместим с prometheus), я этого нужно передать аргумент ?format=prometheus:

curl http://localhost:8421/monitor/proxy_delay?format=prometheus
# TYPE proxy_delay_Past histogram
proxy_delay_Past_bucket{le="1000"} 1
proxy_delay_Past_bucket{le="2000"} 1
proxy_delay_Past_bucket{le="3000"} 1
proxy_delay_Past_bucket{le="4000"} 1
proxy_delay_Past_bucket{le="5000"} 1
proxy_delay_Past_bucket{le="6000"} 1
proxy_delay_Past_bucket{le="7000"} 1
proxy_delay_Past_bucket{le="8000"} 1
proxy_delay_Past_bucket{le="9000"} 1
proxy_delay_Past_bucket{le="10000"} 1
proxy_delay_Past_bucket{le="+Inf"} 1
proxy_delay_Past_sum 152
proxy_delay_Past_count 1

Для проверки преобразования запросов добавим конфигурацию модуля mod_rewrite (по умолчанию включен) в /bfe/conf/mod_rewrite/rewrite.data:

{
    "Version": "1",
    "Config": {
        "my_product": [
            {
                "Cond": "default_t()",
                "Actions": [
                    {
                        "Cmd": "PATH_SET",
                        "Params": [
                            "/"
                        ]
                    }
                ],
                "Last": true
            }
        ]
    }
}

Выполним динамическую перезагрузку конфигурации:

curl http://localhost:8421/reload/mod_rewrite

Теперь при обращении к любому адресу (например http://localhost:8080/test) будет возвращаться страница hello_world.

Одним из полезных модулей является mod_trace, который добавляет заголовки для идентификации цепочки запросов и могут использоваться для трассировки с использованием jaeger или любого другого подхода с поддержкой OpenTracing. Трассировка может быть разрешена для отдельных префиксов или хостов, конфигурация в файле /bfe/conf/mod_trace/trace_rule.data:

{
    "Version": "1",
    "Config": {
        "my_product": [
             {
                 "Cond": "req_host_in(\"localhost\")",
                 "Enable": true 
             }
        ]
    }
}

Для доступа к backend серверам может использоваться не только http-протокол, но и spdy/http2, web sockets.

При использовании Kubernetes можно установить ingress-bfe:

kubectl apply -f https://raw.githubusercontent.com/bfenetworks/ingress-bfe/develop/examples/controller-all.yaml

Также можно использовать Helm для установки.

helm upgrade --install bfe-ingress-controller bfe-ingress-controller --repo https://bfenetworks.github.io/ingress-bfe  --namespace ingress-bfe --create-namespace

Далее регистрация сервисов будет выполняться с использованием аннотации kubernetes.io/ingress.class: bfe

Конфигурация модулей будет выполняться также через аннотации bfe.ingress.kubernetes.io. Сами правила публикации сервисов определяются аналогично тому, как конфигурируются другие ingress-контроллеры (nginx, envoy, traefik), и этому через дополнительные аннотации могут быть указаны весовые коэффициенты для распределения трафика. Список всех аннотаций можно посмотреть здесь.

Функционально BFE похож на Traefik и Envoy и может рассматриваться как альтернатива, когда требуется получать больше диагностической информации о модулях расширения и текущем состоянии кластеров.

Статья подготовлена в преддверии старта курса Highload Architect. По ссылке ниже вы можете подробнее узнать о курсе, а также зарегистрироваться на бесплатный урок.

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