Вітаю! Це невелика стаття, що відповідає на запитання: "що таке 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 намагаються це
А поки що використовуйте для статики nginx.
- Статична конфігурація.
Можна її використовувати, але посланий був створений не для цього. Можливості в статичній конфігурації не будуть розкриті. Моментів багато:
Редагуючи конфігурацію в yaml, Ви помиляєтеся, материте розробників за багатослівність і думатимете, що конфіги nginx/haproxy, нехай менш структуровані, але лаконічніші. У цьому й суть. Конфігурація Nginx і Haproxy створювалася під редагування руками, а в посланий під генерацію із коду. Вся конфігурація описана в
Сценарії 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),
Конфігурація набуває наступного вигляду:
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