Понимание сервисов Kubernetes и входных сетей

Кубернетес-сервисы

Как мы знаем, кластер Kubernetes состоит из набора узловых машин, на которых запущены контейнерные приложения внутри объектов с именем Pods . Модули сгруппированы в зависимости от типа предоставляемых ими услуг в различные группы. Поды должны каким-то образом принимать соединения из вашего кластера или из-за пределов вашего кластера.

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

В случае внутренней связи мы знаем, что каждому поду в системе назначается собственный уникальный IP-адрес, известный как IP-адрес пода. Но эти IP-адреса не являются статическими, поскольку мы знаем, что модули могут выйти из строя в любое время, а в кластере все время создаются новые модули. Поэтому мы не можем полагаться на эти IP-адреса для внутренней связи.

Поэтому нам нужно что-то согласованное, чтобы вещи вне или внутри кластера могли иметь к нему постоянный доступ. Служба — это объект Kubernetes, который действует как конечная точка для обеспечения связи между различными компонентами внутри и вне приложения Другими словами, сервис — это стабильный адрес для подов. Три важных типа службы в Kubernetes:

  1. IP кластера
  2. NodePort
  3. LoadBalancer

IP кластера

Веб-приложение с полным стеком обычно состоит из различных типов модулей, в которых размещаются разные части приложения. У него может быть набор модулей, на которых работает внутренний сервер, набор модулей, на которых работает интерфейсный веб-сервер, набор модулей, на которых работает база данных, и так далее. Все эти наборы модулей должны взаимодействовать друг с другом. Как мы уже говорили, мы не можем зависеть от IP-адресов модулей, поскольку они не являются статическими.

ClusterIP — это тип службы Kubernetes, который используется для группировки модулей и предоставления единого интерфейса для доступа к ним. Например, входящий запрос от другой службы будет случайным образом перенаправлен на один из модулей в ClusterIP.

Теперь давайте посмотрим на пример. Прежде чем создавать службу ClusterIP, мы можем начать с создания простого модуля на основе файла определения.

внешний интерфейс-pod-definition.yml

apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
type: front-end
spec:
containers:
– name: nginx-container
image: nginx

Как мы видим, наш модуль — это просто контейнер, за которым стоит веб-сервер Nginx. Мы добавили приложение ярлыков и тип . Pod будет сгруппирован по типу front-end. Далее нам нужно запустить команду create, чтобы создать модуль.

kubectl create -f frontend-pod-definition.yml

Давайте посмотрим на определение службы ClusterIP:

fe-clusterip-service-definition.yml

apiVersion: v1
kind: Service
metadata:
name: front-end-service
spec:
type: ClusterIP
selector:
app: myapp
type: front-end
ports:
– targetPort: 80
port: 80

Определение службы имеет тип ClusterIP (это не обязательно, так как службы по умолчанию относятся к типу ClusterIP). Мы видим, что использовали селектор, чтобы связать службу с набором модулей. Под портами у нас есть целевой порт и порт.

Целевой порт — это порт, на котором предоставляется интерфейсная служба, в данном случае 80, и порт , на котором предоставляется служба ClusterIP, также 80.

Теперь мы можем создать сервис с помощью команды create.

kubectl create -f clusterip-service-definition.yml

Посмотрим на созданный сервис

Мы видим, что в дополнение к Kubernetes ClusterIP по умолчанию создается новый ClusterIP с именем front-end-service с IP-адресом. Имя службы может использоваться другими модулями для доступа к ней.

NodePort

NodePort — это тип сервиса Kubernetes, который прослушивает порт на узле и перенаправляет запросы на этот порт в модуль на узле. Давайте посмотрим на пример.

  • У нас есть узел с IP-адресом 10.1.3.4 .
  • Внутренняя сеть pod узла находится в диапазоне 10.244.0.0.
  • Сам модуль имеет IP-адрес 10.244.0.2.
  • Фактический веб-сервер работает на порту 80 в модуле.

По сути, мы хотим перенаправлять запросы, поступающие на 10.1.3.4, в модуль.

Когда мы создаем службу NodePort, ей назначается старший порт на всех узлах. Когда приходит запрос для node:port , он действует как встроенный балансировщик нагрузки и случайным образом отправляет запрос одному из модулей.

Давайте создадим службу NodePort для пересылки входящего запроса к узлу на порт 80 пода. Начнем с создания определения службы:

nodeport-service-definition.yml

apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: NodePort
selector:
app: myapp
type: front-end
ports:
– targetPort: 80
port: 80
nodePort: 32593

Мы видим три значения в разделе портов.

targetPort : порт модуля, на котором работает фактический веб-сервер, в данном случае это 80. Служба перенаправляет запросы на целевой порт. Если в спецификации не указаны порты, по умолчанию будет 80.

port : как и все объекты Kubernetes, Service — это виртуальный сервер внутри узла. Внутри кластера у него будет свой IP-адрес. «Порт» — это порт, доступный для самой службы NodePort. Это значение является обязательным.

nodePort: порт на узле, который используется для внешнего доступа к веб-серверу. Эти порты могут находиться только в допустимом диапазоне от 30000 до 32767. Это необязательное поле, если оно не указано, выбирается свободный порт из диапазона.

Теперь мы можем создать сервис командой

kubectl создать -f nodeport-service-definition.yml

Проверим, создана ли служба.

Попробуем получить доступ к сервису по IP ноды

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

minikube ip
Давайте используем curl для доступа к приложению, используя NodePort в этом IP-адресе.
curl 192.168.99.101:32593

Большой! Мы получили ответ от стручка.

LoadBalancer

Используя nodePort, мы смогли открыть наше веб-приложение в Интернете. Однако есть проблема — несколько экземпляров веб-приложения могут быть развернуты на нескольких узлах в нашем кластере. Чтобы получить доступ к этому веб-приложению, нам нужно предоставить пользователю как IP-адрес узла, так и порт узла. В реальной жизни трудно вручную определить IP-адрес и порт узла, которые должны быть предоставлены пользователю. Вместо этого нам нужен балансировщик нагрузки, чтобы представить наше веб-приложение в Интернете.

LoadBalancer — это сервис, который предоставляет (как вы уже догадались) балансировщик нагрузки для нашего приложения в поддерживаемых облачных провайдерах. Служба становится доступной через предоставленную службу балансировки нагрузки. Большинство облачных провайдеров, таких как AWS, GCP, Azure, предлагают эту функциональность. После создания службы типа LoadBalancer поставщики облачных услуг создадут балансировщик нагрузки в серверной части и сгенерируют общедоступный IP-адрес. Этот общедоступный IP-адрес можно использовать для доступа к нашему веб-приложению из общедоступного Интернета.

Это стандартный способ прямого доступа к службе через Интернет. Это похоже на NodePort, где весь трафик на указанном нами порту будет перенаправлен в службу. Почти все виды трафика, такие как HTTP, TCP, UDP, веб-сокеты, gRPC и т. д., могут быть отправлены на этот сервис.

Давайте посмотрим на пример файла определения:

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: myapp
type: LoadBalancer
ports:
– nodePort: 31000
port: 80
targetPort: 9376

Мы видим, что это почти то же самое, что и файл определения NodePort.

Давайте создадим сервис с помощью команды create

kubectl create -f load-balancer-service-definition.yml

Теперь давайте посмотрим на службу, созданную с помощью команды.

kubectl получить услуги

Вы можете видеть, что, поскольку я использую Minikube, значение внешнего IP-адреса отображается как <ожидание>. Однако в реальной облачной настройке IP-адрес будет сгенерирован и может использоваться для доступа к приложению. Это IP-адрес, который наши пользователи могут использовать для доступа к нашему веб-приложению из Интернета.</pending>

Входящая сеть

В разделах о службах Kubernetes мы видели, как представить наше приложение внешнему миру с помощью NodePort и LoadBalancer. Если нам нужен только один служебный порт, мы можем использовать NodePort. В случае нескольких экземпляров одной и той же службы мы должны использовать LoadBalancer.

Но что, если нам нужно добавить еще один сервис к нашему узлу и получить к нему доступ с другого URL-адреса. В этом случае нам придется добавить еще один балансировщик нагрузки в наш кластер. Это означает, что каждая служба, представленная с помощью LoadBalancer, получит свой собственный IP-адрес, и нам придется платить за каждый из этих балансировщиков нагрузки, что может быть довольно дорого.

Ingress используется, когда у нас есть несколько служб в нашем кластере, и мы хотим, чтобы запрос пользователя направлялся в службу на основе их пути. Рассмотрим пример, у меня есть два сервиса foo и bar в нашем кластере. Когда мы  набираем www.example.com/foo , мы должны быть перенаправлены на сервис foo, а  www.example.com/bar — на сервис bar. Эти маршруты будут выполняться Ingress. В отличие от NodePort или LoadBalancer, Ingress на самом деле не является типом службы. Вместо этого это точка входа, которая находится перед несколькими службами в кластере. Его можно определить как набор правил маршрутизации, которые определяют, как внешние пользователи получают доступ к службам, работающим внутри кластера Kubernetes.

Ingress наиболее полезен, если вы хотите предоставить несколько служб под одним и тем же IP-адресом, и все эти службы используют один и тот же протокол L7 (обычно HTTP). Вы платите только за один балансировщик нагрузки, если используете встроенную интеграцию GCP, а поскольку Ingress «умный», вы можете получить множество функций из коробки (например, SSL, аутентификация, маршрутизация и т. д.).

Ingress можно рассматривать как лучший способ предоставить несколько сервисов под одним и тем же IP-адресом. Кроме того, мы должны платить только за один балансировщик нагрузки.

Давайте посмотрим, как работает Ingress. Прежде чем мы реализуем Ingress, нам нужно развернуть поддерживаемый обратный прокси-сервер или решение для балансировки нагрузки, такое как Nginx, Haproxy или Trafik. Затем нам нужно указать набор правил для настройки Ingress. Развертываемое нами решение называется входным контроллером, а набор правил, который мы настраиваем, называется входными ресурсами. Входящие ресурсы создаются с использованием файлов определений, подобных тем, которые мы использовали для создания модулей и развертываний.

Входной контроллер

Контроллер Ingress по умолчанию не является частью кластера Kubernetes. Поэтому мы не можем просто создать ресурс Ingress и ожидать, что он будет работать. Для Ingress доступно несколько решений. Некоторыми из них являются GCE, который представляет собой уровни балансировщика нагрузки HTTP Nginx, Contour, Haproxy Traefik и Istio. Из этого GCE и Nginx в настоящее время поддерживаются и обслуживаются проектом Kubernetes.

Ingress Controller — это не просто еще один балансировщик нагрузки или обратный прокси-сервис. У них есть дополнительные компоненты, которые отслеживают кластер Kubernetes на наличие новых определений и ресурсов Ingress и соответствующим образом настраивают службу.

В качестве примера мы рассмотрим Nginx. Контроллеры Nginx можно развернуть так же, как и другое развертывание в Kubernetes. Вот пример файла определения:apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
spec:
replicas: 1
selector:
matchLabels:
name: nginx-ingress
template:
metadata:
labels:
name: nginx-ingress
spec:
containers:
– name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
args:
– /nginx-ingress-controller
env:
– name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
– name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
– name: http
containerPort: 80
– name: https
containerPort: 443

Мы видим, что наше развертывание называется nginx-ingress-controller и имеет одну реплику. Он содержит определение пода, помеченное как nginx-ingress. В спецификации мы видим, что используем специальную сборку Nginx, специально созданную для использования в качестве входного контроллера. Это изображение имеет свой собственный набор требований. Первый аргумент — это расположение программы Nginx. Затем мы должны передать карту конфигурации, в которой хранятся конфигурации Nginx, такие как порог активности, настройки SSL и т. д.

Образец определения карты конфигурации:kind:

ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
data:
map-hash-bucket-size: “128”
ssl-protocols: SSLv2

Мы также передали переменные среды, содержащие имя модуля и пространство имен, в котором он развернут. Службе требуются эти значения для чтения данных конфигурации модуля.

Наконец, мы указали порты, используемые входным контроллером 80 и 443.

Далее давайте представим контроллер внешнему миру с помощью сервиса. Создадим сервис вида NodePort:apiVersion:

v1
kind: Service
metadata:
name: myapp-service
spec:
type: NodePort
selector:
name: nginx-ingress
ports:
– targetPort: 80
port: 80
protocol: tcp
name: http
– targetPort: 443
port: 443
protocol: tcp
name: http

Теперь, когда наш контроллер готов, давайте рассмотрим правила, необходимые для настройки Ingress.

Входной ресурс

Ресурс Ingress — это набор правил и конфигураций, применяемых к контроллеру Ingress. В правилах можно указать перенаправление всего входящего трафика в одно приложение или маршрутизацию трафика в разные приложения. Итак, в нашем примере, когда пользователь нажимает URL-адрес «foo», затем перенаправляет их в приложение foo или, если пользователь нажимает URL-адрес «bar», затем перенаправляет их в приложение bar. Точно так же запрос должен быть перенаправлен на основе доменного имени.

Мы можем создать ресурс Ingress с файлом определения Kubernetes. Давайте посмотрим на некоторые примеры.

Во-первых, давайте создадим ресурс для маршрутизации входящего запроса на основе пути:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-foo-bar
spec:
rules:
– http:
paths:
– path: /foo
backend:
serviceName: foo-service
servicePort: 80
– path: /bar
backend:
serviceName: bar-service
servicePort: 80

Мы указали объект вида Ingress с именем ingress-foo-bar. И в спецификации мы определили два правила. Первое правило будет проверять, относится ли наш URL-адрес запроса к типу foo или типу bar и обслуживает foo-service или bar в соответствии с пользователем. Внутренняя часть правила должна содержать имя службы и порт службы.

Теперь давайте рассмотрим пример, где маршрутизация основана на доменном имени:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-foo-bar
spec:
rules:
– host: foo.example.com
http:
paths:
– backend:
serviceName: foo-service
servicePort: 80
– host: bar.example.com
http:
paths:
– backend:
serviceName: bar-service
servicePort: 80

Здесь мы видим, что вместо путей в правиле у нас есть хосты, которые являются доменными именами. Кроме того, мы видим, что разделение трафика по URL имеет только одно правило с двумя спецификациями пути, а разделение трафика по имени хоста имеет два правила с одним путем в каждом правиле.

Если пользователи вводят URL-адрес, не указанный в правиле, они будут перенаправлены на URL-адрес по умолчанию, который можно настроить.

Подведение итогов

В этой статье мы рассмотрели различные сервисы Kubernetes и Ingress на очень простых примерах. Следует отметить, что в реальной жизни варианты использования могут быть более сложными, и сервисные решения следует выбирать на основе требований и сложности приложений. Надеюсь, эта статья была вам полезна. Ознакомьтесь с официальной документацией, чтобы найти больше примеров и сценариев.

Перейти к верхней панели