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

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

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

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

Имея большой опыт работы с 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 года! Всем респект и низкий поклон.

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

Комментарии (12)


  1. Heda
    24.10.2023 10:11
    +2

    Куча работы еще впереди, но шаг в нужную сторону. Раз сервис так и остался частично в виде монолита, хотелось бы узнать, какие именно метрики его частей отвечают за readness/liveness. Что делать, если 1 за 10 метрик начинает моросить, хотя остальные части монолита в норме?

    И еще очень болезненный вопрос для меня - секреты в волте, а как вы их безопасно вытягиваете? Те же логопассы к базам как в приклад подкладываются?


    1. mshalenko Автор
      24.10.2023 10:11
      +2

      Приветствую!

      Т.к livenessProbe отвечает за рестарт Pod'ов, этот хелсчек дёргается наиболее часто. Логика его работы на данный момент проста - мы проверям работоспособность веб-сервера в целом и отвечаем 200.

      readinessProbe - с этим хелсчеком ситуация обстоит хитрее. Результат этой пробы говорит о том, готов или не готов Pod принимать трафик. Когда мы дёргаем его, помимо работы веб-сервера, мы проверяем критически важные инфраструктурные компоненты на доступность включая базы, шины данных и т.д.

      Что касается самих метрик, то тут тоже всё просто. У нас заведены алерты на все критически важные для инфраструктуры и бизнеса метрики. Если что-то идёт не так, Alertmanager даст нам знать.

      Секреты из Vault на данный момент доставляются в k8s с помощью gitlab-ci, но в будущем есть планы усовершенствовать этот способ, добавив монтирование секретов при инициализации контейнера. Таким образом из gitlab-ci этот этап вообще можно будет убрать.


    1. Sigest
      24.10.2023 10:11

      Для секретов из vault - есть довольно удобный оператор vault secrets operator от самого hashicorp. Стабильная штука. Создает CRD и через эти CRD настраивается связь между развернутым приложением и Vault. Не супер секьюрно, так как secrets создаются в кубере как они есть, как если бы вы их деплоили через kubectl apply, и это на тот случай, если секреты не хочется держать в репозитории. Но и не так геморно, если вдруг надо подружить например ArgoCD с Vault через его систему плагинов.


  1. lazy_val
    24.10.2023 10:11
    +1

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

    Было бы интересно услышать - почему не Prometheus?


    1. mshalenko Автор
      24.10.2023 10:11
      +4

      Здравствуй! На предыдущем месте работы мы с коллегами администрировали сотни кластеров Openshift. По дефолту Openshift поставляется с Prometheus. Т.к кластеры были высоконагруженные, метрик было много, Prometheus потреблял очень много ресурсов CPU, RAM, IO. В качестве эксперимента мы подняли на серверах с такими же ресурсами Victoria Metrics и сравнили их работу. Разница в потреблении была в 2-3 раза. Поэтому без лишних раздумий, в Ситидрайве выбор пал на Victoria Metrics. Помимо этого компоненты Виктории легко масштабируются.


  1. puzo
    24.10.2023 10:11

    этого признания я давно ждал "прод шатал" - где мои 800 р? когда я с не смог сдать машину?
    А если серьезно, то результат супер! Сам в таком сейчас варюсь.
    но про 800р осадок остался....... ((( ;)


    1. Chernyshevskaia
      24.10.2023 10:11

      Привет! Напиши в личные сообщения, пожалуйста, ФИО и номер телефона из приложения, а также номер авто или дату аренды, когда был сбой. Передам ответственным — всё проверим ????


  1. GoooodBoy
    24.10.2023 10:11

    Вы пишите, что разработали "уникальный chart". А в чем его уникальность?


    1. mshalenko Автор
      24.10.2023 10:11

      Все просто. Он написан таким образом, что подходит для всех сервисов в компании. У нас нет такого, что под каждый сервис пишется отдельный хелм чарт. Правки если и вносятся, то только в одном месте - в unique-helm-chart. Так мы его называем :)


      1. GoooodBoy
        24.10.2023 10:11
        +2

        Тогда я бы назвал "универсальный", а то уникальный вводит в некоторое заблуждение.


  1. 1q2w1q2w
    24.10.2023 10:11
    +1

    вопрос про монолит в облаке, понятно что все преимущества переезда ощущаются после распила монолита, когда можно оперировать маленькими автономными кусками приложения. Но в статье говорится что мигрировали именно монолит. Так а в чем собственно преимущества монолита в облаке если деплой по прежнему занимает много времени, скейлинг работает но требует количество ресурсов пропорциональное количеству реплик, стоимость тоже достаточно высокая, т.к. у всех провайдеров стоимость "больших" инстансов высокая (а в "маленькие" он не поместится). Если не секрет, какой примерно был размер артефактов\потребление ресурсов в кубере сразу после переезда?


  1. erley
    24.10.2023 10:11

    Полезный real-life опыт, интересно почитать. И привязки к конкретным cloud-providers нет, молодцы!