Istio Circuit Breaker: відключаємо несправні контейнери

Свята завершилися, і ми повертаємось з нашим другим постом із серії Istio Service Mesh.

Istio Circuit Breaker: відключаємо несправні контейнери

Сьогоднішня тема - Circuit Breaker, що в перекладі на російський електротехнічний означає "автоматичний вимикач", просто - "автомат захисту". Тільки в Istio цей автомат відключає не коротнувший або перевантажений ланцюг, а несправні контейнери.

Як це має працювати в ідеалі

Коли мікросервіси керуються Kubernetes'ом, наприклад, у рамках платформи OpenShift, вони автоматично масштабуються вгору-вниз залежно від навантаження. Оскільки мікросервіси працюють у pod'ах, на одній кінцевій точці може бути одразу кілька екземплярів контейнеризованого мікросервісу, а Kubernetes маршрутизуватиме запити та балансуватиме навантаження між ними. І – в ідеалі – все це має чудово працювати.

Ми пам'ятаємо, що мікросервіси – маленькі та ефемерні. Ефемерність, яка означає простоту виникнення і зникнення, часто недооцінюють. Народження та смерть чергового екземпляра мікросервісу в pod'є – речі цілком очікувані, OpenShift та Kubernetes з цим добре справляються, і все чудово працює – але знову ж таки в теорії.

Як це працює насправді

А тепер уявіть, що якийсь конкретний екземпляр мікросервісу, тобто контейнер, став непридатним: або не відповідає (помилка 503), або - що неприємніше - реагує, але занадто повільно. Інакше кажучи, він підглючує або не відповідає на запити, але з пулу він при цьому автоматично не забирається. Що треба робити у цьому випадку? Повторити спробу? Прибрати його із схеми маршрутизації? І що означає «надто повільно» – скільки це в цифрах, і хто їх визначає? Можливо, просто дати йому паузу та спробувати пізніше? Якщо так, то наскільки пізніше?

Що таке Pool Ejection у Istio

І тут на допомогу приходить Istio зі своїми автоматами захисту Circuit Breaker, які тимчасово видаляють несправні контейнери з пулу ресурсів маршрутизації та балансування навантаження, реалізуючи процедуру Pool Ejection.

Використовуючи стратегію виявлення відхилень (outlier detection), Istio детектує криві pod'и, які вибиваються із загального ряду, та прибирає їх з пулу ресурсів на заданий час, який називається «вікно сну» (sleep window).

Щоб показати, як це працює в Kubernetes на платформі OpenShift, почнемо зі скріншота мікросервісів, що нормально працюють, з прикладу в репозиторії Red Hat Developer Demos. Тут у нас є два pod'а, v1 та v2, у кожному з яких працює по одному контейнеру. Коли правила маршрутизації Istio не використовуються, Kubernetes за промовчанням застосовує рівномірно збалансовану циклічну маршрутизацію:

Istio Circuit Breaker: відключаємо несправні контейнери

Готуємось до збою

Перш ніж робити Pool Ejection, треба створити правило маршрутизації Istio. Припустимо, ми хочемо розподіляти запити між pod'ами щодо 50/50. Крім того, ми збільшимо кількість контейнерів v2 з одного до двох, так:

oc scale deployment recommendation-v2 --replicas=2 -n tutorial

Тепер задаємо правило маршрутизації, щоб трафік розподілявся між pod'ами щодо 50/50.

Istio Circuit Breaker: відключаємо несправні контейнери
А ось як виглядає результат роботи цього правила:

Istio Circuit Breaker: відключаємо несправні контейнери
Можна причепитися, що на цьому скрині не 50/50, а 14:9, але згодом ситуація виправиться.

Влаштовуємо збій

А тепер виведемо з ладу один із двох контейнерів v2, щоб у нас був один справний контейнер v1, один справний контейнер v2 та один несправний контейнер v2:

Istio Circuit Breaker: відключаємо несправні контейнери

Чиним збій

Отже, у нас є несправний контейнер, і настав час Pool Ejection. За допомогою дуже простого конфігу ми виключимо цей збійний контейнер з будь-яких схем маршрутизації на 15 секунд для того, що він сам повернеться в справний стан (або перезапуститься, або відновить продуктивність). Ось як виглядає цей конфіг та результати його роботи:

Istio Circuit Breaker: відключаємо несправні контейнери
Istio Circuit Breaker: відключаємо несправні контейнери
Як видно, несправний контейнер v2 більше не використовується для маршрутизації запитів, оскільки його прибрали з пула. Але через 15 секунд він автоматично повернеться в пул. Власне, ми щойно показали, як працює Pool Ejection.

Починаємо будувати архітектуру

Pool Ejection у поєднанні з можливостями моніторингу Istio дозволяє почати вибудовувати фреймворк автоматичної заміни несправних контейнерів, щоб скоротити, а то й зовсім усунути простої та збої.

NASA має один гучний девіз – Failure Is Not an Option, автором якого вважається керівник польотів Джин Кранц. Російською його можна перекласти як «Поразка – це не варіант», і сенс тут у тому, що все можна змусити працювати, маючи на те достатньо волі. Однак у реальному житті відмови не просто трапляються, вони неминучі, скрізь та у всьому. І як же з ними впоратися у випадку мікросервісів? На наш погляд, краще покладатися не на силу волі, а на можливості контейнерів, Кубернетес, Red Hat OpenShift, І Істіо.

Istio, як ми вже писали вище, реалізує концепцію автоматичних вимикачів, що чудово зарекомендувала себе у фізичному світі. І як електричний автомат відключає проблемну ділянку ланцюга, так і програмний Circuit Breaker в Istio розмикає зв'язок між потоком запитів та проблемним контейнером, коли з кінцевою точкою щось не в порядку, наприклад коли сервер впав або почав гальмувати.

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

Circuit Breaker у теорії

Circuit Breaker – це проксі, який контролює потік до запитів до кінцевої точки. Коли ця точка перестає працювати або – залежно від налаштувань – починає гальмувати, проксі розриває зв'язок з контейнером. Трафік після цього перенаправляється на інші контейнери, просто через балансування навантаження. Зв'язок залишається розімкнутим (open) протягом заданого вікна сну, скажімо, дві хвилини, а потім вважається напіврозімкненим (half-open). Спроба надіслати наступний запит визначає подальший стан зв'язку. Якщо з сервісом все ОК, зв'язок повертається до робочого стану і знову стає замкненим (closed). Якщо ж з сервісом, як і раніше, щось не те, зв'язок розмикається і заново включається вікно сну. Ось як виглядає спрощена діаграма зміни станів Circuit Breaker:

Istio Circuit Breaker: відключаємо несправні контейнери
Тут важливо зазначити, що це відбувається лише на рівні, так би мовити, системної архітектури. Тому в якийсь момент вам доведеться навчити свої програми працювати з Circuit Breaker, наприклад, надавати у відповідь значення за умовчанням або, якщо це можливо, ігнорувати існування сервісу. Для цього використовується bulkhead pattern, але він виходить за межі цієї статті.

Circuit Breaker на практиці

Наприклад, ми запустимо на OpenShift дві версії нашого мікросервісу рекомендацій. Версія 1 працюватиме нормально, а ось у v2 ми вбудуємо затримку, щоб імітувати гальма на сервері. Для перегляду результатів використовується інструмент облога:

siege -r 2 -c 20 -v customer-tutorial.$(minishift ip).nip.io

Istio Circuit Breaker: відключаємо несправні контейнери
Все начебто працює, але якою ціною? На перший погляд, у нас 100% доступність, але придивіться – максимальна тривалість транзакції становить цілих 12 секунд. Це явно вузьке місце, і його треба розшивати.

Для цього ми за допомогою Istio виключимо звернення до повільних контейнерів. Ось як виглядає відповідний конфіг із використанням Circuit Breaker:

Istio Circuit Breaker: відключаємо несправні контейнери
Останній рядок з параметром httpMaxRequestsPerConnection сигналізує, що зв'язок з повинен розмикатися при спробі створити ще одне - друге - підключення ще до наявного. Оскільки наш контейнер імітує гальмуючий сервіс, такі ситуації періодично виникатимуть, і тоді Istio повертатиме помилку 503, а ось що покаже siege:

Istio Circuit Breaker: відключаємо несправні контейнери

ОК, у нас є Circuit Breaker, що далі?

Отже, ми продали автоматичне відключення, абсолютно не чіпаючи вихідний код самих сервісів. Використовуючи Circuit Breaker та описану вище процедуру Pool Ejection, ми можемо прибирати з пулу ресурсів гальмівні контейнери доти, доки вони не прийдуть у норму, і перевіряти їх стан із заданою періодичністю – у нашому прикладі, це дві хвилини (параметр sleepWindow).

Зверніть увагу, що здатність програми реагувати на помилку 503 все ще визначається на рівні його вихідного коду. Існує безліч стратегій роботи з Circuit Breaker, які використовуються залежно від ситуації.

У наступному пості: розповімо про трасування та моніторинг, які вже вбудовані або легко додаються до Istio, а також про те, як вносити помилки в систему навмисно.

Джерело: habr.com

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