Балансировка нагрузки на стороне клиента gRPC

Я использую gRPC с Python в качестве клиента/сервера внутри стручков kubernetes... Я хотел бы иметь возможность запускать несколько модулей одного типа (серверы gRPC) и разрешать клиенту подключаться к ним (произвольно).

Я отправил 10 модулей сервера и настроил "службу" для их таргетинга. Затем в клиенте я подключился к DNS-имени службы - это означает, что kubernetes должен выполнить балансировку нагрузки и направить меня к произвольному серверному модулю. В действительности, клиент вызывает функции gRPC (что работает хорошо), но когда я просматриваю логи, я вижу, что все вызовы идут на один и тот же серверный модуль.

Я предполагаю, что клиент выполняет какое-то DNS-кэширование, которое приводит к тому, что все вызовы отправляются на один и тот же сервер. Это тот случай? Есть ли в любом случае отключить его и настроить тот же клиент заглушки, чтобы сделать "новый" вызов и получать новый IP по DNS с каждым вызовом?

Я знаю о накладных расходах, которые я могу вызвать, если он будет каждый раз запрашивать DNS-сервер, но распределение нагрузки для меня сейчас гораздо важнее.

Ответ 1

Позвольте мне воспользоваться возможностью, чтобы ответить, описав, как должны работать вещи.

Способ работы LB на стороне клиента работает в ядре gRPC C (основа для всех, кроме Java и Go flavors или gRPC) выглядит следующим образом (авторитетный документ можно найти здесь):

Клиентская сторона LB остается простой и "тупой". Способ, который мы выбрали для реализации сложных политик LB, - через внешний сервер LB (как описано в вышеупомянутом документе). Вы не имеете отношения к этому сценарию. Вместо этого вы просто создаете канал, который будет использовать политику LB (по умолчанию) pick-first.

Ввод политики LB - это список разрешенных адресов. При использовании DNS, если foo.com переходит на [10.0.0.1, 10.0.0.2, 10.0.0.3, 10.0.0.4], политика попытается установить соединение со всеми из них. Первый, который успешно соединится, станет выбранным до тех пор, пока он не отключится. Таким образом, имя "pick-first". Более длинное имя могло бы быть "выбрать сначала и придерживаться его как можно дольше", но это сделало для очень длинного имени файла:). Если/когда выбранный будет отключен, политика pick-first перейдет к возврату следующего успешно подключенного адреса (внутренне называемого "подключенным подканалом" ), если таковой имеется. Еще раз, он будет продолжать выбирать этот подключенный подканал до тех пор, пока он остается подключенным. Если все они терпят неудачу, вызов завершится неудачно.

Проблема заключается в том, что разрешение DNS, основанное на принципе исходящего трафика, запускается только 1) при создании канала и 2) при отключении выбранного подключенного подканала.

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

Данные изменения, внесенные в Q1 2017 (см. https://github.com/grpc/grpc/issues/7818), позволят клиентам выбирать другую политику LB, а именно Round Robin. Кроме того, мы можем взглянуть на введение "рандомизированного" бита в эту конфигурацию клиента, которая перетасовывает адреса до того, как сделает Round-Robin над ними, эффективно достигая того, что вы намереваетесь.

Ответ 2

Обычная балансировка нагрузки K8S не работает для gRPC. Следующая ссылка объясняет почему. https://kubernetes.io/blog/2018/11/07/grpc-load-balancing-on-kubernetes-without-tears/

Это потому, что gRPC построен на HTTP/2, а HTTP/2 предназначен для иметь одно долговременное TCP-соединение, через которое проходят все запросы мультиплексированный - это означает, что несколько запросов могут быть активны на одном соединение в любой момент времени. Обычно это здорово, так как уменьшает накладные расходы на управление соединением. Тем не менее, это также означает, что (как вы можете себе представить) балансировка на уровне соединения не очень полезно. Как только соединение установлено, балансировки больше нет быть сделано. Все запросы будут прикреплены к одному целевому модулю.

Большинство современных контроллеров входа могут справиться с этим, но они либо горячие из духовки (nginx), либо в альфа-версии (traefik), либо требуют последней версии K8S (Linkerd). Вы можете выполнить балансировку нагрузки на стороне клиента, из которых вы можете найти решение Java здесь.

Ответ 3

Если вы создали ванильный сервис Kubernetes, служба должна иметь свой собственный сбалансированный по нагрузке виртуальный IP (проверьте, показывает ли kubectl get svc your-service CLUSTER-IP для вашей службы). Если это так, кэширование DNS не должно быть проблемой, поскольку этот единственный виртуальный IP должен делить трафик между фактическими бэкэндами.

Попробуйте kubectl get endpoints your-service подтвердить, что ваша служба действительно знает обо всех ваших серверах.

Если у вас есть служба служба безглавых головоломок, поиск DNS вернет A-запись с 10 IP-адресами (по одному для каждого вашего Pods). Если ваш клиент всегда выбирает первый IP-адрес в записи A, это также объясняет поведение, которое вы видите.