Envoy. 1. Введення

Вітаю! Це невелика стаття, що відповідає на запитання: "що таке envoy?", "Навіщо він потрібний?" і "з чого почати?"

Що це

Envoy — це L4-L7 балансувальник, написаний на С++, орієнтований на високу продуктивність і доступність. З одного боку, це певною мірою аналог nginx і haproxy, порівнянний з ними за продуктивністю. З іншого, він більше орієнтований під мікросервісну архітектуру і володіє функціоналом не гіршим за балансувальників на java і go, таких як zuul або traefik.

Таблиця порівняння haproxy/nginx/envoy, вона претендує на абсолютну істину, але дає загальну картину.

Nginx
гапрокі
посланий
траефік

зірок на github
11.2k/mirror
1.1k/mirror
12.4k
27.6k

написаний на
C
C
C + +
go

API
немає
socket only/push
dataplane/pull
тягнути

active healthcheck
немає
да
да
да

Open tracing
зовнішній плагін
немає
да
да

JWT
зовнішній плагін
немає
да
немає

Розширення
Lua/C
Lua/C
Lua/C++
немає

Навіщо

Це молодий проект, у ньому багато чого нема, щось у ранній альфі. Але посланийУ тому числі за рахунок молодості, швидко розвивається і вже зараз має багато цікавих можливостей: динамічну конфігурацію, багато готових фільтрів, простий інтерфейс для написання своїх фільтрів.
З цього випливають області застосування, але для початку 2 антипатерни:

  • Віддача статики.

Справа в тому, що на даний момент у посланий немає підтримки кешування. Хлопці з google намагаються це виправити. Ідея одного разу реалізувати на посланий все тонкощі(зоопарк хедерів) відповідності RFC, а конкретних реалізацій створити інтерфейс. Але поки що це навіть не альфа, архітектура в обговоренні, PR відкрито (поки я писав статтю PR вмержили, але цей пункт ще актуальний).

А поки що використовуйте для статики nginx.

  • Статична конфігурація.

Можна її використовувати, але посланий був створений не для цього. Можливості в статичній конфігурації не будуть розкриті. Моментів багато:

Редагуючи конфігурацію в yaml, Ви помиляєтеся, материте розробників за багатослівність і думатимете, що конфіги nginx/haproxy, нехай менш структуровані, але лаконічніші. У цьому й суть. Конфігурація Nginx і Haproxy створювалася під редагування руками, а в посланий під генерацію із коду. Вся конфігурація описана в протобуф, генеруючи її по proto файлам помилитися набагато складніше.

Сценарії canary, b/g деплою та багато іншого, нормально реалізуються тільки в динамічній конфігурації. Я не кажу, що це не можна зробити в статиці, ми все це робимо. Але для цього потрібно обкластися милицями, в будь-якому з балансерів, посланий в тому числі.

Завдання в яких Envoy незамінний:

  • Балансування трафіку у складних та динамічних системах. Сюди потрапляє служба mesh, але це не обов'язково тільки він.
  • Необхідність функціоналу розподіленого трасування, складної авторизації чи іншого, що є в посланий з коробки або зручно реалізовується, а в nginx/haproxy потрібно обкластися lua та сумнівними плагінами.

І те, й інше при необхідності забезпечити високу продуктивність.

Як це працює

Envoy поширюється у бінарниках тільки як docker образ. У образі є приклад статичної конфігурації. Але нам він цікавий лише розуміння структури.

envoy.yaml статична конфігурація

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  host_rewrite: www.google.com
                  cluster: service_google
          http_filters:
          - name: envoy.router
  clusters:
  - name: service_google
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    # Comment out the following line to test on v6 networks
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: service_google
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.google.com
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext
        sni: www.google.com

Динамічна конфігурація

Вирішення якої проблеми ми шукаємо? Не можна просто так взяти та перезавантажити конфігурацію балансувальника під навантаженням, виникнуть "невеликі" проблеми:

  • Валідація конфігурації.

Конфіг може бути великий, може бути дуже великий, якщо ми перевантажуємо його весь раз, шанси десь помилка зростають.

  • Довгоживучі сполуки.

При ініціалізації нового листенера, потрібно подбати про з'єднання працюючих на старому, якщо зміни відбуваються часто і є довгоживучі з'єднання, доведеться шукати компроміс. Привіт, kubernetes ingress на nginx.

  • Активні хелсчеки.

Якщо у нас є активні хелсчеки, треба б їх все перевірити ще раз на новому конфізі до того як надіслати трафік. Якщо апстрімів багато, це потребує часу. Привіт, haproxy.

Як це вирішується в посланий, підвантажуючи конфіг динамічно, по пул моделі, можна поділити його на окремі частини і не переініціалізувати ту частину яка не змінювалася. Наприклад, листенер, який переініціалізувати дорого, а змінюється він рідко.

Конфігурація посланий (З файлу вище) має такі сутності:

  • слухач - листенер висить на певному ip/порту
  • віртуальний хост - Віртуальний хост на ім'я домену
  • маршрут - правило балансування
  • кластер - Група апстрімів з параметрами балансування
  • кінцева точка - адреса інстансу апстріму

Кожну з цих сутностей плюс деякі інші можна заповнити динамічно, для цього в конфігурації вказується адреса сервісу, від якої буде отримано конфіг. Сервіс може бути REST або gRPC, краще використовувати gRPC.

Сервіси називаються відповідно: LDS, VHDS, RDS, CDS та EDS. Можна комбінувати статичну та динамічну конфігурацію, з обмеженням, що динамічний ресурс не можна вказати у статичному.

Для більшості завдань достатньо реалізувати останні три сервіси, вони називаються ADS (Aggregated Discovery Service), Ява і go є готова імплементація gRPC dataplane, в якій достатньо тільки заповнити об'єкти зі свого джерела.

Конфігурація набуває наступного вигляду:

envoy.yaml динамічна конфігурація

dynamic_resources:
  ads_config:
    api_type: GRPC
    grpc_services:
      envoy_grpc:
        cluster_name: xds_clr
  cds_config:
    ads: {}
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          rds:
            route_config_name: local_route
            config_source:
              ads: {}
          http_filters:
          - name: envoy.router
  clusters:
  - name: xds_clr
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: xds_clr
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: xds
                port_value: 6565

При запуску посланий з цим конфігом, він підключиться до control-plane і спробує запросити конфігурацію RDS, CDS та EDS. Як відбувається процес взаємодії описано тут.

Якщо коротко, посланий шле запит, із зазначенням типу запитуваного ресурсу, версією та параметрами ноди. У відповідь отримує ресурс і версію, якщо control-plane версія не змінилася, він відповідає.
Є 4 варіанти взаємодії:

  • Один gRPC стримує всі типи ресурсів, надсилається повний стан ресурсу.
  • Роздільні стрими, повний стан.
  • Один стриманий, інкрементальний стан.
  • Роздільні стрими, інкрементальний стан.

Incremental xDS дозволяє зменшити трафік між control-plane та посланий, це актуально для високих конфігурацій. Але ускладнює взаємодію, у запиті передається список ресурсів для відписки та передплати.

У нашому прикладі використовується ADS – один стрим для RDS, CDS, EDS і не інкрементальний режим. Для включення інкрементального режиму потрібно вказати api_type: DELTA_GRPC

Оскільки у запиті є параметри ноди, ми можемо на control-plane надсилати різні ресурси для різних інстансів посланий, це зручно для побудови service mesh.

Розминка

На посланий При старті або при отриманні нової конфігурації від control-plane запускається процес warmup ресурсів. Він розділений, на listener warmup і cluster warmup. Перший запускається при змінах RDS/LDS, другий при CDS/EDS. Це означає, що якщо змінюються тільки апстрими, листенер не перетворюється.

У процесі прогріву очікуються залежні ресурси від control-plane протягом таймууту. Якщо таймаут вийшов, ініціалізація не буде успішною, новий листонер не почне слухати порт.
Порядок ініціалізації: EDS, CDS, Active Health Check, RDS, LDS. При активованих активних хелсчеках, трафік піде на апстрім, тільки після одного успішного хелсчека.

Якщо перетворювався листенер, старий переходить у стан DRAIN, і буде видалено після закриття всіх з'єднань або закінчення таймууту --drain-time-s, за промовчанням 10 хвилин.

Далі буде.

Джерело: habr.com

Додати коментар або відгук