Микросервисы на монолите

ac55971c334cf5a180bed4c4a0428b95

Всем привет!  

Скажу сразу, эта статья не про очередное переписывание монолита на микросервисы, а о применении микросервисных практик в рамках существующего проекта с использованием интересных, как мне кажется, подходов. Наверное, уже нет смысла объяснять, почему многие проекты активно используют микросервисную архитектуру. Сегодня в 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 можно использовать для того, чтобы роутить запросы на разные сервисы. 

Примерно вот так выглядит схема:

90eab292eb49398e25f4fe38c1d2df58

Проанализировав наш API, мы разбили его на несколько групп:  

  • Внешний API (методы для других сервисов).

  • Внутренний API (методы для фронтенда).

  • Некритичные API-методы, которые зависимы от внешних сервисов, но не влияют на работу системы (статистика, счетчики и т. п.)

Таким образом можно легко создавать новые сервисы. Управлять ресурсами для каждого из них, масштабировать те методы, которые несут большую нагрузку, и получать другие преимущества. Если один из методов перестанет отвечать из-за каких-то внешних причин, то его можно изолировать в отдельное развёртывание и снизить нагрузку на другие части системы.

В конечном итоге наша система, имея один репозиторий и Django под капотом, раздроблена благодаря Kubernetes на 42 сервиса, 5 из которых делят HTTP-трафик, а остальные 37 — консьюмеры и Celery-задачи. И в таком виде она может быть актуальна еще пару лет, несмотря на использование относительно старого стека технологий.

© Habrahabr.ru