Thank you for reading this post, don't forget to subscribe!
Поды в кластере Kubernetes
смертны - они создаются (рождаются), но когда под по какой-либо причине умирает, то он не воскресает. И хотя каждый под при создании получает свой собственный IP-адрес, этот адрес нельзя назвать постоянным и стабильным вследствие “смертности” подов.
К примеру, ReplicaSet
может динамически изменять количество подов в кластере (путем масштабирования), при этом новые поды могут быть запущены на других узлах (нодах) кластера - в этом случае IP-адрес пода наверняка изменится.
Это порождает проблему: если некий набор подов (назовем их backends) предоставляют функционал другому набору подов (назовем их frontends) внутри кластера Kubernetes
, то как frontend-поды узнают адреса backend-подов и взаимодействуют с ними?
Сервис в Kubernetes
- это абстракция, определяющая логический набор подов и политику доступа к ним (иногда такой набор подов еще называют микросервисом). Как правило, этот набор подов определяется на основе меток (присваиваются в момент создания подов) и селекторов.
Например, backend - это 3 реплики подов, занимающихся обработкой изображений. Все три пода абсолютно идентичны с функциональной точки зрения (так как это реплики), поэтому frontend не должен беспокоиться на какой именно backend-под попадет запрос - это работа сервиса.
Простейший пример сервиса может выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 |
kind: Service apiVersion: v1 metadata: name: my-service spec: selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376 |
Согласно данной спецификации, будет создан объект “сервис” (service
) с именем my-service
, перенаправляющий запросы на порт 9376 каждого пода, у которого присутствует метка app=MyApp
. Этому сервису также будет назначен отдельный IP-адрес.
Согласно указанному селектору, в кластере непрерывно будут проверяться поды на наличие метки app=MyApp
, а результат такой проверки будет публиковаться (с помощью POST
) в объект Endpoints
с таким же именем - my-service
.
Стоит отметить, что сервис может сопоставлять входящий порт (port:
) с любым портом назначения (targetPort:
). Если параметр targetPort
не указан, то по умолчанию будет использоваться тот же порт, что и в параметре port
. Еще одна интересная особенность - targetPort
может содержать строку с именем порта в подах (а сам номер порта, присвоенный этому имени, может быть разным для каждого пода).
Сервисы чаще всего используются для доступа к подам в кластере Kubernetes
, но их также можно использовать и для доступа к другим типам бэкендов, например:
- внешний кластер баз данных в production-окружении, локальная БД для тестового окружения;
- совершенно иной сервис в другом неймспейсе или кластере;
- часть бекендов, запущенных вне кластера
Kubernetes
(например, если вы только переносите свою инфраструктуру).
В любом из предложенных вариантов следует создать сервис без селекторов, например:
1 2 3 4 5 6 7 8 9 10 |
kind: Service apiVersion: v1 metadata: name: my-service spec: ports: - protocol: TCP port: 80 targetPort: 9376 |
и, так как селекторов нет, соотвествующий объект Endpoints
не создастся автоматически. Его необходимо создать вручную, тем самым указав связь между сервисом и конечной точкой (бекендом), например так:
1 2 3 4 5 6 7 8 9 10 |
kind: Endpoints apiVersion: v1 metadata: name: my-service subsets: - addresses: - ip: 1.2.3.4 ports: - port: 9376 |
Примечание. IP-адрес в данном случае не может находиться в диапазонах 127.0.0.0/8, 169.254.0.0/16 и 224.0.0.0/24.
Для многих сервисов обычным делом является использование (открытие) нескольких портов. В таком случае, все что необходимо - присвоить имена всем портам, например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
kind: Service apiVersion: v1 metadata: name: my-service spec: selector: app: MyApp ports: - name: http protocol: TCP port: 80 targetPort: 9376 - name: https protocol: TCP port: 443 targetPort: 9377 |
В Kubernetes
существует несколько вариантов предоставления доступа к сервисам, которые называются типы (ServiceTypes
):
ClusterIP
: предоставляет доступ к сервису на внутреннем IP-адресе кластера (сервис доступен только внутри кластера). ТипClusterIP
используется по умолчанию;NodePort
: предоставляет доступ к сервису на IP-адресе каждого узла (ноды) кластера, на статическом порту (из диапазона 30000-32767). Автоматически создастся и сервис типаClusterIP
, на который будут маршрутизироваться запросы сNodePort
. Взаимодействовать с сервисом можно также из-за пределов кластера, используя в качестве адреса<NodeIP>:<NodePort>
;LoadBalancer
: предоставляет доступ к сервису используя балансировщик (load balancer) облачного провайдера. При этом автоматически создаются сервисы типаNodePort
иClusterIP
, на которые будут маршрутизироваться запросы с балансировщика;ExternalName
: особый случай - сопоставляет имя сервиса с содержимым поляexternalName
(например, foo.bar.example.com), возвращая CNAME запись. Никакого проксирования не происходит.