Переезд монолита в k8s. Делаем каршеринг cloud native

Приветствую всех! Меня зовут Максим Шаленко, я старший системный администратор в каршеринге Ситидрайв, и сегодня я хочу поделиться опытом компании по становлению на светлую сторону Силы — переезду в облако.

832ff0903ef2ebf0f21686b426285f7f.jpeg

Kubernetes. Зачем он нужен?

Я пришёл в Ситидрайв 2 года назад и столкнулся с особенностью, которая часто встречается у молодых компаний и стартапов — root-доступ у разработчиков на серверах с приложением. NodeJS-код жил в PM2, разработчик заходил на сервер, делал git pull и запускал новую версию приложения. Следовательно, никакой автоматизации доставки релизов не было, а риск накосячить всегда присутствовал.

Дабы автоматизировать деплой, минимизировать риски и человеческий фактор во время выкаток, я написал gitlab-ci, который запускал Ansible, а тот в свою очередь доставлял код на серверы. В среднем релиз длился 30–40 минут.

5b11d073c31eee3ceec4b6fafa4ed7d7.jpeg

Имея большой опыт работы с k8s и CI/CD процессами, я, конечно же, сказал: «Давайте в кубер. Там будет и ускорение релизов, и бесшовный деплой, и self-healing сервиса, и масштабирование и т.д.». Но не тут-то было. После небольшого ресёрча стало ясно, что сервис не готов к переезду в облачную инфраструктуру.

Из основных особенностей, с которыми предстояло поработать:  

  • сервис сохранял файлы и картинки прямо «под ноги»;  

  • graceful shutdown отсутствовал;  

  • метрики и трассировки отсутствовали, ориентировались только на метрики балансировщиков nginx и на логи;  

  • у логов не было единого формата;  

  • отсутствовали хелсчеки;

  • …и это далеко не конец.

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

Регламент

Мы составили минимальные требования к разработке сервисов:

  1. Stateless

Мы должны были уметь масштабироваться, и один из нюансов, который прям «торчал» — проблема работы с файлами. Как я говорил ранее, сервис попросту сохранял их на файловую систему.

Командами разработки и эксплуатации была проведена большая работа по внедрению файлового сервиса и S3 MinIO, благодаря чему теперь вся статика хранилась в отказоустойчивом хранилище. После этого мы могли смело указать readOnlyRootFilesystem: true в securityContext нашего деплоймента и спать спокойно.

  1. Environment variables

Было очень неудобно управлять конфигурацией приложения через js файлы, поэтому приняли решение стандартизировать настройку через переменные окружения.

  1. Хранилища данных

Любые подключения к хранилищам данных должны быть конфигурируемыми, начиная от банальных username и password и заканчивая конкретными параметрами определённого компонента. Например:

  • PostgreSQL. Если сервис работает с реляционной базой данных, он должен отправлять запросы на запись в мастер, а на чтение из реплик. Исключения могут быть только в случае, если требуются самые актуальные данные, например, если бизнес логика связана с биллингом и оплатами.

  • Redis. Работа с редиской как напрямую, так и через redis sentinels для большей отказоустойчивости сервиса.

  • Kafka. В случае использования кафки, должна быть возможность конфигурировать security_protocol, sasl_mechanism, sasl_username, sasl_password, ssl_ca_pem. Помимо параметров авторизации нужно уметь настраивать топики: их названия, кол-во партиций, replication factor, retention time и т.д.

  1. Health checks

Сервис должен включать в себя хелсчеки для проверки его работоспособности. Это позволит обеспечить так называемый self-healing, благодаря которому приложение станет более доступным несмотря на какие-то баги в коде.

  1. Graceful shutdown

Чтобы приложение корректно перезагружалось или выключалось, оно должно уметь обрабатывать сигнал SIGTERM. Это так называемый graceful shutdown. Это одно из главных требований к микросервису на сегодняшний день, т.к. если сервис выполняет какой-то важный функционал и, как пример, сохраняет картинки в s3, то в таком случае главное, чтобы мы не потеряли изображение во время его загрузки, соответственно, после получения сигнала мы должны загрузить файл до конца, перестать принимать новые соединения, и только после этого завершать процесс.

  1. Метрики

Сервис должен отдавать по запросу GET /metrics метрики в prometheus формате. Они должны включать в себя не только технические, но и критически важные бизнес-метрики, такие как: кол-во машин в аренде; кол-во машин, которые выпали из сети; проблемы с открытием и закрытием дверей и т.д.

  1. Трассировки

Сервис должен уметь слать трассировки в Jaeger. Это дополнительный слой мониторинга, который помогает увидеть, на каких этапах «тормозим», будь то запросы в БД или взаимодействие с какой-то внешней интеграцией. Очень полезный инструмент.

  1. Логи

Как единый формат логов для всех сервисов был выбран json. Бонусом является то, что по полям в json очень удобно строить индексы в системе агрегации и логирования Grafana Loki, которую мы используем в инфраструктуре.

  1. Security

Сервис не должен запускаться под пользователем root, в привилегированном режиме, иметь какие-либо capabilities кроме требуемых и т.д.

  1. Resources

Приложение не может запускаться без resources. Мы должны знать, сколько мы потребляем ресурсов и видеть проблему заранее, если вдруг сервис «течёт». 

Что касается 9 и 10 пункта, валидацию деплойментов выполняет OPA Gatekeeper.

Архитектура и инструментарий

Чтобы доставить сервис в production и заставить эту карусель крутиться, была выстроена определённая архитектура и использованы соответствующие инструменты, которыми вкратце хочу поделиться:

  1. За CI/CD (Continuous Integration and Continuous Delivery/Continuous Deployment) отвечает Gitlab.

  2. Секреты храним в Vault Hashicorp.

  3. Хранилищем артефактов является Nexus.

  4.  Для деплоя используем helm и уникальный chart, написанный специально под нужды компании. Он подходит для доставки всех сервисов в production-окружение.

  5. Мы также имеем опыт canary выкаток и помогает нам в этом Argo Rollouts — простое решение с достаточно гибким инструментарием. Чего только стоит metric providers. Благодаря этому функционалу можно гибко управлять canary релизами на основе prometheus метрик (и не только) приложения.

  6. Как я упоминал ранее, манифесты в k8s валидирует OPA Gatekeeper. Это позволяет предотвращать выкатки, которые не соответствуют регламенту.

  7. За входящий трафик в k8s отвечает nginx ingress controller. Сделали этот выбор, т.к контроллер полностью соответствует всем техническим потребностям компании, а именно: поддержка HTTP/HTTPS, HTTP2, gRPC, TCP, TCP+TLS, Websockets. Из бенефитов также то, что контроллер имеет привычную для нас конфигурацию и большое комьюнити.

  8. Мониторинг:

    • В качестве основной системы мониторинга используем TSDB решение Victoria Metrics.

    • Grafana Loki + Promtail для сбора и отображения логов.

    • Трассировки отправляем в Jaeger.

Хронология релиза

А теперь самое интересное — релиз монолита в k8s. Был анонсирован код-фриз в компании. Переключение происходило в несколько этапов и длилось несколько дней. За это время нам нужно было отловить все те баги, которые по тем или иным причинам не могли быть воспроизведены в dev-окружении.

Главная цель — выкатить сервис-монолит в кубер и перевести на него трафик с минимальными потерями для бизнеса и клиента.

  1. Эпизод I: Новая надежда.

    Для начала мы нарезали несколько worker-нод из резерва в k8s специально для этого сервиса, что позволило нам гарантировать выделенные ресурсы и не аффектить другие сервисы.

    Как я уже упоминал ранее, для приёма и балансировки внешнего трафика мы используем nginx. С помощью директивы split_clients мы могли направлять клиентский трафик в тот или иной upstream.

    Итак, начинаем. 5% трафика в k8s.

  2. Эпизод II: Баги наносят ответный удар.

    Фикс багов будем воспринимать как должное, т.к. без них, увы, не обошлось:

  • Одним из неприятных моментов, который не учли и не протестировали при переезде — функционал генерации промокодов. Как оказалось, промокоды генерировались… угадаете как и куда? Прямиком в CSV файлик «под ноги». Как workaround мы просто смонтировали emptyDir volume в Pod«ы приложения и релиз продолжился. Костыль жил недолго, т.к наша доблестная команда разработки в этот момент параллельно работала над новым сервисом промокодов. Таким образом мы отрезали очередной кусочек от монолита и вывезли функционал в отдельный микросервис.

  • Была кратковременная недоступность авторизации с помощью SberID из-за косяка в коде, который быстро пофиксили, и всё нормализовалось.

  • Задели авторизацию с помощью SMS. Правда, на этот раз из-за того, что не прокинули в нужном месте X-Real-IP header в конфигурации nginx.

Но не будем заострять на этом внимание, т.к статья всё-таки не об ошибках в коде.

Учитывая, что наши железные ресурсы были ограничены на тот момент, мы не могли себе позволить просто так пойти и заказать x8 серверов/виртуальных машин по 32 CPU и 128GB RAM. Поэтому пришлось параллельно высвобождать старые серверы с приложениями и добавлять их как worker-ноды в k8s. Этот процесс повторялся вплоть до конца переезда, пока мы не перевели 100% клиентов.

На следующий день с интервалами в несколько часов мы пустили 10%, 25%, 50%.

  1. Эпизод III: Пробуждение силы.

    Наконец, финальный, третий, день — это 75%, 100% трафика. Аренды в норме, а значит это был успех.

Итоги

Оправдались ли наши ожидания? Несомненно.

  • Прежде всего мы стали на 100% cloud native.

  • Мы задали стандарт, по которому теперь разрабатываются все новые микросервисы в компании.

  • На протяжении 2-х лет монолитный сервис был разбит на десятки микросервисов, и мы продолжаем двигаться в этом направлении.

  • Мы создали уникальный CI/CD инструмент, тем самым ускорив time-to-market в 4 раза (вместо 30–40 минут стало 8–10).

  • Внедрили canary релизы.

  • Безопасно храним секреты.

  • Можем быстро и эффективно создавать новые стенды в dev-окружении.

  • Мы стали попросту стабильнее как сервис, т.к. избавились от тех. долга, а это значит, что конечный пользователь будет доволен стабильностью приложения.

  • За масштабирование теперь отвечает horizontal pod autoscaler, который скейлит кол-во реплик приложения в зависимости от нагрузки на сервис.

  • Обеспечили отказоустойчивость и self-healing сервиса благодаря инструментам k8s.

  • Настроили инструменты мониторинга. Теперь мы о многих проблемах можем знать заранее, а траблшутинг ошибок или багов стал намного легче и прозрачнее — без гаданий на кофейной гуще.

Пожалуй, это всё на сегодня, если говорить вкратце и по сути :)

Я бы хотел сказать спасибо каждому, кто внёс свой вклад в развитие этого проекта! Мы переезжали из одного датацентра в другой, мы шатали прод по ночам, выкатывали новые микросервисы, переписывали старые, за это время наш парк машин вырос в 4 раза. И всё это за экстремальные 2 года! Всем респект и низкий поклон.

cce9b20cfb79ee6a76bf40a1e68a9937.jpeg

Надеюсь, данная статья оказалась для вас полезной, и да пребудет с вами Сила!

© Habrahabr.ru