Навроцкий Артем
Сервис жил на виртуальных хостах в AWS и активно общался с MongoDB.
Для общения с шардированным кластером MongoDB используется отдельный процесс: mongos.
По рекомендации 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.
На месте mongos мог быть и другой сервис.
В данном случае про него важно знать следующее:
Как полагается в такой ситуации, сам сервис к переезду в Kubernetes не был готов.
Также по классике жанра, мы сами не были хорошо знакомы с Kubernetes.
Переделать сервис так, чтобы он был Kubernetes Ready перед переездом, звучит заманчиво, но нет:
Пропасть нельзя перепрыгнуть в два прыжка, поэтому мы будем двигаться через нее маленькими шажками.
План переноса: перенести сервис «как есть», по возможности сохраняя топологию.
На каждой виртуалке были запущены по одному mongos'у на каждый кластер. В качестве базового варианта для старта, было решено запускать mongos'ы на каждой ноде через DaemonSet.
Этот вариант запуска хорошо использовать как базовый при сравнении производительности.
Сохраняется:
Меняется:
Но, к примеру, для сервиса Jaeger Collector этот вариант развёртывания подошел идеально.
На виртуалках mongos'ы потребляют разное количество ресурсов в зависимости от сервисов, которые их используют. Эти значения разные, но в целом стабильные.
В Kubernetes очень большой разброс и очень разные сервисы попадают на ноду.
В результате все mongos'ы стремятся «раздуться» по максимуму.
Запускаем mongos'ы внутри Pod'а. Рядом с самим сервисом.
Два варианта:
Запуск в виде side car отмели, так как ресурсы выставляются на каждый контейнер, а не на Pod в целом.
Для HPA (Horizontal Pod Autoscaler) ресурсы должны быть выставлены на каждый контейнер Pod'а. Целевое использование CPU задаётся от суммы Request'ов контейнера.
В этом случае управление Request/Limit должно превратиться в адъ.
Заявленные Pod'ом ресурсы всегда стремятся быть завышенными: избыток ресурсов не так заметен, как их недостаток.
Плюсы:
Минусы:
В целом этот вариант был в эксплуатации довольно долго без серьёзных нареканий.
В целях экономии пытаемся перейти к пулу mongos'ов: запускать его в виде Deployment и ходить в него через Service.
Плюсы:
Минусы:
Это самая дорогая с точки зрения реализации схема.
Что пошло хорошо:
Что пошло плохо:
В MongoDB до версии 5.0 не реализован Graceful Shutdown: при завершении mongos он просто рвёт все подключения.
На стороне приложения мы не знаем, в каком состоянии оказалась транзакция.
В результате мы завернули mongos в простенький proxy, который после начала завершения сразу отправлял ошибку 11600 (InterruptedAtShutdown) на любой запрос.
По умолчанию Service прячет Pod'ы за общим IP-адресом.
По DNS-имени сервиса резолвится один IP-адрес. Service сам определяет, на какой Pod попадёт сетевое подключение.
В случае, когда клиенту важно знать, куда именно он подключается (например, в случае с gRPC или mongos), это либо работает плохо (gRPC), либо не работает (mongos).
Клиент держит несколько подключений к mongos. В случае использования Cluster IP он начинает их «путать».
Данные о курсоре при выборке данных живут на mongos и клиент начинает постоянно терять курсоры.
Это можно обойти, используя SessionAffinity, но платой будет очень неравномерная нагрузка на mongos'ы.
Для сервисов с clusterIP: None по DNS-имени сервиса резолвятся все IP-адреса Pod'ов.
Это даёт приложению больше данных для принятия решений, но есть ряд особенностей, которые нужно учитывать.
Подключение к IP уже несуществующего Pod'а не получает TCP-ошибку, а отваливается по таймауту.
https://bozaro.ru/2021/06/17/k8s-headless-service-pod-disappeared/
В Kubernetes есть возможность выбора Pod'а внутри Service с учетом топологии:
К сожалению, данная опция не работает с Headless Service.
Для реализации привязки к Availability Zone мы создали по одному Service на каждую Availability Zone и на уровне приложения брали правильный URL.
Информация об Availability Zone хранится в Label'ах ноды, но для доступа к ним нужно давать Pod'у дополнительные права и писать дополнительную логику.
Чтобы не бороться с правами, мы просто получили имя зоны по адресу:
К счастью, данный вопрос уже поднимался в сообществе:
В комментарии от 1 марта 2022 года было предложено решение через MutatingAdmissionWebhook:
Сейчас самое время задавать вопросы.
Отзыв по ссылке QR-кода категорически приветствуется :)