Хабрахабр

Что мы знаем о микросервисах

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

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

Наш бэкенд принимает больше 20 тыс. Авито — один из крупнейших в мире классифайдов, на нём публикуется больше 15 млн новых объявлений в сутки. Сейчас у нас несколько сотен микросервисов. запросов в секунду.

Как именно — наши коллеги деталях рассказали на нашей секции на РИТ++ 2017. Микросервисную архитектуру мы выстраиваем не первый год. видео), Сергей Орлов и Михаил Прокопчук подробно объяснили, зачем нам вообще понадобился переход к микросервисам и какую роль здесь у нас играл Kubernetes. На CodeFest 2017 (см. Ну а сейчас мы делаем всё, чтобы свести к минимуму те издержки масштабирования, которые такой архитектуре присущи.

Просто собирали толковые опенсорсные решения, запускали их у себя и предлагали разработчику разобраться с ними. Изначально мы не делали экосистему, которая всесторонне помогала бы нам в разработке и запуске микросервисов. Зелёным цветом на схемах ниже обозначено то, что делает разработчик так или иначе своими руками, жёлтым цветом — автоматизация. В итоге тот ходил в десяток мест (дашборды, внутренние сервисы), после чего укреплялся в стремлении пилить код по-старому, в монолите.

Сейчас в 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);
  • мониторинг всего, что с микросервисами происходит.

Пойдём от верхнего к нижнему. Уровни абстракции инфраструктуры включают в себя три слоя.

Верхний — service mesh. Поначалу мы пробовали Istio, но оказалось, что он использует слишком много ресурсов, что на наших объёмах выходит чересчур дорого. A. Средний — Kubernetes. На нём мы развёртываем и эксплуатируем микросервисы.
C. Поэтому старший инженер в команде архитектуры Александр Лукьянченко разработал собственное решение — Netramesh (доступно в Open Source), которое мы сейчас используем в продакшене и которое потребляет в несколько раз меньше ресурсов, чем Istio (но и делает не всё, чем может похвастаться Istio).
B. Нижний — bare metal. Мы не используем облака и штуки типа OpenStack, а сидим целиком на bare metal.

А платформа эта, в свою очередь, состоит из трёх частей. Все слои объединяются PaaS.

Генераторы, управляемые через CLI-утилиту. I. Именно она помогает разработчику создать микросервис по-правильному и с минимумом усилий.

Сводный коллектор с контролем всех инструментов через общий дашборд. II.

Хранилище. III. Благодаря такой системе ни одна задача не оказывается упущенной только из-за того, что кто-то забыл поставить себе таск в Jira. Стыкуется с планировщиками, которые автоматически выставляют триггеры на значимые действия. Мы для этого используем внутренний инструмент под названием Atlas.

Реализация микросервисов в Авито также ведётся по единой схеме, что упрощает контроль над ними на каждой стадии разработки и выпуска.

В общем виде цепочка создания микросервиса выглядит следующим образом:

CLI-push → Continuous Integration → Bake → Деплой → Искусственные тесты → Canary-тесты → Squeeze Testing → Продакшен → Обслуживание.

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

CLI-push

В том числе писали в Confluence подробные инструкции. • Создание микросервиса.
Мы долго бились над тем, чтобы научить каждого разработчика делать микросервисы. Итог — бутылочное горлышко образовалось в начале пути: на запуск микросервисов уходило времени куда больше допустимого, и всё равно при их создании часто возникали проблемы. Но схемы менялись и дополнялись.

Фактически она заменяет первый git push. В конце концов мы соорудили простую CLI-утилиту, которая автоматизирует основные шаги при создании микросервиса. Вот что конкретно она делает.

У нас есть шаблоны для основных языков программирования в бэкенде Авито: PHP, Golang и Python. — Создаёт сервис по шаблону — пошагово, в режиме «визарда».

— По одной команде разворачивает среду для локальной разработки на конкретной машине — поднимается Minikube, Helm-чарты автоматически генерируются и запускаются в локальном kubernetes’е.

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

Допустим, разработчик поправил что-то в микросервисе через свою IDE. — Сама выполняет live-сборку. Для PHP мы просто пробрасываем директорию внутрь куба и там live-reload получается «автоматом». Утилита видит изменения в файловой системе и исходя из них пересобирает приложение (для Golang) и перезапускает.

В виде болванок, но вполне пригодных к использованию. — Генерирует автотесты.

• Деплой микросервиса.

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

Dockerfile. I.

Конфиг.
III. II. Helm-чарт, который сам по себе громоздкий и включает в себя:

— сами чарты;
— шаблоны;
— конкретные значения с учётом разных сред.

Но главное, упростили до предела деплой. Мы избавились от боли с переделыванием манифестов Kubernetes, и теперь они генерируются автоматически. Отныне у нас есть Dockerfile, а весь конфиг разработчик прописывает в одном-единственном коротком файле app.toml.

Прописываем, где сколько копий сервиса поднимать (на dev-сервере, на staging, на продакшене), указываем его зависимости. Да и в самом app.toml сейчас дел на минуту. Это лимит, который будет выделен сервису через Kubernetes. Обратите внимание на строку size = "small" в блоке [engine].

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

• Базовая валидация. Такие проверки тоже автоматизированы.
Нужно отслеживать:
— есть ли Dockerfile;
— есть ли app.toml;
— имеется ли документация;
— в порядке ли зависимости;
— заданы ли правила алертов.
К последнему пункту: владелец сервиса сам указывает, какие продуктовые метрики мониторить.

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

Краткое описание сервиса. I. Буквально несколько предложений о том, что он делает и для чего нужен.

Ссылка на диаграмму архитектуры. II. В Авито пока что это ссылка на Confluence. Важно, чтобы при беглом взгляде на неё легко было понять, например, используете вы Redis для кэширования или как основное хранилище данных в персистентном режиме.

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

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

Описание endpoints для API. V. Сейчас у нас для этого используется Swagger и наше решение под названием brief. Если вдруг вы не указали точки назначения, расплачиваться за это почти наверняка будут коллеги, чьи микросервисы связаны с вашим.

Labels. VI. Помогают быстро понять, например, не пилите ли вы функциональность, которую неделю назад выкатили для того же бизнес-юнита ваши коллеги. Или маркеры, которые показывают, к какому продукту, функциональности, структурному подразделению компании относится сервис.

Владелец или владельцы сервиса. VII. В большинстве случаев его — или их — с помощью PaaS получается определить автоматически, но для страховки мы требуем от разработчика указывать их и вручную.

Наконец, хорошая практика — проводить ревью документации, по аналогии с code review.

Continuous Integration

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

Bake

Следующая стадия — упаковка сервисов перед деплоем.

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

Максимум: 643ms, минимум: 42ms. Время исполнения, меньше — лучше. Фото кликабельно.

Максимум: 14091 ns, минимум: 151 ns. Время на операцию, меньше — лучше. Фото кликабельно.

На этапе подготовки сборки можно выставлять эту переменную явно или можно пользоваться библиотекой automaxprocs от ребят из Uber.

Деплой

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

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

Синтетические тесты

Сначала он записывает реальную нагрузку на сервис, затем — как раз в закрытом контуре — её эмулирует. • Тестирование в закрытом контуре. Для него мы сейчас используем опенсорсный Hoverfly.io.

И все версии каждого сервиса должны подвергаться нагрузочному тестированию — так мы можем понять текущую производительность сервиса и разницу с предыдущими версиями этого же сервиса. • Нагрузочное тестирование. Все сервисы мы стараемся привести к оптимальной производительности. Если после апдейта сервиса его performance упал в полтора раза, это чёткий сигнал для его владельцев: нужно закопаться в код и исправить ситуацию.
От собранных данных мы отталкиваемся, например, чтобы правильно реализовать auto scaling и, в конце концов, вообще понять, насколько сервис поддаётся масштабированию.

И заостряем внимание прежде всего на экстремумах. При нагрузочном тестировании мы проверяем, отвечает ли потребление ресурсов выставленным ограничениям.

a) Смотрим на общую нагрузку.
— Слишком мала — скорее всего что-то вообще не работает, если нагрузка вдруг упала в несколько раз.
— Слишком велика — требуется оптимизация.

Например, если сервис выдает 100 rps — то он либо плохо написан, либо это его специфика, но в любом случае это повод очень пристально посмотреть на сервис.
Если же RPS наоборот слишком много, то, возможно, какой-то баг и какой-то из endpoint-ов перестал выполнять полезную нагрузку, а просто срабатывает какой-нибудь return true; b) Смотрим на отсечку по RPS.
Тут смотрим и разницу текущей версии и предыдущей и общее количество.

Canary-тесты

Начинаем осторожно, с мизерной доли предполагаемой аудитории сервиса — меньше 0,1%. После того как пройдены синтетические тесты, мы обкатываем работу микросервиса на малом количестве пользователей. Минимальное время canary-теста — 5 минут, основное — 2 часа. На этом этапе очень важно, чтобы в мониторинге были заведены правильные технические и продуктовые метрики, чтобы они максимально быстро показали проблему в сервисе. Для сложных сервисов выставляем время в ручном режиме.
Анализируем:
— метрики, специфические для языка, в частности, воркеры php-fpm;
— ошибки в Sentry;
— статусы ответов;
— время ответов (response time), точное и среднее;
— latency;
— исключения, обработанные и необработанные;
— продуктовые метрики.

Squeeze Testing

Название методики ввели в Netflix. Squeeze Testing ещё называют тестированием через «выдавливание». Дальше добавляем ещё один инстанс и нагружаем эту парочку — снова до максимума; мы видим их потолок и дельту с первым «сквизом». Суть её в том, что сначала мы заполняем реальным трафиком один инстанс до состояния отказа и таким образом устанавливаем его предел. И так подключаем по одному инстансу за шаг и высчитываем закономерность в изменениях.
Данные по тестам через «выдавливание» тоже стекаются в общую базу метрик, где мы либо обогащаем ими результаты искусственной нагрузки, либо вообще заменяем ими «синтетику».

Продакшен

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

В итоге при масштабировании анализируем:
— показатели CPU и RAM,
— количество запросов в очереди,
— время ответа,
— прогноз на основании накопленных исторических данных.

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

Обслуживание

После того как микросервис введён в строй, мы можем навешивать на него триггеры.

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

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

Если совсем коротко, дашборд — контрольный пульт всего нашего PaaS.

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




д. До внедрения PaaS новый разработчик мог потратить несколько недель на то, чтобы разобраться во всех инструментах, необходимых для запуска микросервиса в продакшен: Kubernetes, Helm, — в наших внутренних особенностях TeamCity, настройке подключения к базам и кэшам в отказоустойчивом виде и т. Сейчас на это уходит пара часов — прочесть quickstart и сделать сам сервис.

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

Хотим подарить возможность участия в нём кому-то из читателей этого поста. Мы в Авито организуем внутренний трёхдневный тренинг для разработчиков от Криса Ричардсона, эксперта по микросервисной архитектуре. Здесь выложена программа тренинга.

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

От вас — ответ на вопрос, почему именно вам нужно посетить тренинг и информация, как с вами связаться. Подать заявку на участие можно в этой гугл-форме. Отвечайте на английском, потому что участника, который попадёт на тренинг, Крис будет выбирать сам.
Мы объявим имя участника тренинга апдейтом к этому посту и в социальных сетях Авито для разработчиков (AvitoTech в Фейсбуке, Вконтакте, Твиттере) не позднее 19 июля.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть