Балансировка нагрузки в Kubernetes представляет собой целый ряд способов перенаправления входящего трафика на определенные серверы кластера. Это позволяет распределять трафик равномерно, облегчая задачи масштабирования.
Главный плюс балансировки состоит в том, что она позволяет избежать простоя приложения. Таким образом можно предотвратить запланированный простой из-за развертывания новой версии ПО или незапланированный из-за проблем с оборудованием. Давайте рассмотрим, как балансирование нагрузки помогает стабилизировать работу кластера Kube, повышая доступность приложений.
Балансировать нагрузку помогают службы Kubernetes, поэтому дадим их общее описание и покажем, как они работают, а затем приведем конкретные примеры балансировки нагрузки. Но прежде всего о том, как в Kube реализовано отслеживание подов, без которого и сама балансировка была бы крайне затруднительна.
Поды в Kubernetes являются временными объектами и получают новый IP при каждом запуске: после выполнения определенной задачи они уничтожаются и затем создаются заново при новом развертывании. При отсутствии инструментов Kubernetes service нам пришлось бы отслеживать IP всех активных подов. И это было бы крайне сложной задачей, особенно по мере того, как наше приложение масштабируется. Служба Kube решает эту проблему благодаря селектору. Посмотрим на такой код (замените значения в ряде строк на нужные):
kind: Service
metadata:
name: timewebapp
spec:
selector:
app: timewebapp
ports:
- protocol: TCP
name: timewebapp
port: 5428
targetPort: 5428
Селектор обеспечивает корректность сопоставления служб с относящимися к ним подами. Когда служба получает под с matching label, то происходит обновление IP пода в списках объектов Endpoints. Endpoints отслеживают IP-адреса всех подов и обновляются автоматически, а каждая служба создает свой объект Endpoint.
Впрочем, для наших целей нам не нужно вдаваться в подробности об Endpoints. Просто помните, что именно Endpoints обновляют список IP-адресов, чтобы служба Kube могла перенаправлять свой трафик.
Далее заметим, что способ определения службы с помощью селектора является наиболее распространенным, однако мы также можем определить службу без селектора. Например, если мы перенесем наше приложение в Kube, то сможем оценить его поведение без переноса сервера. Давайте попробуем использовать существующее приложение, находящееся на старом сервере:
kind: Service
metadata:
name: timewebapp-without-ep
spec:
ports:
- protocol: TCP
port: 5428
targetPort: 5428
И затем:
kind: Endpoints
metadata:
name: timewebapp-without-ep
subsets:
- addresses:
- ip: x.x.x.x #укажите актуальный IP
ports:
- port: 5428
Это позволит установить имя timewebapp-without-ep
для подключения к серверу timewebapp
.
Kube по умолчанию всегда создает службу вида ClusterIP. Однако существует четыре вида служб, рассчитанных каждая на свои задачи, и вместе они помогают обеспечить достаточно гибкую балансировку нагрузки. Рассмотрим их все и приведем примеры кода для настройки.
Предназначен для внутрикластерного взаимодействия приложений. Настраивается это так (значения приложения даны случайные, их следует заменить на свои):
kind: Service #обязательная строчка для определения любой службы
metadata:
name: timewebapp
spec:
type: ClusterIP
selector:
app: timewebapp
ports:
- protocol: TCP
port: 5428
targetPort: 5428
Внешняя служба для сопоставления подов с хостами через постоянный порт, прописываемый отдельно ниже (все значения тоже даны случайные, замените их на свои):
kind: Service
metadata:
name: timewebapp
spec:
type: NodePort
selector:
app: timewebapp
ports:
- protocol: TCP
port: 5428
targetPort: 5428
nodePort: 32157
Служба для работы в облачной инфраструктуре. Позволяет обеспечить маршрутизацию, например, через сайт. Вот код запуска:
kind: Service
metadata:
name: timewebapp
spec:
type: LoadBalancer
selector:
app: timewebapp
ports:
- protocol: TCP
port: 5428
targetPort: 5428
Эта служба нужна для обеспечения внекластерного доступа. Делается это просто:
metadata:
name: timewebapp
spec:
type: ExternalName
externalName: timewebapp.mydomain.ru
Далее отметим, что у любой службы создается имя DNS по такому шаблону: название-службы.пространство-имен.svc.cluster.local
. Такая запись будет указывать на IP кластера. А при ее отсутствии Kube будет обращаться к IP отдельных подов.
Как мы убедились по описаниям всех четырех служб Kube, организовать балансировку нагрузки можно по-разному. Начнем с описания того, как это делается внутри кластера.
Для внутрикластерной балансировки предназначена служба ClusterIP (код для настройки этой и других служб Kube мы публиковали выше в их описаниях). Она подойдет, например, для организации взаимодействия отдельных групп подов, расположенных в пределах одного кластера Kube. Организовать доступ к службе можно организовать двумя способами: через DNS или при помощи переменных окружения.
Про то, как это делается с помощью DNS, было сказано выше. Добавим, что такая организация доступа — это наиболее распространенный и рекомендуемый способ взаимодействия микросервисов. Однако учтите, что DNS работают в Kube только с надстройкой DNS-сервера: например, CoreDNS.
Что касается переменных среды, то они устанавливаются при запуске нового пода через инструкцию service-name. Вам могут понадобиться переменные PORT
и SERVICE_HOST
, а вот инструкции для их установки:
service-name_PORT
service-name_SERVICE_HOST
Она может выполняться с использованием NodePort (далее NP) и LoadBalancer (далее LB). NP подходит для балансировки ограниченного числа служб, а его преимущество заключается в обеспечении подключения без выделенного внешнего инструмента балансировки.
Первое ограничение NP: он подходит только для частной сети, а вот интернет-подключение через NP организовать не получится. Еще один недостаток NP в том, что он работает только через статические порты и в ограниченном диапазоне, причем служба должна выделять один и тот же порт для каждого узла. Это становится проблематичным, когда приложение масштабируется до нескольких микросервисов.
LB предоставляет общедоступный IP или DNS, к которым могут подключаться внешние пользователи. Трафик поступает от LB к сопоставляемой службе на назначенном порту, который в конечном итоге перенаправляет его на рабочие поды. Однако у LB нет прямого соответствия с подами.
Давайте посмотрим, как создать внешний LB. В примере ниже мы запустим под и подключим его графический интерфейс из внешней сети. Обратите внимание, что LB не фильтрует входящий или исходящий трафик. Это просто прокси для связи с внешней сетью, перенаправляющий трафик на соответствующие модули/сервисы. Создаем кластер командой create cluster и прописываем следующие параметры (значения name
и region
приведены для примера, а в поле ssh-public-key
введите свой публичный ключ SSH).
--name myTimewebCluster
--region my-timeweb-region
--with-oidc
--ssh-access
--ssh-public-key <xxxxxxxxxx>
--managed
О том, как сгенерировать ключи SSH, смотрите в нашем руководстве.
Теперь редактируем yaml
пода:
kind: Pod
metadata:
name: timewebpod
labels:
app: timewebpod
spec:
containers:
- name: timewebpod
image: timewebpod:latest
Затем создаем под:
kubectl apply -f timewebpod.yaml
Проверяем работоспособность:
kubectl get pods --selector='app=timewebpod'
И подключаем LB (значения в образцах кода случайные, замените их на актуальные):
kind: Service
metadata:
name: timewebpod-ext-serv
spec:
type: LoadBalancer
selector:
app: timewebpod
ports:
- name: timewebpod-admin
protocol: TCP
port: 14953
targetPort: 14953
Далее стартуем сервис и подтверждаем:
kubectl apply -f timewebpod-svc.yaml
service/timewebpod-ext-serv created
kubectl get svc
Полученный DNS копируем в браузер и не забываем там же прописать порт, указанный в коде выше. Допустим, мы получили такой DNS:
http://b9f305e6d743a85cb32f48f6a210cb51.my-timeweb-region.ru
Тогда нужно вставить в браузер следующее:
http://b9f305e6d743a85cb32f48f6a210cb51.my-timeweb-region.ru: 14953
Теперь вы сможете поделиться этим адресом с любым, кто желает подключиться к вашей записи администратора. Как видим, создать и настроить внешний балансировщик для приложения довольно легко. Правда, у Load Balancer есть некоторые ограничения, но их помогает обойти Ingress.
Мы видели, что LoadBalance создает экземпляры приложения для каждой службы. Это нормально, пока у нас есть несколько служб, однако с увеличением служб управлять ими становится сложно. Кроме того, LB не поддерживает маршрутизацию URL, SSL и многое другое. И здесь на помощь приходит Ingress, который является расширением для NP и LB. В задачи Ingress входит обработка внутреннего трафика с целью определить, какие поды или службы пересылать дальше. Основной функцией Ingress является балансировка нагрузки, однако с его помощью можно также выполнять маршрутизацию URL, терминацию SSL и ряд других функций. Конфигураций Ingress довольно много, и их нетрудно найти по запросам. Приведем одну в качестве иллюстрации:
В приведенном примере определены правила, по которым будет идти трафик от конечных пользователей. Нужно также добавить, что Ingress не является службой Kube, как, например, LB или NP, а представляет собой набор правил, используемый этими службами. Кроме того, кластеру, использующему Ingress, необходим Ingress Controller. Этих контроллеров создано довольно много, а ознакомиться с некоторыми популярными решениями вы можете по этим ссылкам: AWS ALB, NGINX, Istio, Traefik.
Добавим, что различные контроллеры имеют разные функции и возможности, поэтому вам придется оценить их, исходя из ваших требований. Но какой бы контроллер вы ни использовали, Ingress значительно упростит настройку и управление правилами маршрутизации и поможет реализовать трафик на основе SSL. И, конечно, как и традиционные инструменты, контроллеры Ingress поддерживают различные алгоритмы балансировки.
Итак, мы узнали о различиях между службами Kubernetes, научились получать к ним доступ, выяснили, как организовать внутрикластерную и внешнюю балансировку, а также познакомились с дополнительными инструментами. Но чтобы эффективно задействовать службы Kube, вам нужно понять, какая из них оптимальна для решения ваших задач, и настроить ее соответствующим образом. Это позволит сэкономить массу времени на отладку и обеспечит безотказную работу ваших приложений и сервисов.