Виявлення сервісів в Stripe

Кожен рік з'являється стільки нових технологій (таких як Kubernetes або Habitat), що легко забути про тих інструментах, які тихо і непомітно підтримують наші системи в промисловій експлуатації. Одним з таких інструментів, який ми використовуємо в Stripe протягом декількох років, є Consul. Consul допомагає у виявленні сервісів (тобто допомагає знаходити тисячі працюючих у нас серверів з запущеними на них тисячами різних сервісів і повідомляти, які з них доступні для використання). Це ефективне і практичне архітектурне рішення не було чимось зовсім новим і особливо помітним, але він вірою і правдою служить справі надання надійних сервісів нашим користувачам по всьому світу.
У цій статті ми збираємося поговорити про наступне:
  • Що таке виявлення сервісів і Consul.
  • Як ми управляли ризиками, що виникали при впровадженні критично важливого програмного продукту.
  • Виклики, з якими ми зіткнулися, і наші відповіді на ці виклики.
Добре відомо, що не можна просто так взяти і встановити нове ПЗ, розраховуючи, що воно якось магічно буде працювати і вирішувати всі проблеми. Використання нового ПЗ — це процес. І в цій статті описується приклад того, яким виявився для нас процес використання нового в промисловій експлуатації.
image
Що таке виявлення сервісів?
Відмінний питання! Уявіть себе балансировщиком навантаження компанії Stripe. До вас надходить запит на проведення платежу, який потрібно скерувати на сервер API. Будь північ API!
У нас працюють тисячі серверів, на яких запущені різні сервіси. Які з них сервери API? На якому порту запущений сервіс API? Одним з «чудодійних» властивостей Amazon Web Services є те, що наші инстансы можуть вийти з ладу в будь-який момент, тому ми повинні бути готові:
  • втратити будь-сервер API;
  • додати в обіг нові сервери, якщо потрібна додаткова потужність.
Відстеження змін стану доступності мережевих служб називається виявленням сервісів (service discovery). Для виконання цієї роботи ми використовуємо інструмент під назвою Consul, що розробляється HashiCorp.
Той факт, що наші инстансы в будь-який момент можуть вийти з ладу, насправді дуже допомагає, так як наша інфраструктура постійно практикується, регулярно втрачаючи инстансы і дозволяючи такі ситуації в автоматичному режимі. Тому, коли непередбачена ситуація трапляється, нічого страшного не відбувається. Це звичайна справа, рутина. Тим більше що коректно відпрацювати збій набагато простіше, коли збої відбуваються досить часто.
Введення в Consul
Consul — це інструмент виявлення сервісів, який дає можливість сервісів реєструватися і знаходити інші сервіси. З допомогою спеціального клієнта Consul записує інформацію про працюючих сервісах в БД, а інше клієнтське ПО користується цією базою. Складових частин тут чимало — є з чим повозитися.
Найголовніша частина Consul — це база даних. Вона містить записи типу «api-service запущений на IP 10.99.99.99, порт 12345. Він доступний».
Окремі машини говорять Consul: «Привіт! У мене запущений api-service, порт 12345! Я живий!»
Потім, якщо потрібно звернутися до API-сервісу, можна запитати Consul «Які з api-services доступні?» У відповідь він поверне список відповідних IP-адрес і портів.
Сам Consul — теж розподілена система (пам'ятаєте, що ми можемо втратити будь-яку машину в будь-який час, і Consul в тому числі!), тому, щоб підтримувати базу даних в синхронізованому стані, в Consul використовується алгоритм досягнення консенсусу під назвою Raft.
Почитати про досягнення консенсусу в Consul тут.
Consul в Stripe: Початок
На першому етапі ми обмежилися конфігурацією, в якій машини з клієнтом Consul на борту писали на сервер звіти про свою доступності. Для виявлення сервісів ця інформація не використовувалася. Щоб налаштувати таку систему, ми написали конфігурацію Puppet, що виявилося не так вже й складно!
Таким чином ми змогли виявити потенційні проблеми з клієнтом Consul і отримати досвід його використання на тисячах машин. На початковому етапі ми не застосовували Consul для виявлення будь-яких сервісів.
Що тут могло піти не так?

Витоку пам'яті

Якщо ви ставите будь-то на всі машини своєї інфраструктури, у вас однозначно можуть початися проблеми. Практично відразу ми зіткнулися з витоками пам'яті статистичної бібліотеці Consul. Ми помітили, що одна з машин зайняла більше 100 МБ оперативної пам'яті, і споживання зростала. Всьому виною була помилка в Consul, яку ми поправили.
100 МБ — невеликий витік, але вона швидко збільшувалася. У загальному випадку витоку пам'яті представляють серйозну небезпеку, так як із-за них всього один процес може повністю паралізувати машину, на якій запущено.
Як добре, що ми не стали з самого початку використовувати Consul для виявлення сервісів! Нам вдалося уникнути серйозних проблем і практично безболісно усунути знайдені помилки за рахунок того, що спочатку ми дали Consul попрацювати на кількох бойових серверах, при цьому стежачи за споживанням пам'яті.

Починаємо займатися виявленням сервісів

Оскільки ми були впевнені, що з виконанням Consul на наших серверах не буде проблем, ми почали додавати клієнтів, опитувальними Consul. Щоб знизити ризики, ми зробили наступне:
  • для початку стали використовувати Consul обмежено;
  • зберегли резервну систему, щоб продовжувати функціонувати під час перебоїв в роботі Consul.
Ось кілька проблем, з якими довелося зіткнутися. Тут ми не намагаємося скаржитися на Consul, а радше хочемо підкреслити, що при використанні нової технології дуже важливо розгортати систему не кваплячись і бути обачним.
Величезна кількість перемикань Raft. Пам'ятайте, що Consul використовує протокол досягнення консенсусу? Він копіює всі дані одного сервера кластера Consul на інші сервери цього кластера. У первинного сервера було багато проблем з введенням/виводом: диски були недостатньо швидкі і не встигали виконати всі побажання Consul, що призводило до зависання первинного сервера цілком. Raft говорив: «Ох, первинний сервер знову недоступний!», вибирав новий первинний сервер, і цей порочний цикл повторювався. В той час коли Consul був зайнятий виборами нового первинного сервера, він блокував базу даних на запис і читання (так як за замовчуванням встановлені погоджені читання).
У версії 0.3 повністю зламали SSL. Для забезпечення безпечного обміну даними між нашими нодами ми використовували SSL-функціонал Consul (технічно, TLS), який був успішно відбитий у черговому релізі. Ми його полагодили. Це приклад такого роду проблем, які неважко виявити і яких не варто боятися (в циклі QA ми усвідомили, що SSL зламаний, і просто не стали переходити на новий реліз), але вони досить поширені в програмному забезпеченні, що знаходиться на ранніх стадіях розробки.
Витоку goroutine. Ми почали використовувати вибори лідера Consul, а там виявилася витік goroutine, приводила до того, що Consul з'їдав всю доступну пам'ять. Люди з команди Consul нам дуже допомогли у вирішенні цієї проблеми, і ми прибрали кілька витоків пам'яті (вже інших, не тих, що знайшли раніше).
Коли всі проблеми були виправлені, ми опинилися в набагато кращому становищі. Дорога від «наш перший клієнт Consul» до «ми виправили всі проблеми у production» зайняла трохи менше року виконання фонових робочих циклів.
Масштабування Consul з метою виявлення доступних сервісів
Отже, ми знайшли і виправили кілька помилок в Consul. Все стало працювати набагато краще. Але пам'ятайте, про якому кроці ми говорили на початку? Де ви питаєте Consul: «Хей, які машинки доступні для api-service?» З такими запитами у нас періодично виникали проблеми: сервер Consul відповідав повільно або зовсім не відповідав.
В основному це відбувалося під час raft-перемикань або періодів нестабільності. Оскільки Consul використовує суворо узгоджене сховище, його доступність завжди буде гірше, ніж доступність системи, що не має таких обмежень. Особливо важко нам доводилося в самому початку.
У нас все ще відбувалися перемикання, і перебої в роботі Consul стали для нас досить болючими. У таких ситуаціях ми переходили на жорстко прописаний набір DNS-імен (наприклад, «apibox1»). Коли ми тільки розгорнули Consul, такий підхід працював нормально, але в процесі масштабування і розширення області застосування Consul він ставав все менш життєздатним.

Consul Template в допомогу

Ми не могли покластися на одержання від Consul інформації про доступність сервісів (через HTTP API). Але в усьому іншому він нас повністю влаштовував!
Нам хотілося отримувати від Consul інформацію про доступні сервіси не через його API. Але як це зробити?
ДОБРЕ, Consul бере ім'я (наприклад, monkey-srv) і переводить його в один або кілька IP-адрес («ось де живе monkey-srv»). Знаєте, хто ще отримує на вхід ім'я і повертає IP-адресу? DNS-сервер! Загалом, ми замінили Consul DNS-сервером. Ось як ми це зробили: Consul Template — це програма Go, яка генерує статичні конфігураційні файли на основі бази даних Consul.
Ми почали використовувати Consul Template для генерації DNS-записів про сервіси Consul. Якщо monkey-srv був запущений на IP 10.99.99.99, ми генерували DNS запис:
monkey-srv.service.consul IN A 10.99.99.99

Код виглядає так. А тут ви можете знайти нашу робочу конфігурацію Consul Template, яка трохи складніше.
{{range service $service.Name}}
{{$service.Name}}.service.consul. IN A {{.Address}}
{{end}}

Якщо ви скажете: «Зачекайте, в записах DNS є тільки IP-адресу, а потрібен ще й порт!», то будете абсолютно праві! DNS A-записи (той тип, який найчастіше зустрічається) містять тільки IP-адресу. Однак DNS SRV-записи можуть мати також і порт, тому наш Consul Template генерує SRV-запису.
Ми запускаємо Consul Template з допомогою cron кожні 60 секунд. У Consul Template також є встановлений за замовчуванням режим стеження («watch» mode), в якому файли конфігурації постійно оновлюються по мірі надходження нової інформації в базу даних. Коли ми в перший раз включили режим стеження, він поклав наш сервер Consul надмірним кількість запитів, тому ми вирішили від неї відмовитися.
Отже, поки наш Consul-сервер недоступний, на внутрішньому DNS-сервері є всі необхідні записи! Можливо, вони не зовсім свіжі, але нічого страшного. У нашого DNS-сервера є одна чудова особливість: він не є новомодної розподіленої мегасистемой, і відносна простота робить його менш схильним до раптових поломок. Тобто я можу просто зробити nslookup monkey-srv.service.consul, отримати IP і нарешті почати працювати з моїм API-сервісом!
Оскільки DNS є неподілюване, в кінцевому рахунку узгодженою системою, ми можемо її багаторазово кешувати реплікувати (у нас є 5 канонічних DNS-серверів, на кожному з яких є локальний DNS-кеш і інформація про решту 5 канонічних серверах). Тому наша резервна DNS-система просто за визначенням набагато надійніше Consul.

Додаємо балансувальник навантаження для більш швидких перевірок стану

Тільки що ми говорили про те, що оновлюємо DNS-записи на підставі даних з Consul кожні 60 секунд. Так що ж відбувається, коли ми втрачаємо API-сервер? Продовжуємо ми направляти запити на цей IP протягом приблизно 45 секунд, поки не оновиться DNS? Ні! У цій історії є ще один герой — HAProxy.
HAProxy — це балансувальник навантаження. Якщо налаштувати відповідні перевірки, він може стежити за станом сервісів, на які пересилає запити. Всі наші API-запити за фактом йдуть через HAProxy. Ось як це працює.
  • Consul Template кожні 60 секунд переписує конфігураційний файл HAProxy.
  • Таким чином, у HAProxy завжди є більш або менш коректний список доступних внутрішніх серверів.
  • Якщо машина йде в off-line, HAProxy дізнається про це достатньо швидко (він виконує перевірки кожні 2 секунди).
Виходить, що ми перезапускаємо HAProxy кожні 60 секунд. Але чи означає це, що ми заодно обриваємо з'єднання? Немає. Щоб уникнути скидання з'єднань під час перезапусків, ми використовуємо функцію м'якого перезапуску (graceful restart) HAProxy. При цьому зберігається ризик втратити деяку кількість мережевого трафіку (відповідно до цієї статті), але наш трафік не настільки великий, щоб це стало проблемою.
Ми використовуємо стандартну кінцеву точку (endpoint) перевірки стану сервісів. Практично у кожного сервісу є кінцева точка /healthcheck, яка поверне 200, якщо з сервісом все в порядку, і видасть помилки, якщо щось пішло не так. Наявність стандарту дуже важливо, так як допомагає нам легко настроювати HAProxy перевірки стану сервісів.
Якщо Consul падає, у HAProxy залишається в наявності нехай і трохи неактуальна, але цілком працездатна конфігурація.
Міняємо узгодженість на доступність
Якщо ви уважно стежили за розповіддю, то могли помітити, що система, з якої ми почали (суворо узгоджена база даних, гарантовано має всі оновлення), дуже сильно відрізняється від системи, до якої ми прийшли (DNS-сервер, який може запізнюватися на час, що доходить до однієї хвилини). Відмова від узгодженості дозволив нам створити набагато більш чуйну систему, так як перебої в роботі Consul практично не впливають на нашу здатність виявляти сервіси.
Звідси можна отримати корисний урок: узгодженість має свою ціну! Потрібно бути готовим заплатити за систему з хорошою доступністю. І якщо ви збираєтеся використовувати строгу узгодженість, важливо переконатися, що вам потрібна саме вона.

Що відбувається при створенні запиту

У цій статті ми вже багато встигли поговорити, так що давайте тепер подивимося на шлях, яким йдуть запити, раз вже ми розібралися, як це працює.
Що відбувається, коли ви запитуєте https://stripe.com/? Як цей запит потрапляє на потрібний сервер? Ось декілька спрощене пояснення.
  1. Спочатку запит приходить на один з наших публічних балансировщиков навантаження, на якому запущений HAProxy.
  2. Consul Template вже записав список серверів, що обслуговують stripe.com у конфігураційний файл /etc/haproxy.conf.
  3. HAProxy перезавантажує свою конфігурацію кожні 60 секунд.
  4. HAProxy пересилає запит на сервер stripe.com, переконавшись, що він доступний.
У реальному житті процес не такий простий (там є додатковий шар, так і запити Stripe API складніше, так як у нас є системи, що забезпечують дотримання стандарту PCI), але реалізація виконана згідно ідей, описаних у цій статті.
Це означає, що, коли ми додаємо або прибираємо сервери, Consul автоматично оновлює конфігураційні файли HAProxy. Руками нічого робити не потрібно.
Більше року мирного життя
У нашому підході до виявлення сервісів залишилося ще чимало речей, які ми б хотіли поліпшити. Щоб це зробити, в першу чергу потрібна активна розробка, і ми бачимо можливості витончено вирішити питання інтеграції нашої інфраструктури планування та інфраструктури маршрутизації запитів.
Проте ми прийшли до висновку, що прості рішення часто виявляються ще й самими вірними. Вказана система надійно служить нам вже більше року без будь-яких інцидентів. При тому що Stripe за кількістю оброблюваних запитів дуже далекий від Twitter або Facebook, ми намагаємося добитися максимальної надійності. Іноді буває, що найбільш виграшним виявляється не новаторське, а стабільний і відмінно виконує свої функції рішення.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.