Приветствую всех! Меня зовут Максим Шаленко, я старший системный администратор в каршеринге Ситидрайв, и сегодня я хочу поделиться опытом компании по становлению на светлую сторону Силы — переезду в облако.
Kubernetes. Зачем он нужен?
Я пришёл в Ситидрайв 2 года назад и столкнулся с особенностью, которая часто встречается у молодых компаний и стартапов — root-доступ у разработчиков на серверах с приложением. NodeJS-код жил в PM2, разработчик заходил на сервер, делал git pull и запускал новую версию приложения. Следовательно, никакой автоматизации доставки релизов не было, а риск накосячить всегда присутствовал.
Дабы автоматизировать деплой, минимизировать риски и человеческий фактор во время выкаток, я написал gitlab-ci, который запускал Ansible, а тот в свою очередь доставлял код на серверы. В среднем релиз длился 30-40 минут.
Имея большой опыт работы с k8s и CI/CD процессами, я, конечно же, сказал: «Давайте в кубер. Там будет и ускорение релизов, и бесшовный деплой, и self-healing сервиса, и масштабирование и т.д.». Но не тут-то было. После небольшого ресёрча стало ясно, что сервис не готов к переезду в облачную инфраструктуру.
Из основных особенностей, с которыми предстояло поработать:
сервис сохранял файлы и картинки прямо «под ноги»;
graceful shutdown отсутствовал;
метрики и трассировки отсутствовали, ориентировались только на метрики балансировщиков nginx и на логи;
у логов не было единого формата;
отсутствовали хелсчеки;
…и это далеко не конец.
Поэтому первое, что нужно было сделать — составить регламент, задать стандарт, которому должен соответствовать сервис.
Регламент
Мы составили минимальные требования к разработке сервисов:
Stateless
Мы должны были уметь масштабироваться, и один из нюансов, который прям «торчал» — проблема работы с файлами. Как я говорил ранее, сервис попросту сохранял их на файловую систему.
Командами разработки и эксплуатации была проведена большая работа по внедрению файлового сервиса и S3 MinIO, благодаря чему теперь вся статика хранилась в отказоустойчивом хранилище. После этого мы могли смело указать readOnlyRootFilesystem: true в securityContext нашего деплоймента и спать спокойно.
Environment variables
Было очень неудобно управлять конфигурацией приложения через js файлы, поэтому приняли решение стандартизировать настройку через переменные окружения.
Хранилища данных
Любые подключения к хранилищам данных должны быть конфигурируемыми, начиная от банальных username и password и заканчивая конкретными параметрами определённого компонента. Например:
PostgreSQL. Если сервис работает с реляционной базой данных, он должен отправлять запросы на запись в мастер, а на чтение из реплик. Исключения могут быть только в случае, если требуются самые актуальные данные, например, если бизнес логика связана с биллингом и оплатами.
Redis. Работа с редиской как напрямую, так и через redis sentinels для большей отказоустойчивости сервиса.
Kafka. В случае использования кафки, должна быть возможность конфигурировать security_protocol, sasl_mechanism, sasl_username, sasl_password, ssl_ca_pem. Помимо параметров авторизации нужно уметь настраивать топики: их названия, кол-во партиций, replication factor, retention time и т.д.
Health checks
Сервис должен включать в себя хелсчеки для проверки его работоспособности. Это позволит обеспечить так называемый self-healing, благодаря которому приложение станет более доступным несмотря на какие-то баги в коде.
Graceful shutdown
Чтобы приложение корректно перезагружалось или выключалось, оно должно уметь обрабатывать сигнал SIGTERM. Это так называемый graceful shutdown. Это одно из главных требований к микросервису на сегодняшний день, т.к. если сервис выполняет какой-то важный функционал и, как пример, сохраняет картинки в s3, то в таком случае главное, чтобы мы не потеряли изображение во время его загрузки, соответственно, после получения сигнала мы должны загрузить файл до конца, перестать принимать новые соединения, и только после этого завершать процесс.
Метрики
Сервис должен отдавать по запросу GET /metrics метрики в prometheus формате. Они должны включать в себя не только технические, но и критически важные бизнес-метрики, такие как: кол-во машин в аренде; кол-во машин, которые выпали из сети; проблемы с открытием и закрытием дверей и т.д.
Трассировки
Сервис должен уметь слать трассировки в Jaeger. Это дополнительный слой мониторинга, который помогает увидеть, на каких этапах «тормозим», будь то запросы в БД или взаимодействие с какой-то внешней интеграцией. Очень полезный инструмент.
Логи
Как единый формат логов для всех сервисов был выбран json. Бонусом является то, что по полям в json очень удобно строить индексы в системе агрегации и логирования Grafana Loki, которую мы используем в инфраструктуре.
Security
Сервис не должен запускаться под пользователем root, в привилегированном режиме, иметь какие-либо capabilities кроме требуемых и т.д.
Resources
Приложение не может запускаться без resources. Мы должны знать, сколько мы потребляем ресурсов и видеть проблему заранее, если вдруг сервис «течёт».
Что касается 9 и 10 пункта, валидацию деплойментов выполняет OPA Gatekeeper.
Архитектура и инструментарий
Чтобы доставить сервис в production и заставить эту карусель крутиться, была выстроена определённая архитектура и использованы соответствующие инструменты, которыми вкратце хочу поделиться:
За CI/CD (Continuous Integration and Continuous Delivery/Continuous Deployment) отвечает Gitlab.
Секреты храним в Vault Hashicorp.
Хранилищем артефактов является Nexus.
Для деплоя используем helm и универсальный chart, написанный специально под нужды компании. Он подходит для доставки всех сервисов в production-окружение.
Мы также имеем опыт canary выкаток и помогает нам в этом Argo Rollouts — простое решение с достаточно гибким инструментарием. Чего только стоит metric providers. Благодаря этому функционалу можно гибко управлять canary релизами на основе prometheus метрик (и не только) приложения.
Как я упоминал ранее, манифесты в k8s валидирует OPA Gatekeeper. Это позволяет предотвращать выкатки, которые не соответствуют регламенту.
За входящий трафик в k8s отвечает nginx ingress controller. Сделали этот выбор, т.к контроллер полностью соответствует всем техническим потребностям компании, а именно: поддержка HTTP/HTTPS, HTTP2, gRPC, TCP, TCP+TLS, Websockets. Из бенефитов также то, что контроллер имеет привычную для нас конфигурацию и большое комьюнити.
-
Мониторинг:
В качестве основной системы мониторинга используем TSDB решение Victoria Metrics.
Grafana Loki + Promtail для сбора и отображения логов.
Трассировки отправляем в Jaeger.
Хронология релиза
А теперь самое интересное — релиз монолита в k8s. Был анонсирован код-фриз в компании. Переключение происходило в несколько этапов и длилось несколько дней. За это время нам нужно было отловить все те баги, которые по тем или иным причинам не могли быть воспроизведены в dev-окружении.
Главная цель — выкатить сервис-монолит в кубер и перевести на него трафик с минимальными потерями для бизнеса и клиента.
-
Эпизод I: Новая надежда.
Для начала мы нарезали несколько worker-нод из резерва в k8s специально для этого сервиса, что позволило нам гарантировать выделенные ресурсы и не аффектить другие сервисы.
Как я уже упоминал ранее, для приёма и балансировки внешнего трафика мы используем nginx. С помощью директивы split_clients мы могли направлять клиентский трафик в тот или иной upstream.
Итак, начинаем. 5% трафика в k8s.
-
Эпизод II: Баги наносят ответный удар.
Фикс багов будем воспринимать как должное, т.к. без них, увы, не обошлось:
Одним из неприятных моментов, который не учли и не протестировали при переезде — функционал генерации промокодов. Как оказалось, промокоды генерировались… угадаете как и куда? Прямиком в CSV файлик “под ноги”. Как workaround мы просто смонтировали emptyDir volume в Pod’ы приложения и релиз продолжился. Костыль жил недолго, т.к наша доблестная команда разработки в этот момент параллельно работала над новым сервисом промокодов. Таким образом мы отрезали очередной кусочек от монолита и вывезли функционал в отдельный микросервис.
Была кратковременная недоступность авторизации с помощью SberID из-за косяка в коде, который быстро пофиксили, и всё нормализовалось.
Задели авторизацию с помощью SMS. Правда, на этот раз из-за того, что не прокинули в нужном месте X-Real-IP header в конфигурации nginx.
Но не будем заострять на этом внимание, т.к статья всё-таки не об ошибках в коде.
Учитывая, что наши железные ресурсы были ограничены на тот момент, мы не могли себе позволить просто так пойти и заказать x8 серверов/виртуальных машин по 32 CPU и 128GB RAM. Поэтому пришлось параллельно высвобождать старые серверы с приложениями и добавлять их как worker-ноды в k8s. Этот процесс повторялся вплоть до конца переезда, пока мы не перевели 100% клиентов.
На следующий день с интервалами в несколько часов мы пустили 10%, 25%, 50%.
-
Эпизод 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)
lazy_val
24.10.2023 10:11+1В качестве основной системы мониторинга используем TSDB решение Victoria Metrics
Было бы интересно услышать - почему не Prometheus?
mshalenko Автор
24.10.2023 10:11+4Здравствуй! На предыдущем месте работы мы с коллегами администрировали сотни кластеров Openshift. По дефолту Openshift поставляется с Prometheus. Т.к кластеры были высоконагруженные, метрик было много, Prometheus потреблял очень много ресурсов CPU, RAM, IO. В качестве эксперимента мы подняли на серверах с такими же ресурсами Victoria Metrics и сравнили их работу. Разница в потреблении была в 2-3 раза. Поэтому без лишних раздумий, в Ситидрайве выбор пал на Victoria Metrics. Помимо этого компоненты Виктории легко масштабируются.
puzo
24.10.2023 10:11этого признания я давно ждал "прод шатал" - где мои 800 р? когда я с не смог сдать машину?
А если серьезно, то результат супер! Сам в таком сейчас варюсь.
но про 800р осадок остался....... ((( ;)Chernyshevskaia
24.10.2023 10:11Привет! Напиши в личные сообщения, пожалуйста, ФИО и номер телефона из приложения, а также номер авто или дату аренды, когда был сбой. Передам ответственным — всё проверим ????
GoooodBoy
24.10.2023 10:11Вы пишите, что разработали "уникальный chart". А в чем его уникальность?
mshalenko Автор
24.10.2023 10:11Все просто. Он написан таким образом, что подходит для всех сервисов в компании. У нас нет такого, что под каждый сервис пишется отдельный хелм чарт. Правки если и вносятся, то только в одном месте - в unique-helm-chart. Так мы его называем :)
GoooodBoy
24.10.2023 10:11+2Тогда я бы назвал "универсальный", а то уникальный вводит в некоторое заблуждение.
1q2w1q2w
24.10.2023 10:11+1вопрос про монолит в облаке, понятно что все преимущества переезда ощущаются после распила монолита, когда можно оперировать маленькими автономными кусками приложения. Но в статье говорится что мигрировали именно монолит. Так а в чем собственно преимущества монолита в облаке если деплой по прежнему занимает много времени, скейлинг работает но требует количество ресурсов пропорциональное количеству реплик, стоимость тоже достаточно высокая, т.к. у всех провайдеров стоимость "больших" инстансов высокая (а в "маленькие" он не поместится). Если не секрет, какой примерно был размер артефактов\потребление ресурсов в кубере сразу после переезда?
erley
24.10.2023 10:11Полезный real-life опыт, интересно почитать. И привязки к конкретным cloud-providers нет, молодцы!
Heda
Куча работы еще впереди, но шаг в нужную сторону. Раз сервис так и остался частично в виде монолита, хотелось бы узнать, какие именно метрики его частей отвечают за readness/liveness. Что делать, если 1 за 10 метрик начинает моросить, хотя остальные части монолита в норме?
И еще очень болезненный вопрос для меня - секреты в волте, а как вы их безопасно вытягиваете? Те же логопассы к базам как в приклад подкладываются?
mshalenko Автор
Приветствую!
Т.к livenessProbe отвечает за рестарт Pod'ов, этот хелсчек дёргается наиболее часто. Логика его работы на данный момент проста - мы проверям работоспособность веб-сервера в целом и отвечаем 200.
readinessProbe - с этим хелсчеком ситуация обстоит хитрее. Результат этой пробы говорит о том, готов или не готов Pod принимать трафик. Когда мы дёргаем его, помимо работы веб-сервера, мы проверяем критически важные инфраструктурные компоненты на доступность включая базы, шины данных и т.д.
Что касается самих метрик, то тут тоже всё просто. У нас заведены алерты на все критически важные для инфраструктуры и бизнеса метрики. Если что-то идёт не так, Alertmanager даст нам знать.
Секреты из Vault на данный момент доставляются в k8s с помощью gitlab-ci, но в будущем есть планы усовершенствовать этот способ, добавив монтирование секретов при инициализации контейнера. Таким образом из gitlab-ci этот этап вообще можно будет убрать.
Sigest
Для секретов из vault - есть довольно удобный оператор vault secrets operator от самого hashicorp. Стабильная штука. Создает CRD и через эти CRD настраивается связь между развернутым приложением и Vault. Не супер секьюрно, так как secrets создаются в кубере как они есть, как если бы вы их деплоили через kubectl apply, и это на тот случай, если секреты не хочется держать в репозитории. Но и не так геморно, если вдруг надо подружить например ArgoCD с Vault через его систему плагинов.