Що ми знаємо про мікросервіси

Вітання! Мене звуть Вадим Мадісон, я керую розробкою System Platform Авіто. Про те, як ми в компанії переходимо з монолітної архітектури на мікросервіс, було сказано не раз. Настав час поділитися тим, як ми перетворили свою інфраструктуру, щоб витягти з мікросервісів максимум користі і не дати собі в них загубитися. Як нам тут допомагає PaaS, як ми спростили деплою і звели створення мікросервісу до одного кліку читайте далі. Не все, про що я пишу нижче, в Авіто реалізовано повною мірою частина — те, як ми розвиваємо нашу платформу.

(А ще наприкінці цієї статті я розповім про можливість потрапити на триденний семінар від експерта з мікросервісної архітектури Кріса Річардсона).

Що ми знаємо про мікросервіси

Як ми прийшли до мікросервісів

Авіто — один із найбільших у світі класифайдів, на ньому публікується понад 15 млн нових оголошень на добу. Наш бекенд приймає понад 20 тис. запитів на секунду. Нині у нас кілька сотень мікросервісів.

Мікросервісну архітектуру ми вибудовуємо не перший рік. Як саме наші колеги деталях розповіли на нашій секції на РІТ++ 2017. На CodeFest 2017 (див. відео), Сергій Орлов та Михайло Прокопчук докладно пояснили, навіщо нам взагалі знадобився перехід до мікросервісів та яку роль тут у нас грав Kubernetes. Ну а зараз ми робимо все, щоб звести до мінімуму ті масштабування, які такій архітектурі притаманні.

Спочатку ми не робили екосистему, яка всебічно допомагала б нам у розробці та запуску мікросервісів. Просто збирали тямущі опенсорсні рішення, запускали їх у себе і пропонували розробнику розібратися з ними. У результаті той ходив у десяток місць (дашборди, внутрішні послуги), після чого зміцнювався в прагненні пиляти код по-старому, в моноліті. Зеленим кольором на схемах нижче позначено те, що робить розробник однак своїми руками, жовтим кольором — автоматизація.

Що ми знаємо про мікросервіси

Зараз у CLI-утиліті PaaS однією командою створюється новий сервіс, ще двома додається нова база даних і деплоїться в Stage.

Що ми знаємо про мікросервіси

Як подолати епоху «мікросервісної роздробленості»

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

Крім того, щоб мікросервісна архітектура була ефективною, потрібно налагодити безліч процесів, а саме:

• логування;
• трасування запитів (Jaeger);
• агрегація помилок (Sentry);
• статуси, повідомлення, події з Kubernetes (Event Stream Processing);
• race limit / circuit breaker (можна використовувати Hystrix);
• контроль зв'язку сервісів (ми використовуємо Netramesh);
• моніторинг (Grafana);
• складання (TeamCity);
• спілкування та нотифікація (Slack, email);
• трекінг завдань; (Jira)
• складання документації.

Щоб у міру масштабування система не втратила цілісність і залишалася ефективною, ми переосмислили організацію роботи мікросервісів Авіто.

Як ми управляємось з мікросервісами

Проводити єдину «політику партії» серед багатьох мікросервісів Авіто допомагають:

  • поділ інфраструктури на верстви;
  • концепція Platform as a Service (PaaS);
  • моніторинг всього, що із мікросервісами відбувається.

Рівні абстракції інфраструктури включають три шари. Ходімо від верхнього до нижнього.

A. Верхній - Service Mesh. Спочатку ми пробували Istio, але виявилося, що він використовує дуже багато ресурсів, що на наших обсягах виходить дуже дорого. Тому старший інженер у команді архітектури Олександр Лук'янченко розробив власне рішення. Netramesh (Доступно в Open Source), яке ми зараз використовуємо в продакшені і яке споживає в кілька разів менше ресурсів, ніж Istio (але і робить не все, чим може похвалитися Istio).
B. Середній - Kubernetes. На ньому ми розгортаємо та експлуатуємо мікросервіси.
C. Нижній – bare metal. Ми не використовуємо хмари та штуки типу OpenStack, а сидимо на bare metal.

Усі шари поєднуються PaaS. А ця платформа, у свою чергу, складається з трьох частин.

I. Генераторикеровані через CLI-утиліту. Саме вона допомагає розробнику створити мікросервіс по-правильному та з мінімумом зусиль.

ІІ. Зведений колектор із контролем усіх інструментів через загальний дашборд.

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

Що ми знаємо про мікросервіси

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

Як влаштований стандартний конвеєр розробки мікросервісу

У загальному вигляді ланцюжок створення мікросервісу виглядає так:

CLI-push → Continuous Integration → Bake → Деплой → Штучні тести → Canary-тести → Squeeze Testing → Продакшен → Обслуговування.

Пройдемося по ній рівно в такій послідовності.

CLI-push

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

Зрештою, ми спорудили просту CLI-утиліту, яка автоматизує основні кроки при створенні мікросервісу. Фактично вона замінює перший git push. Ось що саме вона робить.

- Створює сервіс за шаблоном - покроково, в режимі візарду. У нас є шаблони для основних мов програмування в бекенді Авіто: PHP, Golang та Python.

- По одній команді розгортає середовище для локальної розробки на конкретній машині - піднімається Minikube, Helm-чарти автоматично генеруються і запускаються в локальному kubernetes'і.

- Підключає потрібну базу даних. Розробнику не потрібно знати IP, логін і пароль, щоб отримати доступ до потрібної йому БД — хоч локально, хоч у Stage, хоч на продакшені. Причому розгортається база даних одночасно у відмовостійкій конфігурації та з балансуванням.

— Сама виконує live-складання. Припустимо, розробник поправив щось у мікросервіс через свою IDE. Утиліта бачить зміни у файловій системі і виходячи з них перезбирає програму (для Golang) і перезапускає. Для PHP ми просто прокидаємо директорію всередину куба і там live-reload виходить "автоматом".

- Генерує автотести. У вигляді болванок, але цілком придатних для використання.

• Деплой мікросервісу.

Розгортати мікросервіс у нас раніше було трохи нудно. В обов'язковому порядку були потрібні:

I. Dockerfile.

ІІ. Конфіг.
ІІІ. Helm-чарт, який сам по собі громіздкий і включає:

- Самі чарти;
- Шаблони;
- Конкретні значення з урахуванням різних середовищ.

Ми позбулися болю з переробкою маніфестів Kubernetes і тепер вони генеруються автоматично. Але головне, спростили до краю деплою. Відтепер у нас є Dockerfile, а весь конфіг розробник прописує в одному короткому файлі app.toml.

Що ми знаємо про мікросервіси

Та й у самому app.toml зараз справ на хвилину. Прописуємо де скільки копій сервісу піднімати (на dev-сервері, на staging, на продакшені), вказуємо його залежності. Зверніть увагу на рядок size = "small" у блоці [engine]. Це ліміт, який буде виділено сервісу через Kubernetes.

Далі на базі конфігу автоматично генеруються всі необхідні Helm-чарти та створюються підключення до баз даних.

• Базова валідація. Такі перевірки також автоматизовані.
Потрібно відстежувати:
- Чи є Dockerfile;
- Чи є app.toml;
- Чи є документація;
- Чи в порядку залежності;
- Чи задані правила алертів.
До останнього пункту: власник сервісу сам показує, які продуктові метрики моніторити.

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

I. Короткий опис сервісу. Буквально кілька пропозицій про те, що він робить і для чого потрібний.

ІІ. Посилання на діаграму архітектури. Важливо, щоб при побіжному погляді на неї легко було зрозуміти, наприклад, використовуєте ви Redis для кешування або як основне сховище даних у персистентному режимі. В Авіто поки що це посилання на Confluence.

ІІІ. Runbook. Короткий гайд із запуску сервісу та тонкощів поводження з ним.

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

V. Опис endpoints для API. Якщо ви не вказали точки призначення, розплачуватися за це майже напевно будуть колеги, чиї мікросервіси пов'язані з вашим. Зараз у нас для цього використовується Swagger та наше рішення під назвою brief.

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

VII. Власник чи власники сервісу. У більшості випадків його – або їх – за допомогою PaaS виходить визначити автоматично, але для страховки ми вимагаємо від розробника вказувати їх і вручну.

Зрештою, хороша практика — проводити ревію документації, за аналогією з code review.

Безперервна інтеграція

  • Підготовка репозиторіїв.
  • Створення пайплайну в TeamCity.
  • Виставлення прав.
  • Пошук власників сервісу. Тут гібридна схема – ручне маркування та мінімальна автоматика від PaaS. Повністю автоматична схема дає збої під час передачі сервісів на підтримку іншу команду розробки чи, наприклад, якщо розробник сервісу звільнився.
  • Реєстрація сервісу в Atlas (див. вище). З усіма його власниками та залежностями.
  • Перевірка міграцій. Перевіряємо, чи немає серед них потенційно небезпечних. Наприклад, в одній з них спливає alter table або щось здатне порушити сумісність схеми даних між різними версіями сервісу. Тоді міграція не виконується, а ставиться у підписку — PaaS має просигналити власнику сервісу, коли стане безпечно її застосувати.

Випікати

Наступна стадія – упаковка сервісів перед деплом.

  • Складання програми. За класикою – у Docker-образ.
  • Генерація Helm-Чартів для самого сервісу та пов'язаних з ним ресурсів. У тому числі для баз даних та кешу. Створюються вони автоматично відповідно до того конфіга app.toml, який був сформований на стадії CLI-push.
  • Створення тикетів адмінам на відкриття портів (Коли це потрібно).
  • Прогін юніт-тестів та підрахунок code coverage. Якщо покриття коду нижче за задане порогове значення, то, швидше за все, далі — на деплой — сервіс не пройде. Якщо воно на межі допустимого, то сервісу буде присвоєно «песимізуючий» коефіцієнт: тоді за відсутності поліпшень показника з часом розробник отримає повідомлення про те, що прогресу в частині тестів немає (і треба щось з цим зробити).
  • Облік обмежень з пам'яті та CPU. Здебільшого мікросервіси ми пишемо на Golang і запускаємо їх у Kubernetes. Звідси одна тонкість, пов'язана з особливістю мови Golang: за замовчуванням запускаються всі ядра на машині, якщо в явному вигляді не виставити змінну GOMAXPROCS і коли на одній машині запускається кілька таких сервісів, то вони починають конкурувати за ресурси, заважаючи один одному. На графіках нижче показано, як змінюється час виконання, якщо запустити програму без конкуренції та в режимі гонки за ресурси. (Вихідники графіків лежать тут).

Що ми знаємо про мікросервіси

Час виконання, менше – краще. Максимум: 643ms, мінімум: 42ms. Фото клікабельно.

Що ми знаємо про мікросервіси

Час на операцію, менше – краще. Максимум: 14091 151 ns, мінімум: XNUMX ns. Фото клікабельно.

На етапі підготовки складання можна виставляти цю змінну явно або можна користуватися бібліотекою automaxprocs від хлопців із Uber.

Деплой

• Перевірка конвенцій. Перед тим, як почати доставляти складання сервісу в намічені середовища, потрібно перевірити наступне:
- API endpoints.
— Відповідність відповідей API endpoints до схеми.
- Формат логів.
- Виставлення заголовків при запитах до сервісу (зараз це робить netramesh)
— Виставлення маркера власника під час відправлення повідомлень у шину (event bus). Це необхідно для відстеження зв'язків сервісів через шину. У шину можна відправляти як ідемпотентні дані, що не підвищують зв'язність сервісів (що добре), так і бізнес-дані, які посилюють зв'язність сервісів (що дуже погано!). І в той момент, коли цей зв'язок стає проблемою, розуміння, хто пише і читає шину, допомагає правильно розділити сервіси.

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

Синтетичні тести

• Тестування у закритому контурі. Для нього ми зараз використовуємо опенсорний Hoverfly.io. Спочатку він записує реальне навантаження на сервіс, потім - саме в закритому контурі - його емулює.

• Тестування навантаження. Усі сервіси ми намагаємось привести до оптимальної продуктивності. І всі версії кожного сервісу повинні піддаватися тестуванню навантаження — так ми можемо зрозуміти поточну продуктивність сервісу і різницю з попередніми версіями цього ж сервісу. Якщо після апдейту сервісу його performance впав у півтора рази, це чіткий сигнал для його власників: потрібно закопатись у код та виправити ситуацію.
Від зібраних даних ми відштовхуємось, наприклад, щоб правильно реалізувати auto scaling і, зрештою, взагалі зрозуміти, наскільки сервіс піддається масштабуванню.

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

a) Дивимося на загальне навантаження.
— Занадто мала — швидше за все, щось взагалі не працює, якщо навантаження раптом впало в кілька разів.
— Занадто велика — потрібна оптимізація.

b) Дивимося на відсічення за RPS.
Тут дивимося і різницю поточної версії та попередньої та загальної кількості. Наприклад, якщо сервіс видає 100 rps - то він або погано написаний, або це його специфіка, але в будь-якому випадку це привід дуже уважно подивитися на сервіс.
Якщо ж RPS навпаки занадто багато, то, можливо, якийсь баг і якийсь із endpoint-ів перестав виконувати корисне навантаження, а просто спрацьовує якийсь return true;

Canary-тести

Після того, як пройдено синтетичні тести, ми обкатуємо роботу мікросервісу на малій кількості користувачів. Починаємо обережно, з мізерної частки передбачуваної аудиторії сервісу менше 0,1%. На цьому етапі дуже важливо, щоб у моніторингу були заведені правильні технічні та продуктові метрики, щоб максимально швидко показали проблему в сервісі. Мінімальний час canary-тесту - 5 хвилин, основний - 2 години. Для складних сервісів виставляємо час у ручному режимі.
Аналізуємо:
- метрики, специфічні для мови, зокрема воркери php-fpm;
- Помилки в Sentry;
- Статуси відповідей;
- Час відповідей (response time), точне та середнє;
- Latency;
- Винятки, оброблені та необроблені;
- продуктові метрики.

Squeeze Testing

Squeeze Testing ще називають тестуванням через видавлювання. Назву методики ввели в Netflix. Суть її в тому, що спочатку ми заповнюємо реальним трафіком один інстанс до стану відмови і встановлюємо його межу. Далі додаємо ще один інстанс і навантажуємо цю парочку знову до максимуму; ми бачимо їхню стелю та дельту з першим «сквізом». І так підключаємо по одному інстансу за крок та вираховуємо закономірність у змінах.
Дані за тестами через «видавлювання» теж стікаються у загальну базу метрик, де ми або збагачуємо ними результати штучного навантаження, або замінюємо ними «синтетику».

Продакшен

• Масштабування. Викочуючи сервіс на продакшен, ми відстежуємо, як він масштабується. Моніторити при цьому лише показники CPU, на наш досвід, неефективно. Auto scaling з бенчмаркінгом RPS у чистому вигляді працює, але лише для окремих сервісів, наприклад, онлайн-стримінгу. Тож ми дивимося насамперед на специфічні для застосування продуктові метрики.

У результаті під час масштабування аналізуємо:
- Показники CPU і RAM,
— кількість запитів у черзі,
- Час відповіді,
прогноз на підставі накопичених історичних даних.

При масштабуванні сервісу також важливо відстежувати його залежності, щоб не вийшло так, що ми перший сервіс у ланцюжку скейлім, а ті, до яких він звертається, падають під навантаженням. Щоб встановити прийнятне для всього пулу сервісів навантаження, ми дивимося на історичні дані «найближчого» залежного сервісу (за комбінацією показника CPU і RAM разом з app-specific metrics) і зіставляємо їх з історичними даними сервісу, що ініціалізує, і так далі по всій «ланцюжку» », зверху до низу.

Обслуговування

Після того, як мікросервіс введений в дію, ми можемо навішувати на нього тригери.

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

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

Дашборд

Якщо дуже коротко, дашборд – контрольний пульт всього нашого PaaS.

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

Що ми знаємо про мікросервіси
Що ми знаємо про мікросервіси
Що ми знаємо про мікросервіси
Що ми знаємо про мікросервіси

Разом

До впровадження PaaS новий розробник міг витратити кілька тижнів на те, щоб розібратися у всіх інструментах, необхідних для запуску мікросервісу в продакшен: Kubernetes, Helm, - у наших внутрішніх особливостях TeamCity, налаштування підключення до баз і кешів у відмовостійкому вигляді і т.д. Зараз на це йде пара годинника - прочитати quickstart і зробити сам сервіс.

Я робив доповідь на цю тему для HighLoad 2018, можна подивитися відео и презентацію.

Бонус-трек для тих, хто дочитав до кінця

Ми в Авіто організуємо внутрішній триденний тренінг для розробників від Кріса Річардсона, експерт з мікросервісної архітектури. Хочемо подарувати можливість участі у ньому комусь із читачів цієї посади. Тут викладено програму тренінгу.

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

Подати заявку на участь можна у цій гугл-формі. Від вас – відповідь на запитання, чому саме вам потрібно відвідати тренінг та інформація, як з вами зв'язатися. Відповідайте англійською, тому що учасника, який потрапить на тренінг, Кріс обиратиме сам.
Ми оголосимо ім'я учасника тренінгу апдейтом до цієї посади та в соціальних мережах Авіто для розробників (AvitoTech в Фейсбуці, Вконтакте, Твіттері) не пізніше 19 липня.

Джерело: habr.com

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