Микросервисы на монолите
Всем привет!
Скажу сразу, эта статья не про очередное переписывание монолита на микросервисы, а о применении микросервисных практик в рамках существующего проекта с использованием интересных, как мне кажется, подходов. Наверное, уже нет смысла объяснять, почему многие проекты активно используют микросервисную архитектуру. Сегодня в IT возможности таких инструментов как Docker, Kubernetes, Service Mesh и прочих сильно меняют наше представление об архитектуре современного приложения, вынуждая пересматривать подходы и переписывать целые проекты на микросервисы. Но так ли это необходимо для всех частей проекта?
В нашем проекте есть несколько систем, которые писались в те времена, когда преимущества микросервисного подхода были не столь очевидны, а инструментов, позволяющих использовать такой подход, было очень мало, и переписывать системы полностью просто нецелесообразно. Для адаптации к новой архитектуре мы решили в части задач использовать асинхронный подход, а также перейти к хранению части данных на общих сервисах. Само приложение при этом осталось на Django (API для SPA). При переходе на k8s деплой приложения был разбит на несколько команд: HTTP-часть (API), Celery-воркеры и RabbitMQ-консьюмеры. Причем было именно три развёртывания, то есть все воркеры, как и консьюмеры, крутились в одном контейнере. Быстрое и простое решение. Но этого оказалось недостаточно, так как это решение не обеспечивало нужный уровень надежности.
Начнем с RabbitMQ-консьюмеров. Основная проблема была в том, что внутри контейнера стартовал воркер, который запускал много потоков на каждый консьюмер, и пока их было пару штук, всё было хорошо, но сейчас их уже десятки. Решение нашли простое: каждый консьюмер вывели в отдельную команду manage.py и деплоим отдельно. Таким образом, у нас несколько десятков k8s-развёртываний на одном образе, с разными параметрами запуска. Ресурсы мы тоже выставляем для каждого консьюмера отдельно, и реальное потребление у консьюмеров достаточно невысокое. В результате для одного репозитория у нас десятки реальных отдельных сервисов в k8s, которые можно масштабировать, и при этом разработчикам намного удобнее работать только с одним репозиторием. То же самое и для развёртываний Сelery.
Если решение с воркерами достаточно простое и очевидное, то с HTTP-частью было немного сложнее. В нашей системе внешний межсервисный API и внутренний для фронтенда были в одном развёртывании с большим лимитом ресурсов и большим количеством инстансов.
Вроде бы рабочее решение? Да, но ДомКлик — большой сложный механизм с сотнями сервисов, и иногда выход одного стороннего (вне конкретной системы) сервиса, который используется в одном единственном API-методе, может привести к пробке на сервере uwsgi. К чему это приводит, надеюсь, объяснять не нужно, всё встает или тормозит. В такие моменты должен приходить Кэп и говорить что-то вроде:»Нужно было делать отдельные микросервисы и тогда упал бы только тот, что связан с отказавшим внешним сервисом». Но у нас-то монолит.
В Kubernetes есть такой сервис, как Ingress Controller, его основная задача — распределять нагрузку между репликами. Но он, по сути, nginx, а значит Ingress Controller можно использовать для того, чтобы роутить запросы на разные сервисы.
Примерно вот так выглядит схема:
Проанализировав наш API, мы разбили его на несколько групп:
Внешний API (методы для других сервисов).
Внутренний API (методы для фронтенда).
Некритичные API-методы, которые зависимы от внешних сервисов, но не влияют на работу системы (статистика, счетчики и т. п.)
Таким образом можно легко создавать новые сервисы. Управлять ресурсами для каждого из них, масштабировать те методы, которые несут большую нагрузку, и получать другие преимущества. Если один из методов перестанет отвечать из-за каких-то внешних причин, то его можно изолировать в отдельное развёртывание и снизить нагрузку на другие части системы.
В конечном итоге наша система, имея один репозиторий и Django под капотом, раздроблена благодаря Kubernetes на 42 сервиса, 5 из которых делят HTTP-трафик, а остальные 37 — консьюмеры и Celery-задачи. И в таком виде она может быть актуальна еще пару лет, несмотря на использование относительно старого стека технологий.