Как мы сервисы с mongos в Kubernetes переносили

Навроцкий Артем

Как мы сервисы с mongos в Kubernetes переносили

Навроцкий Артем

Исходная ситуация

Жил-был сервис backend'a...

Сервис жил на виртуальных хостах в AWS и активно общался с MongoDB.

Для общения с шардированным кластером MongoDB используется отдельный процесс: mongos.

mongos
Сервис для доступа к шардированному MongoDB-кластеру, который не является «тупым» Proxy — он еще маршрутизирует запросы.

Mongos жил на localhost

По рекомендации MongoDB, mongos жил на той же виртуалке, что и сам сервис:

Deploying multiple mongos routers supports high availability and scalability. A common pattern is to place a mongos on each application server.

Deploying one mongos router on each application server reduces network latency between the application and the router.

https://docs.mongodb.com/manual/core/sharded-cluster-components/#number-of-mongos-and-distribution

Итого про mongos

На месте mongos мог быть и другой сервис.

В данном случае про него важно знать следующее:

Переезд в Kubernetes

Цели

Правда жизни

Как полагается в такой ситуации, сам сервис к переезду в Kubernetes не был готов.

Также по классике жанра, мы сами не были хорошо знакомы с Kubernetes.

Переделать сервис?

Переделать сервис так, чтобы он был Kubernetes Ready перед переездом, звучит заманчиво, но нет:

Переносим «как есть»

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

План переноса: перенести сервис «как есть», по возможности сохраняя топологию.

Текущая топология

DaemonSet

DaemonSet

DaemonSet

На каждой виртуалке были запущены по одному mongos'у на каждый кластер. В качестве базового варианта для старта, было решено запускать mongos'ы на каждой ноде через DaemonSet.

Этот вариант запуска хорошо использовать как базовый при сравнении производительности.

DaemonSet

Сохраняется:

Меняется:

Что пошло хорошо?

Что пошло плохо?

Но, к примеру, для сервиса Jaeger Collector этот вариант развёртывания подошел идеально.

Что пошло плохо?

Ресурсы на mongos

На виртуалках mongos'ы потребляют разное количество ресурсов в зависимости от сервисов, которые их используют. Эти значения разные, но в целом стабильные.

В Kubernetes очень большой разброс и очень разные сервисы попадают на ноду.

В результате все mongos'ы стремятся «раздуться» по максимуму.

Запуск mongos'ов внутри Pod'а

Запуск внутри Pod'а

Запускаем mongos'ы внутри Pod'а. Рядом с самим сервисом.

Два варианта:

Запуск внутри Pod'а

Запуск в виде side car

Запуск в виде side car отмели, так как ресурсы выставляются на каждый контейнер, а не на Pod в целом.

Для HPA (Horizontal Pod Autoscaler) ресурсы должны быть выставлены на каждый контейнер Pod'а. Целевое использование CPU задаётся от суммы Request'ов контейнера.

В этом случае управление Request/Limit должно превратиться в адъ.

Ресурсы всегда завышены

Заявленные Pod'ом ресурсы всегда стремятся быть завышенными: избыток ресурсов не так заметен, как их недостаток.

Запуск в одном контейнере

Плюсы:

Запуск в одном контейнере

Минусы:

Что пошло хорошо?

Что пошло плохо?

В целом этот вариант был в эксплуатации довольно долго без серьёзных нареканий.

Запуск mongos'ов в отдельном Deployment

Deployment + Service

В целях экономии пытаемся перейти к пулу mongos'ов: запускать его в виде Deployment и ходить в него через Service.

Deployment + Service

Deployment + Service

Плюсы:

Deployment + Service

Минусы:

Это самая дорогая с точки зрения реализации схема.

Deployment + Service

Что пошло хорошо:

Что пошло плохо:

Gracefully Shutdown

В MongoDB до версии 5.0 не реализован Graceful Shutdown: при завершении mongos он просто рвёт все подключения.

На стороне приложения мы не знаем, в каком состоянии оказалась транзакция.

В результате мы завернули mongos в простенький proxy, который после начала завершения сразу отправлял ошибку 11600 (InterruptedAtShutdown) на любой запрос.

Cluster IP

По умолчанию Service прячет Pod'ы за общим IP-адресом.

По DNS-имени сервиса резолвится один IP-адрес. Service сам определяет, на какой Pod попадёт сетевое подключение.

В случае, когда клиенту важно знать, куда именно он подключается (например, в случае с gRPC или mongos), это либо работает плохо (gRPC), либо не работает (mongos).

Cluster IP

Cluster IP

Клиент держит несколько подключений к mongos. В случае использования Cluster IP он начинает их «путать».

Данные о курсоре при выборке данных живут на mongos и клиент начинает постоянно терять курсоры.

Это можно обойти, используя SessionAffinity, но платой будет очень неравномерная нагрузка на mongos'ы.

Headless Service

Для сервисов с clusterIP: None по DNS-имени сервиса резолвятся все IP-адреса Pod'ов.

Это даёт приложению больше данных для принятия решений, но есть ряд особенностей, которые нужно учитывать.

Привязка к Availability Zone

В Kubernetes есть возможность выбора Pod'а внутри Service с учетом топологии:

К сожалению, данная опция не работает с Headless Service.

Привязка к Availability Zone

Для реализации привязки к Availability Zone мы создали по одному Service на каждую Availability Zone и на уровне приложения брали правильный URL.

Информация об Availability Zone хранится в Label'ах ноды, но для доступа к ним нужно давать Pod'у дополнительные права и писать дополнительную логику.

Привязка к Availability Zone

Чтобы не бороться с правами, мы просто получили имя зоны по адресу:

Привязка к Availability Zone

К счастью, данный вопрос уже поднимался в сообществе:

Спасибо за внимание!

Сейчас самое время задавать вопросы.

Отзыв по ссылке QR-кода категорически приветствуется :)