Назад до мікросервісів разом із Istio. Частина 2

Назад до мікросервісів разом із Istio. Частина 2

Прим. перев.: Перша частина цього циклу була присвячена знайомству з можливостями Istio та їх демонстрації у дії. Тепер же мова піде про складніші аспекти конфігурації та використання цього service mesh, а зокрема — про маршрутизацію, що тонко налаштовується, і управління мережним трафіком.

Нагадуємо також, що у статті використовуються конфігурації (маніфести для Kubernetes та Istio) з репозиторію istio-mastery.

Управління трафіком

З Istio у кластері з'являються нові можливості, що дозволяють забезпечити:

  • Динамічну маршрутизацію запитів: канаркові викати, A/B-тестування;
  • Балансування навантаження: просту та несуперечливу, засновану на хешах;
  • Відновлення після падіння: таймаути, повторні спроби, circuit breakers;
  • Внесення несправностей: затримки, обрив запитів тощо.

У продовженні статті ці можливості будуть показані на прикладі вибраного додатка та принагідно представлені нові концепції. Першою такою концепцією стане DestinationRules (тобто правила про одержувача трафіку/запитів - прим. перекл.), за допомогою яких ми активуємо A/B тестування.

A/B-тестування: DestinationRules на практиці

A/B тестування застосовується у випадках, коли існують дві версії програми (зазвичай вони відрізняються візуально) і ми не впевнені на 100%, яка з них покращить взаємодію з користувачем. Тому ми одночасно запускаємо обидві версії та збираємо метрики.

Для деплою другої версії фронтенда, необхідної для демонстрації A/B-тестування, виконайте наступну команду:

$ kubectl apply -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions/sa-frontend-green created

Маніфест deployment'а для «зеленої версії» відрізняється у двох місцях:

  1. Образ заснований на іншому тегу istio-green,
  2. Pod'и мають лейбл version: green.

Оскільки обидва deployment'а мають лейбл app: sa-frontend, запити, що маршрутизуються віртуальним сервісом sa-external-services на сервіс sa-frontend, будуть перенаправлені на всі його екземпляри та навантаження розподілиться за допомогою алгоритму round-robin, що призведе до наступної ситуації:

Назад до мікросервісів разом із Istio. Частина 2
Запитані файли не знайдені

Ці файли не знайшли через те, що вони по-різному називаються в різних версіях програми. Давайте переконаємось у цьому:

$ curl --silent http://$EXTERNAL_IP/ | tr '"' 'n' | grep main
/static/css/main.c7071b22.css
/static/js/main.059f8e9c.js
$ curl --silent http://$EXTERNAL_IP/ | tr '"' 'n' | grep main
/static/css/main.f87cd8c9.css
/static/js/main.f7659dbb.js

Це означає, що index.html, що запитує одну версію статичних файлів, може бути відправлений балансувальником навантаження на pod'и, що мають іншу версію, де, зі зрозумілих причин, таких файлів не існує. Тому для того, щоб програма запрацювала, нам необхідно поставити обмеження: «та ж версія програми, що віддала index.html, має обслужити й наступні запити».

Ми досягнемо мети за допомогою несуперечливого балансування навантаження на основі хешів (Consistent Hash Loadbalancing). У цьому випадку запити від одного клієнта надсилаються в один і той же екземпляр бекенду, для чого використовується певна властивість - наприклад, HTTP-заголовок. Реалізується за допомогою DestinationRules.

DestinationRules

Після того, як VirtualService надіславши запит у потрібний сервіс, за допомогою DestinationRules ми можемо визначити політики, які будуть застосовуватися до трафіку, що призначається екземплярам цього сервісу:

Назад до мікросервісів разом із Istio. Частина 2
Управління трафіком з ресурсами Istio

Примітка: Вплив ресурсів Istio на мережевий трафік представлений тут у спрощеному для розуміння вигляді. Якщо бути точним, то рішення, на який екземпляр надсилати запит, робиться Envoy'ем в Ingress Gateway, налаштованим у CRD.

За допомогою Destination Rules ми можемо налаштувати балансування навантаження так, щоб використовувалися несуперечливі хеші та гарантувалися відповіді одного екземпляра сервісу одному й тому ж користувачу. Наступна конфігурація дозволяє досягти цього (destinationrule-sa-frontend.yaml):

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: sa-frontend
spec:
  host: sa-frontend
  trafficPolicy:
    loadBalancer:
      consistentHash:
        httpHeaderName: version   # 1

1 — хеш генеруватиметься на основі вмісту HTTP-заголовка version.

Застосуйте конфігурацію наступною командою:

$ kubectl apply -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io/sa-frontend created

А тепер виконайте команду нижче та переконайтеся, що отримуєте потрібні файли, коли вказуєте заголовок version:

$ curl --silent -H "version: yogo" http://$EXTERNAL_IP/ | tr '"' 'n' | grep main

Примітка: Щоб додавати різні значення в заголовку та тестувати результати прямо у браузері, можна скористатися цим розширенням до Chrome (або ось цим для Firefox - прим. перев.).

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

Перед тим, як вивчати VirtualService далі, видалимо «зелену версію» програми та відповідне правило за направленням трафіку, виконавши наступні команди:

$ kubectl delete -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions “sa-frontend-green” deleted
$ kubectl delete -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io “sa-frontend” deleted

Дзеркаловання: Virtual Services на практиці

Тінізація («екранування») або Mirroring («дзеркалювання») застосовується у тих випадках, коли ми хочемо протестувати зміну в production, не торкнувшись кінцевих користувачів: для цього ми дублюємо («дзеркалуємо») запити на другий екземпляр, де зроблено потрібні зміни, і дивимося на наслідки. Простіше кажучи, це коли ваш(а) колега вибирає найкритичніший issue і робить pull request у вигляді такого величезного грудки бруду, що ніхто не може насправді зробити йому ревью.

Щоб перевірити цей сценарій у дії, створимо другий екземпляр SA-Logic з багами (buggy), виконавши наступну команду:

$ kubectl apply -f resource-manifests/kube/shadowing/sa-logic-service-buggy.yaml
deployment.extensions/sa-logic-buggy created

І тепер виконаємо команду, щоб переконатися, що всі екземпляри з app=sa-logic мають ще й лейбли з відповідними версіями:

$ kubectl get pods -l app=sa-logic --show-labels
NAME                              READY   LABELS
sa-logic-568498cb4d-2sjwj         2/2     app=sa-logic,version=v1
sa-logic-568498cb4d-p4f8c         2/2     app=sa-logic,version=v1
sa-logic-buggy-76dff55847-2fl66   2/2     app=sa-logic,version=v2
sa-logic-buggy-76dff55847-kx8zz   2/2     app=sa-logic,version=v2

Сервіс sa-logic націлений на pod'и з лейблом app=sa-logicтому всі запити будуть розподілені між усіма екземплярами:

Назад до мікросервісів разом із Istio. Частина 2

… але ми хочемо, щоб запити надсилалися на екземпляри з версією v1 та дзеркувалися на екземпляри з версією v2:

Назад до мікросервісів разом із Istio. Частина 2

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

Визначення підмножин у Destination Rules

Підмножини (subsets) визначаються наступною конфігурацією (sa-logic-subsets-destinationrule.yaml):

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: sa-logic
spec:
  host: sa-logic    # 1
  subsets:
  - name: v1        # 2
    labels:
      version: v1   # 3
  - name: v2
    labels:
      version: v2

  1. Хост (host) визначає, що це правило застосовується лише до випадків, коли маршрут іде у бік сервісу sa-logic;
  2. Назви (name) підмножини використовуються при маршрутизації на екземпляри підмножини;
  3. Лейбл (label) визначає пари ключ-значення, яким повинні відповідати екземпляри, щоб стати частиною підмножини.

Застосуйте конфігурацію наступною командою:

$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-destinationrule.yaml
destinationrule.networking.istio.io/sa-logic created

Тепер, коли підмножини визначені, можна рухатися далі і налаштувати VirtualService, щоб застосувати правила до запитів до sa-logic, щоб вони:

  1. Маршрутизувалися до підмножини v1,
  2. Дзеркалірувались до підмножини v2.

Наступний маніфест дозволяє досягти задуманого (sa-logic-subsets-shadowing-vs.yaml):

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: sa-logic
spec:
  hosts:
    - sa-logic          
  http:
  - route:
    - destination:
        host: sa-logic  
        subset: v1      
    mirror:             
      host: sa-logic     
      subset: v2

Пояснення тут не потрібні, тому просто подивимося в дії:

$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-shadowing-vs.yaml
virtualservice.networking.istio.io/sa-logic created

Додамо навантаження викликом такої команди:

$ while true; do curl -v http://$EXTERNAL_IP/sentiment 
    -H "Content-type: application/json" 
    -d '{"sentence": "I love yogobella"}'; 
    sleep .8; done

Подивимося на результати в Grafana, де можна побачити, що версія з багами (buggy) призводить до збою для ~60% запитів, але жоден із цих збоїв не зачіпає кінцевих користувачів, оскільки їм відповідає працюючий сервіс.

Назад до мікросервісів разом із Istio. Частина 2
Успішність відповідей різних версій сервісу sa-logic

Тут ми вперше побачили, як VirtualService застосовується по відношенню до Envoy'ям наших сервісів: коли sa-web-app робить запит до sa-logic, він проходить через sidecar Envoy, який - через VirtualService - налаштований на маршрутизацію запиту до підмножини v1 та дзеркаловання запиту до підмножини v2 сервісу sa-logic.

Знаю, ви вже встигли подумати, що Virtual Services прості. У наступному розділі ми розширимо цю думку тим, що вони ще й по-справжньому чудові.

Канаркові викати

Canary Deployment – ​​процес викочування нової версії програми для невеликої кількості користувачів. Його використовують, щоб переконатися у відсутності проблем у релізі і тільки після цього, вже будучи впевненим у достатній його (релізі) якості, поширити на бобільшу аудиторію.

Для демонстрації канаркових викатів ми продовжимо роботу з підмножиною buggy у sa-logic.

Не будемо дріб'язуватися і відразу ж відправимо 20% користувачів на версію з багами (вона і представлятиме наш канарковий викочування), а 80%, що залишилися, — на нормальний сервіс. Для цього застосуємо наступний VirtualService (sa-logic-subsets-canary-vs.yaml):

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: sa-logic
spec:
  hosts:
    - sa-logic    
  http:
  - route: 
    - destination: 
        host: sa-logic
        subset: v1
      weight: 80         # 1
    - destination: 
        host: sa-logic
        subset: v2
      weight: 20 # 1

1 - це вага (weight), визначальний відсоток запитів, які будуть направлені на отримувача або підмножину отримувача.

Оновимо минулу конфігурацію VirtualService для sa-logic наступною командою:

$ kubectl apply -f resource-manifests/istio/canary/sa-logic-subsets-canary-vs.yaml
virtualservice.networking.istio.io/sa-logic configured

... і відразу побачимо, що частина запитів призводить до збоїв:

$ while true; do 
   curl -i http://$EXTERNAL_IP/sentiment 
   -H "Content-type: application/json" 
   -d '{"sentence": "I love yogobella"}' 
   --silent -w "Time: %{time_total}s t Status: %{http_code}n" 
   -o /dev/null; sleep .1; done
Time: 0.153075s Status: 200
Time: 0.137581s Status: 200
Time: 0.139345s Status: 200
Time: 30.291806s Status: 500

VirtualServices активують канаркові викати: в даному випадку ми звузили потенційні наслідки від проблем до 20% від користувальницької бази. Прекрасно! Тепер у кожному випадку, коли ми не впевнені у своєму коді (іншими словами завжди), ми можемо використовувати дзеркалювання та канаркові викати.

Таймаути та повторні спроби

Але не завжди баги опиняються у коді. У списку з «8 помилок у розподілених обчисленнях» на першому місці значиться помилкова думка, що «мережа надійна». Насправді мережа НЕ надійна, і з цієї причини нам потрібні таймаути (timeouts) та повторні спроби (повторна спроба).

Для демонстрації ми продовжимо використовувати ту саму проблему версію sa-logic (buggy), а ненадійність мережі симулюватимемо випадковими збоями.

Нехай наш сервіс з багами має 1/3 шанс на надто довгу відповідь, 1/3 – на завершення з помилкою Internal Server Error та 1/3 – на успішну віддачу сторінки.

Щоб пом'якшити наслідки від подібних проблем і зробити життя користувачів кращим, ми можемо:

  1. додати тайм, якщо сервіс відповідає довше 8 секунд,
  2. робити повторну спробу, якщо запит має збій.

Для реалізації скористаємося таким визначенням ресурсу (sa-logic-retries-timeouts-vs.yaml):

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: sa-logic
spec:
  hosts:
    - sa-logic
  http:
  - route: 
    - destination: 
        host: sa-logic
        subset: v1
      weight: 50
    - destination: 
        host: sa-logic
        subset: v2
      weight: 50
    timeout: 8s           # 1
    retries:
      attempts: 3         # 2
      perTryTimeout: 3s # 3

  1. Таймаут для запиту встановлено за 8 секунд;
  2. Повторні спроби запитів по 3 рази;
  3. І кожна спроба вважається невдалою, якщо час відповіді перевищує 3 секунди.

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

Застосуйте оновлену конфігурацію наступною командою:

$ kubectl apply -f resource-manifests/istio/retries/sa-logic-retries-timeouts-vs.yaml
virtualservice.networking.istio.io/sa-logic configured

І перевірте у графіках Grafana, що кількість успішних відповідей стала понад:

Назад до мікросервісів разом із Istio. Частина 2
Поліпшення у статистиці успішних відповідей після додавання таймутів та повторних спроб

Перед переходом до наступного розділу (а точніше - вже до наступної частини статті, тому що в цій практичних експериментів більше не буде - прим. перекл.), видаліть sa-logic-buggy та VirtualService, виконавши наступні команди:

$ kubectl delete deployment sa-logic-buggy
deployment.extensions “sa-logic-buggy” deleted
$ kubectl delete virtualservice sa-logic
virtualservice.networking.istio.io “sa-logic” deleted

Паттерни Circuit Breaker та Bulkhead

Йдеться про два важливі патерни в мікросервісній архітектурі, які дозволяють добитися самостійного відновлення (self-healing) сервісів.

Автоматичний вимикач ("автоматичний вимикач") використовується для припинення запитів, що надходять на екземпляр сервісу, який вважається нездоровим, та його відновлення у той час, як запити клієнтів перенаправляються на здорові екземпляри цього сервісу (що підвищує відсоток успішних відповідей). (Прим. перекл.: Докладніше опис патерну можна знайти, наприклад, тут.)

Перегородка («перегородка») ізолює збої у сервісах від поразки всієї системи. Наприклад, сервіс B зламаний, а інший сервіс (клієнт сервісу B) робить запит до сервісу B, внаслідок чого він витратить свій пул потоків і не зможе обслуговувати інші запити (навіть якщо вони не належать до сервісу B). (Прим. перекл.: Докладніше опис патерну можна знайти, наприклад, тут.)

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

PS від перекладача

Читайте також у нашому блозі:

Джерело: habr.com

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