Skip to content

Microservices

Microservices

Моноліт [💡10/100]

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

Усі компоненти програми (бізнес-логіка, інтерфейс користувача, доступ до бази даних) знаходяться в одному додатку. Логіка взаємодії між компонентами часто реалізується через прямі виклики методів або функцій.

Моноліт підходить для невеликих команд і проєктів із передбачуваними обсягами роботи. Його також використовують для MVP (мінімально життєздатного продукту), де швидкість розробки важливіша за складні архітектурні рішення.

Моноліт часто є першим кроком у розробці, але з часом додатки можуть бути розділені на мікросервіси для підвищення гнучкості та масштабованості.

Переваги моноліту

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

Недоліки моноліту

  • З часом ускладнюється підтримка великого коду, особливо якщо команда зростає.
  • Масштабування потребує збільшення ресурсів для всього додатку, навіть якщо навантаження створює лише одна його частина.
  • Тісно пов’язані компоненти можуть ускладнювати впровадження нових технологій або змін.

Links

Microservices - Мікросервіси [🔥31/100]

Мікросервіси — це архітектурний стиль, який передбачає розбиття програми на набір незалежних компонентів (сервісів), кожен з яких відповідає за окрему бізнес-функцію і може розгортатися та масштабуватися окремо. Цей підхід спрямований на підвищення гнучкості, простоти підтримки та масштабування великих систем.

Кожен сервіс є незалежним, має свою кодову базу, базу даних (або доступ до окремих таблиць) і виконує одне конкретне завдання. Взаємодія між сервісами зазвичай відбувається через легкі протоколи, такі як HTTP (REST, GraphQL) або брокери повідомлень (RabbitMQ, Kafka).

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

Переваги мікросервісів

  • Зручність масштабування: кожен сервіс можна масштабувати незалежно від інших.
  • Можливість використовувати різні технології для різних сервісів залежно від їхніх потреб.
  • Полегшення підтримки: зміни в одному сервісі не впливають на інші (за умови чітких контрактів).
  • Зменшення ризику: збій в одному сервісі не зупиняє роботу всієї системи.

Недоліки мікросервісів

  • Ускладнена інфраструктура: потрібен надійний механізм для оркестрації сервісів, моніторингу та логування.
  • Накладні витрати на комунікацію: виклики між сервісами додають латентність і вимагають захисту від помилок.
  • Складнощі в забезпеченні узгодженості даних між сервісами.

Моноліт vs Мікросервіси [💡26/100]

Summary

Моноліт підходить для простих рішень і швидкого старту, тоді як мікросервіси необхідні для великих, складних проєктів, які вимагають масштабованості та модульності.

Вибір між монолітною архітектурою та мікросервісами залежить від масштабів проєкту, команди та бізнес-вимог. Кожен підхід має свої сильні сторони, які слід враховувати залежно від конкретної ситуації.

Сценарії для Моноліту

  • Проєкт невеликий, із чітко визначеною функціональністю та обмеженими масштабами.
  • Команда розробників мала, і всі члени працюють над одним кодом.
  • Потрібно швидко створити MVP (мінімально життєздатний продукт).
  • Інфраструктурні ресурси обмежені, немає необхідності в складній оркестрації.
  • Немає значних вимог до незалежного масштабування різних компонентів.
  • Основний фокус на простоті розробки, підтримки та розгортання.

Сценарії для Мікросервісів

  • Проєкт великий і передбачає постійне зростання як за функціональністю, так і за навантаженням.
  • Є чітка потреба в масштабуванні окремих частин системи, наприклад, модуля оплати чи пошуку.
  • У проєкті працює велика команда, яка може бути розділена на кілька груп, кожна з яких відповідає за окремий сервіс.
  • Проєкт потребує високої гнучкості у виборі технологій для різних частин системи.
  • Необхідно забезпечити високу надійність: збої в одному сервісі не повинні впливати на роботу інших.
  • Бізнес-вимоги швидко змінюються, і потрібна модульність для швидкого впровадження нових функцій.

Розрізнення мікросервісів і модульного моноліту

Поширена помилка на співбесіді - назвати ознакою інкапсуляцію або взаємозамінність ("кожен сервіс замкнений у собі", "сервіс можна підмінити іншою реалізацією"). Це не відмітні ознаки: добре спроєктований модуль моноліту так само інкапсульований, а взаємозамінність реалізацій - це LSP, який однаково діє і для класів. Відрізняють архітектури не властивості коду, а спосіб розгортання та володіння ресурсами - те, що видно ззовні, зі схем і конфігурації:

  • Окремий процес і незалежний деплой. Модулі моноліту виконуються в одному процесі і деплояться разом; мікросервіси - окремі процеси/контейнери з власним циклом релізу. Це необхідна ознака: без неї це моноліт незалежно від того, наскільки "сервісним" виглядає код.
  • Власна БД на сервіс (див. Database per Service). Якщо кожен компонент має своє сховище й не ходить у чужі таблиці - це майже напевно мікросервіси; модулі моноліту зазвичай ділять одну БД.
  • Поліглотний рантайм. Компоненти на різних мовах (Python + Go + JVM) - достатня, хоча й не необхідна ознака: класи моноліту живуть в одному рантаймі, тож різні мови майже завжди означають окремі сервіси за мережевою межею.

Жодна ознака окремо не є абсолютною (мікросервіси бувають одномовними; спільна БД - відомий антипатерн distributed monolith), але разом вони дають надійне розрізнення.

Strangler Fig (поступова міграція моноліту)

Summary

Поступова заміна моноліту: перед ним ставлять маршрутизуючий фасад, який перенаправляє окремі частини функціональності на нові сервіси. Моноліт "обплітається" новими сервісами і поступово відмирає - замість ризикованого переписування з нуля.

Проблема, яку вирішує

Переписування великої системи "з нуля" (big-bang rewrite) ризиковане: розробка нової версії триває місяцями, на цей час доводиться заморожувати зміни у старій, а перемикання відбувається одномоментно - якщо щось зламано, страждає вся система. Strangler Fig замінює це інкрементною міграцією, де кожен крок невеликий, перевіряється у проді й допускає відкат.

Принцип роботи

Назва походить від фікуса-душителя, який обплітає дерево-господар і поступово займає його місце. Перед монолітом розташовують маршрутизуючий шар (проксі або API Gateway). Окремий домен виносять у новий сервіс, після чого маршрутизатор спрямовує відповідні запити на нього, а решту - далі в моноліт. З кожним кроком на нові сервіси переходить більше маршрутів; моноліт зменшується і зрештою вимикається повністю.

Перехідний стан

Поки міграція триває, винесений сервіс може тимчасово читати дані зі спільної з монолітом БД - це прийнятний проміжний компроміс, а не цільовий стан. Кінцева мета - власна БД на сервіс (Database per Service): кожен сервіс володіє власним сховищем, а спільні дані синхронізуються через API чи події.

Обмеження

  • Потрібен надійний маршрутизуючий шар; помилки маршрутизації зачіпають увесь трафік.
  • На час переходу логіка частково дублюється між монолітом і новим сервісом.
  • Поки БД спільна, зберігається зв'язаність схем - повна автономність настає лише після розділення даних.
  • Межі виносу мають збігатися з bounded context'ами (див. architecture/ddd.md); невдалий поділ дає distributed monolith.

Database per Service [💡17/100]

Summary

Кожен мікросервіс володіє власною базою даних, до якої не мають прямого доступу інші сервіси. Зовнішній доступ - лише через API власника. Порушення цього правила перетворює межу сервісу на спільну таблицю і відновлює сильну зв'язаність схем, від якої мікросервіси відходили.

Принцип роботи

Сервіс інкапсулює не лише код бізнес-логіки, а й схему даних. Інші сервіси отримують потрібні їм дані через публічний API цього сервіса (REST/gRPC) або через події (publish-subscribe), а не безпосереднім SQL-запитом до чужої таблиці. Таким чином зміна внутрішньої схеми не вимагає скоординованого деплою кількох сервісів.

Переваги

  • Автономність деплою. Зміна схеми всередині сервіса не зачіпає інших.
  • Свобода вибору сховища. Сервіс пошуку може триматися на Elasticsearch, профільний - на Postgres, аналітичний - на ClickHouse.
  • Локальні транзакції залишаються простими. Один сервіс - один транзакційний контекст у власній БД.

Недоліки

  • Розподілена консистентність. Дані, які раніше об'єднувалися одним JOIN, тепер вимагають Saga / Outbox / CDC для синхронізації.
  • Дублювання даних. Профільний сервіс часто тримає копію частини даних з інших сервісів для локального читання.
  • Звітність ускладнюється. Кросс-сервісна аналітика будується через ETL/CDC у окреме сховище, а не одним SQL-запитом.

Типові порушення та як з ними боротися

  • Запис з іншого сервісу в чужу таблицю - найчастіший антипатерн. Поза часом інциденту виявити складно, бо доступ, що починався лише з читання, з часом отримує і запис. Фіксується ревізією міграцій і правом доступу на рівні ролей у БД: сервіс має read/write лише на власні таблиці.
  • Прямі JOIN'и через dblink/foreign tables. Те саме - повертає сильну зв'язаність схем. Замінюється API-викликом або реплікацією через CDC.
  • Пряме читання чужої БД для звітності - вирішується розгортанням окремої read-репліки або потоку CDC в аналітичне сховище, а не прямим читанням операційної БД іншого сервіса.

Зв'язок із суміжними патернами

  • Saga / Transactional Outbox / Inbox Pattern - механіки, які підтримують консистентність даних, розділених між сервісами.
  • CQRS - природне продовження: read-модель агрегує дані з кількох сервісів у окремому сховищі, не порушуючи кордонів запису.

API Gateway [💡22/100]

Summary

API Gateway - єдина точка входу для зовнішніх клієнтів у систему мікросервісів. Виконує маршрутизацію, агрегацію відповідей, балансування навантаження, аутентифікацію та rate limiting, звільняючи окремі сервіси від цих cross-cutting обов'язків.

Призначення

У системі з десятками мікросервісів зовнішньому клієнту незручно знати адресу кожного сервіса і дублювати в коді логіку авторизації, ретраїв, балансування. Gateway виносить цю логіку на один шар перед сервісами.

Принцип роботи

  • Маршрутизація. Зовнішній запит GET /api/v1/orders/42 потрапляє на gateway, який за конфігурацією маршрутів пересилає його до сервіса замовлень.
  • Агрегація відповідей. Один зовнішній запит (GET /api/v1/checkout/summary) може транслюватися в кілька внутрішніх викликів (профіль, кошик, доставка) і повертати клієнту склеєну відповідь. Цей варіант роботи gateway інколи називають Backend-for-Frontend (BFF). Узагальнення цього підходу - composite UI: коли дані одного екрана розпорошені по кількох bounded context'ах (хибний агрегат), кожен контекст віддає й рендерить свій зріз; на запис один зовнішній POST навпаки розкладається (decomposition) на виклики до контекстів-власників, а кожен контекст кешує свій зріз із власним TTL (ціну - хвилини, назву й опис - місяці, наявність - без кешу).
  • Аутентифікація і авторизація. Gateway перевіряє JWT або сесійний токен, після чого передає внутрішнім сервісам уже довірений user_id у заголовку. Самі сервіси не повторюють перевірку підпису.
  • Rate limiting. Обмеження частоти запитів per-user або per-IP реалізується на gateway, а не в кожному сервісі окремо. Деталі алгоритмів - у секції Rate Limiter.
  • Спостережуваність. Структуровані логи, metrics і trace-id додаються у вхідному пункті і прокидаються далі по системі.

Балансування навантаження на gateway

Балансування буває двох рівнів:

  • L4 (транспортний). Балансер працює на рівні TCP/UDP, не розбирає HTTP. Розподіл - за вихідним IP/портом, round-robin, hash. Дешевший і швидший, але не вміє роздавати запити за HTTP-заголовками чи cookie.
  • L7 (прикладний). Балансер розбирає HTTP-запит і розподіляє його за заголовками, шляхом, cookie. Дорожчий за CPU, але потрібен для sticky sessions, A/B-тестування, canary deployments.

Окремо розрізняють reverse proxy і load balancer: reverse proxy корисний навіть з одним бекендом (термінація TLS, стиснення, кеш статики, приховування внутрішніх серверів), тоді як load balancer розподіляє запити між кількома інстансами.

Алгоритми розподілу. Незалежно від рівня, балансер обирає інстанс за алгоритмом:

  • Round-robin - по колу, порівну. Просто; ігнорує реальне навантаження інстанса.
  • Weighted round-robin - як round-robin, але потужнішим інстансам дають більшу вагу (більше запитів). Враховує різну ємність машин, але ваги статичні й не відбивають навантаження в реальному часі.
  • Least connections - на інстанс із найменшою кількістю активних з'єднань. Динамічно враховує поточне навантаження; корисно, коли з'єднання мають різну тривалість.
  • Hash-based - інстанс обирається хешем від ключа (IP клієнта, user_id). Той самий клієнт стабільно потрапляє на той самий інстанс (основа sticky sessions); зміна складу пулу ламає прив'язку - звідси consistent hashing нижче.

Sticky sessions і consistent hashing

Якщо сесія користувача збережена в пам'яті конкретного інстанса сервіса (anti-pattern для stateless систем, але реальність для legacy), потрібно скеровувати наступні запити цього користувача на той самий інстанс - sticky session, зазвичай через cookie на L7.

Простий hash за IP має проблему: при додаванні чи видаленні інстанса перерозподіляється майже весь трафік. Consistent hashing мінімізує зсуви - при зміні складу пулу переходить лише пропорційна частка ключів. Деталі - стандартна тема system design.

Обмеження

  • Single point of failure. Gateway сам потребує кластеризації і резервування - інакше один збій кладе всю систему.
  • Додаткова латентність. Один зайвий мережевий хоп для кожного зовнішнього запита.
  • Антипатерн "товстого gateway". Перенесення бізнес-логіки у gateway перетворює його на моноліт, на заміну якому й проектувалася мікросервісна архітектура.

Реалізації

  • Готові: Kong, Traefik, Envoy + Istio, AWS API Gateway, Cloudflare Gateway.
  • Self-hosted: nginx з Lua-модулями, HAProxy.

Зв'язок із Sidecar pattern / Service Mesh

Gateway обробляє трафік north-south (зовнішній клієнт ↔ система). Sidecar і Service Mesh обробляють трафік east-west (сервіс ↔ сервіс всередині системи). Це різні шари, які доповнюють одне одного, а не замінюють.

Service Discovery

Summary

Service Discovery - механізм, який дозволяє сервісам знаходити одне одного у динамічному оточенні, де інстанси постійно з'являються (auto-scaling), переміщуються (rolling deploy) і зникають (crash, K8s reschedule). Без Discovery hard-coded IP-адреси перестають працювати першого ж дня. Дві основні моделі - client-side (клієнт сам опитує реєстр і обирає інстанс) і server-side (інфраструктурний шар - API Gateway, Service Mesh, K8s Service - робить це за клієнта).

Проблема, яку розв'язує

У монолітній системі компоненти "знаходять" одне одного через виклики функцій у тому ж процесі. У мікросервісах виклик "клієнт → сервіс orders" вимагає IP-адреси і порту. Якщо orders має 5 інстансів, які можуть зникнути і з'явитись будь-якої миті, hard-coded конфіг не підходить.

Service Discovery - dynamic name-resolution для сервісів: за іменем (orders.prod.local) повертає актуальний список здорових ендпоінтів.

Client-side discovery

Клієнт запитує реєстр (Service Registry) і отримує список інстансів, обирає один (load balance) і робить запит напряму.

[client] → [service registry] → list of orders instances
[client] → directly to orders-instance-3:8080

Реалізації:

  • Netflix Eureka - історично канонічна для Spring/Java; REST API, heartbeat для liveness, dropped інстанси через ~30s без heartbeat'у.
  • HashiCorp Consul - крім registry дає health checks, KV store, DNS interface (можна запитувати сервіси як звичайний DNS).
  • etcd / ZooKeeper - low-level KV-сховища, на яких будують registry (Kubernetes сам використовує etcd для service-discovery).

Переваги: клієнт контролює load-balancing (наприклад, sticky session, geographic affinity), один мережевий хоп. Недоліки: кожен клієнт мусить мати logic discovery (бібліотека на кожній мові), складніше міняти стратегію без redeploy клієнтів.

Server-side discovery

Клієнт робить запит на стабільну точку (API Gateway, Service Mesh sidecar, K8s Service), а вона сама знає, куди розподілити.

[client] → [api gateway / k8s service] → orders-instance-3

Реалізації:

  • Kubernetes Service - найпоширеніша сучасна модель: kube-proxy робить iptables/eBPF-правила, які перенаправляють трафік на здорові поди. Клієнт бачить тільки стабільний ClusterIP або DNS-ім'я orders.default.svc.cluster.local.
  • API Gateway (Kong, Nginx, Spring Cloud Gateway) - shed-зовнішнього трафіку, плюс service-discovery лише на boundary системи.
  • Service Mesh (Istio, Linkerd) - sidecar-проксі бачить service registry через control plane і робить routing/load-balancing замість клієнта.

Переваги: клієнт нічого не знає про discovery (звичайний HTTP до orders/), можна міняти стратегію без redeploy. Недоліки: додатковий мережевий хоп (через gateway/proxy), gateway сам стає critical-path component.

Реєстрація і health checks

Інстанс має зареєструватись у реєстрі при старті і слати періодичний heartbeat. Якщо heartbeat припинився - реєстр позначає інстанс як unhealthy і прибирає його з ротації. Канонічні підходи:

  • Self-registration - сервіс сам викликає register() при старті, deregister() при graceful shutdown. Просто, але вимагає бібліотеки на кожній мові.
  • Third-party registration - окремий компонент (registrator у Docker, K8s controller для подів) спостерігає за станом і реєструє/дереєструє за сервіс. Канонічно у K8s.

Сучасний default - Kubernetes

У K8s service discovery вбудований: Pod автоматично реєструється у endpoints об'єкті свого Service'у; kube-proxy/CoreDNS забезпечують resolution. Окремий Eureka/Consul у K8s-cluster'і зазвичай зайвий - дубль функціональності. Eureka/Consul залишаються релевантними поза K8s (VM-on-prem, multi-cluster, гібридні середовища).

Links

Sidecar pattern

Summary

Sidecar - допоміжний процес (зазвичай окремий контейнер у тому ж pod'і), що працює поруч з основним сервісом і обслуговує cross-cutting задачі: TLS, retry, circuit breaker, метрики, трасування, авторизацію. Бізнес-сервіс залишається мінімалістичним, інфраструктурна логіка - в sidecar.

Проблема, яку вирішує

У системі з десятками сервісів на різних мовах кожна команда має повторно реалізовувати ті самі речі: HTTPS, ретраї, таймаути, circuit breaker, експорт метрик у Prometheus, distributed tracing, JWT-перевірки. Це дублювання коду, дублювання інцидентів і дублювання версій бібліотек.

Sidecar виносить цю логіку у окремий процес, який слухає мережу замість основного сервіса і прозоро проксує запити.

Принцип роботи

  • У pod'і Kubernetes (або одиниці деплою VM-епохи) запускається два контейнери: основний бізнес-контейнер та sidecar.
  • Зовнішній трафік потрапляє в sidecar (через iptables-перенаправлення або явний slug у мережевому стеці). Sidecar виконує TLS-термінацію, перевіряє токени, застосовує політики, потім пересилає запит на localhost у бізнес-контейнер.
  • Вихідний трафік від бізнес-контейнера так само йде через sidecar, який додає mTLS, ретраї, circuit breaker, metric/trace-id.
  • Конфігурація політик надходить з центрального control plane і не зашита в код сервіса.

Типові обов'язки sidecar

  • Транспортна безпека: mTLS між сервісами (див. підсекцію в Service Mesh).
  • Стійкість: retry з exponential backoff + jitter, circuit breaker, таймаути на з'єднання та читання, bulkhead-ізоляція.
  • Спостережуваність: експорт RED-метрик (Rate, Errors, Duration), distributed tracing (OpenTelemetry).
  • Авторизація: RBAC і per-route політики, JWT-перевірка, ACL за SPIFFE-ідентифікатором.
  • Маршрутизація: canary, traffic splitting, retry policies per-endpoint.

Переваги

  • Polyglot-системи. Sidecar один на всі мови - бізнес-сервіс може бути написаний на Go, Python, Rust без потреби тримати n SDK.
  • Оновлення без правки бізнес-коду. Зміна політики ретраю - конфіг у control plane, без релізу сервіса.
  • Чистий бізнес-сервіс. У коді сервіса немає requests.adapters.HTTPAdapter(max_retries=...), tenacity, перевірки JWT, експорту метрик.

Недоліки

  • Зайвий хоп на кожен запит. Латентність трохи зростає (зазвичай порядку міллісекунд для well-tuned Envoy).
  • Ресурсна вартість. На кожен pod - додатковий процес з власною пам'яттю та CPU. У масштабі тисяч pod'ів це помітно.
  • Складність деплою. Pod зі sidecar'ом стає двоконтейнерним - діагностика, моніторинг, версіонування ускладнюються.

Реалізації

  • Envoy - найпоширеніший sidecar-проксі (C++), використовується в Istio, Consul Connect, AWS App Mesh.
  • Linkerd-proxy - легший Rust-проксі.
  • NGINX як sidecar - історична опція, менш гнучка за політиками.

Зв'язок з Service Mesh

Sidecar - data plane service mesh. Сам по собі sidecar - лише процес-проксі; інфраструктура, що ним керує (control plane, конфіги, сертифікати), - це і є service mesh.

Service Mesh

Summary

Service Mesh - інфраструктурний шар для управління міжсервісною комунікацією, що складається з data plane (sidecar-проксі біля кожного сервіса) і control plane (централізованого керування політиками, маршрутизацією, сертифікатами). Виносить cross-cutting логіку (mTLS, retry, метрики, traffic splitting) з коду сервісів у конфігурацію.

Архітектура

  • Data plane - sidecar'и (зазвичай Envoy) поруч з кожним екземпляром сервіса; перехоплюють вхідний і вихідний трафік.
  • Control plane - центральні компоненти (Istiod в Istio, Linkerd control plane), що видають data plane'у:
  • політики маршрутизації (VirtualService, DestinationRule),
  • сертифікати для mTLS (через SPIFFE/SPIRE),
  • конфіг retry/timeout/circuit breaker,
  • правила авторизації (AuthorizationPolicy).

Сервіс не знає про mesh - він робить звичайний http://orders/list, а sidecar розв'язує адресу, додає mTLS і метрики.

Можливості

  • mTLS між усіма сервісами - шифрування й взаємна автентифікація без правки коду.
  • Traffic management: canary release (5% трафіку на v2), shadow traffic, retry-budget, fault injection для chaos engineering.
  • Observability: автоматичні RED-метрики, distributed tracing для всіх викликів без правки коду.
  • Security: zero-trust політики "сервіс A може звертатися до сервіса B лише на /orders".

mTLS на рівні mesh

mTLS (mutual TLS) - двостороння автентифікація: і клієнт, і сервер пред'являють X.509-сертифікат. У звичайному TLS лише сервер автентифікує себе перед клієнтом, у mTLS - обидві сторони.

Переваги над JWT-перевіркою в коді кожного сервіса:

  • Ідентичність на рівні з'єднання, а не запиту. Sidecar встановлює mTLS-сесію один раз; усі запити в ній уже довірені. Не потрібно валідувати підпис JWT на кожен HTTP-виклик.
  • Один шар відповідальності. Видача й ротація сертифікатів - повністю в control plane (Istiod, SPIRE). Бізнес-код не знає про секрети.
  • Zero-trust за замовчуванням. Без mTLS неможливо встановити з'єднання, тому припущення про довірену внутрішню мережу відпадає - кожен виклик автентифікується криптографічно.
  • Інтеграція з RBAC. Політики виду "сервіс orders може звертатися до payments лише на endpoint /charge" описуються у декларативному конфізі mesh, а не в коді кожного сервіса.

JWT при цьому не зникає - він залишається механізмом передачі користувацької ідентичності (user_id, roles) крізь систему. mTLS вирішує сервісну ідентичність.

Реалізації

  • Istio + Envoy - найфункціональніший і найскладніший варіант.
  • Linkerd - простіший, легший Rust-проксі, менше функцій.
  • Consul Connect - mesh від HashiCorp, інтегрований з Consul service discovery.
  • AWS App Mesh - керована опція в AWS.

Сценарії застосування

  • Сотні мікросервісів на різних мовах із спільними вимогами до безпеки, спостережуваності, ретраїв.
  • Регуляторні вимоги до шифрування трафіку всередині кластера.
  • Складні стратегії релізу (canary, blue-green) на рівні маршрутизації.

Для невеликих систем (десятки сервісів, одна команда, одна мова) service mesh є надлишковим: ті самі задачі простіше вирішити бібліотекою на боці сервіса.

Distributed Tracing [💡10/100]

Summary

Distributed Tracing - спосіб простежити одиничний запит, що проходить через N мікросервісів, як єдину сутність. У монолітному застосунку стек-трейс покаже шлях запиту; у мікросервісах кожен сервіс має свій лог, і без явного Trace ID, що супроводжує запит, реконструювати картину неможливо. Канонічний стандарт - OpenTelemetry (об'єднання OpenTracing + OpenCensus, CNCF, 2019).

Проблема, яку розв'язує

Запит клієнта POST /orders у системі з 5 сервісами проходить через api-gateway → orders → inventory → payments → notifications. Якщо payments повертає 500, у логах notifications нічого не видно, а у логах orders - тільки факт, що "виклик payments впав". Без єдиного ідентифікатора неможливо знайти ВСІ записи, що належать цьому конкретному запиту, серед мільйонів інших.

Trace, span, parent-child

OpenTelemetry і попередники (Dapper, Zipkin) описують модель:

  • Trace - вся життєва історія одного запиту через систему. Має унікальний trace_id (наприклад, 128-bit UUID).
  • Span - одиничний відрізок роботи в межах одного сервісу (HTTP-call, DB-query, обробка повідомлення з MQ). Має span_id, parent_span_id (для побудови дерева), start_time, duration, attributes (теги).
  • Один Trace складається з багатьох Spans, утворюючи дерево або DAG.
Trace abc123 (POST /orders, 850ms total)
├── span_1: api-gateway (auth, 5ms)
└── span_2: orders.create (820ms)
    ├── span_3: orders.db_insert (40ms)
    ├── span_4: inventory.check (50ms)
    │   └── span_5: inventory.db_query (15ms)
    └── span_6: payments.charge (700ms)  ← bottleneck

Trace ID propagation

trace_id народжується на edge (API Gateway генерує його для нового запиту, який не приніс свого) і передається у кожен наступний синхронний/асинхронний виклик через HTTP-заголовки. Стандарт - W3C Trace Context (traceparent, tracestate).

HTTP/1.1 POST /charge
traceparent: 00-abc123def456-1234abcd-01

Кожен сервіс на ході:

  1. Витягує trace_id із вхідного запиту (або генерує, якщо немає).
  2. Створює власний span з цим trace_id і parent_span_id.
  3. Додає trace_id у структуровані логи ({"trace_id": "abc123", "msg": "..."}).
  4. Передає trace_id у вихідні виклики (HTTP-headers, Kafka-headers).

OpenTelemetry як стандарт

OpenTelemetry - єдиний sdk + протокол (OTLP) для збору метрик, логів і трейсів. Bтратив фрагментацію (раніше: OpenTracing для трейсів, OpenCensus для метрик, що дублювали один одного). Канонічний пайплайн:

[app + OTel SDK] → OTel Collector → backend (Jaeger / Tempo / Datadog / etc.)

Для популярних бібліотек (requests, aiohttp, SQLAlchemy, asyncpg, FastAPI middleware) існує auto-instrumentation - окремі пакети (opentelemetry-instrumentation-<lib>) обгортають виклики бібліотеки у hook'и і експортують span'и без зміни коду застосунку.

Log aggregation: ELK, Loki, CloudWatch

Trace ID у логах марний без агрегації - логи лежать на N машинах. Канонічний пайплайн:

  • ELK (Elasticsearch + Logstash + Kibana) - індексує всі логи, дозволяє query trace_id:abc123 для одиничного запиту.
  • Grafana Loki - простіший і дешевший за ELK, label-based індексація; query через LogQL.
  • Fluentd / Fluent Bit - log forwarder (заміна Logstash), збирає логи з контейнерів і відправляє у centralized сховище.
  • AWS CloudWatch Logs / GCP Cloud Logging - cloud-native варіанти.

Кореляція з метриками і алертами

Високий за тривалістю span у трейсі → алерт у Grafana → click на трейс у Jaeger → деталі span'у payments.charge → click на trace_id у Kibana → повний contextual лог. Це і є "observability" триада: logs + metrics + traces, об'єднані через trace_id/span_id.

Sidecar / Service Mesh як safety net

У Service Mesh (Istio + Envoy) інструментація трейсингу - автоматична: sidecar бачить весь HTTP-трафік, генерує/прокидає trace headers, експортує span'и без зміни коду сервісу. Деталі - у Service Mesh.

Links