Thank you for reading this post, don't forget to subscribe!
Для сетевого взаимодействия Kubernetes предоставляет четыре типа Service-ресурсов — ClusterIP
(дефолтный), NodePort
, LoadBalancer
и ExternalName
, плюс ресурс Ingress
.
Подготовка
Для тестов создадим деплоймент с подом, в котором запустим контейнер с NGINX, который будет принимать подключения на порт 80:
Т.к. Service будут искать поды по их лейблам — проверим, какие теги созданы для подов этого деплоймента:
Окей — тег app, значение nginx — запомним его.
kubectl port-forward
Что бы убедиться, что наш под принимает подключения на порт 80 — используем kubectl port-forward
. После того, как будем точно знать, что у самого пода с сетью всё в порядке — можно будет настраивать сеть со стороны Kubernetes.
Находим имя пода:
С локальной машины проверяем доступ к NGINX в Kubernetes:
Окей, теперь, когда у нас есть работающий под — посмотрим, как к нему можно получить доступ через Service объекты Kubernetes.
Типы Service — обзор
Рассмотрим типы кратко, а потом перейдём к примерам:
ClusterIP
: дефолтный тип, создаёт сервис с IP из пула внутренних адресов кластера, такой сервис будет доступен только внутри кластера (либо черезkube-proxy
)NodePort
: открывает TCP порт на каждой WorkerNode EС2, “за ним” автоматом создаётClusterIP
Service, и роутит трафик с порта ЕС2 на этотClusterIP
— такой сервис будет доступен из мира (если EC2 имеют публичные адреса), либо только внутри VPCLoadBalancer
: создаёт внешний Load Balancer (AWS Classic LB), “за ним” автоматом создаётNodePort
, за ним автоматомClusterIP
, и таким образом роутит трафик от Load Balancer к поду в кластереExternalName
: что-то вроде “DNS-прокси” — в ответ на обращение к такому сервису через CNAME вернёт значение, заданное вexternalName
ClusterIP
Самый простой тип, используется по-умолчанию.
Открывает доступ к приложению внутри кластера, без доступа из мира.
Можно использовать для, например, для доступа к системе кеширования, которая будет доступна другим подам в неймспейсе кластера.
Используем такой манифест:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 |
--- apiVersion: v1 kind: Service metadata: name: "nginx-service" namespace: "default" spec: ports: - port: 80 type: ClusterIP selector: app: "nginx" |
[/codesyntax]
Создаём сервис:
Проверяем:
kubectl proxy
и Service DNS
Так как при ClusterIP
сервис доступен только изнутри кластера — то для проверки его работы можно использовать kubectl proxy
, который пробросит локальный TCP-порт с рабочей машины к нашему API-серверу, а через него мы сможем обратиться к созданному сервису.
Запускаем прокси:
Зная имя сервиса — мы его указали в metadata: name
— можем обратиться к localhost:8080 и через имя неймспейса — к самому сервису:
Или просто получить информацию об этом сервисе:
Итак, ClusterIP:
- обеспечивает доступ к приложению в пределах кластера, но без доступа из мира
- получает IP из CIDR кластера, доступен через DNS-имя в пределах кластера, см. DNS for Services and Pods
NodePort
Теперь попробуем NodePort
.
При этом типе Kubernetes откроет TCP-порт на всех WorkerNodes, а затем через kube-proxy
, работающий на всех хостах кластера, будет проксировать запросы с этого TCP-порта к поду на этой ноде.
Обновляем манифест:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 |
--- apiVersion: v1 kind: Service metadata: name: "nginx-service" namespace: "default" spec: ports: - port: 80 nodePort: 30001 type: NodePort selector: app: "nginx" |
[/codesyntax]
Параметр nodePort
тут опционален, добавлен для примера. Если его не указать — Kubernetes выделит порт из диапазона 30000-32767.
Обновляем сервис:
Проверяем:
И проверяем порт на самом ЕС2-инстансе:
Очевидно, что если WorkerNodes живут в приватных сетях, и к ним нет доступа из мира — то вы не сможете использовать такой сервис для работы.
Но вы можете получить доступ к нашему NGINX из этой подсети, например с Bastion-хоста:
Итак, NodePort
:
- привязан к конкретному серверу, например ЕС2
- если сервер не доступен из мира — очевидно, что не обеспечит доступ к подам из мира
- получает IP из блока адресов, выделенных провайдером, например — VPC CIDR
- обеспечивает доступ к подам только на конкретной ноде
LoadBalancer
Наиболее часто используемый тип сервиса.
В случае с AWS — создаст AWS Load Balancer, по-умолчанию Classic, который будет проксировать запросы на ЕС2-инстансы TargetGroup, и на них, через NodePort
Service, в поды.
На таком Load Balancer вы можете использовать TLS, менять его тип — Internal/External, и т.д, см. Other ELB annotations.
Обновляем манифест сервиса:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 |
--- apiVersion: v1 kind: Service metadata: name: "nginx-service" namespace: "default" spec: ports: - port: 80 type: LoadBalancer selector: app: "nginx" |
[/codesyntax]
Применяем:
Ждём пару минут, пока обновится ДНС — и проверяем URL балансировщика:
Чего не позволяет такой тип сервиса — так это использовать какие-то правила роутинга, см. Application Load Balancer vs. Classic Load Balancer.
Собственно для того, что бы получить все возможности AWS Application Load Balancer — используем ещё один тип ресурса Kubernetes — Ingress
, о нём — в Ingress.
Итак, LoadBalancer:
- обеспечивает постоянный доступ к Сервису из мира
- обеспечивает балансировку запросов к подам на разных EC2
- даёт возможность использования TLS/SSL
- не поддерживает Level-7 роутинг
ExternalName
Ещё один тип сервиса — ExternalName
, который при обращении к нему перенаправит запрос на домен, указанный в параметре externalName
:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 |
--- apiVersion: v1 kind: Service metadata: name: "google-service" namespace: "default" spec: ports: - port: 80 type: ExternalName externalName: google.com |
[/codesyntax]
Создаём:
Проверяем сервис:
И проверим его работу — зайдём в наш под с NGINX, и выполним dig
:
Тут мы обращаемся к локальному DNS-имени сервиса google-service, которое разрезолвилось в IP домена google.com, которое мы указали в нашем externalName
.
Ingress
По сути, Ingress
не явлется отдельным сервисом — он просто описывает набор правил, по которым Ingress Controller выполняет действия по созданию Load Balancer, Listeners, и правила роутинга для них.
В случае с AWS это будет ALB Ingress Controller — см. ALB Ingress Controller on Amazon EKS и AWS Elastic Kubernetes Service: запуск ALB Ingress controller.
При этом для Ingress
требуется связанный с ним Service, на который Ingress
будет перенаправлять трафик — его backend.
Для ALB Ingress Controller манифест с Ingress
и связанный с ним Service будут выглядеть так:
[codesyntax lang="php"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
--- apiVersion: v1 kind: Service metadata: name: "nginx-service" namespace: "default" spec: ports: - port: 80 type: NodePort selector: app: "nginx" --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: "nginx-ingress" annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing labels: app: "nginx" spec: backend: serviceName: "nginx-service" servicePort: 80 |
[/codesyntax]
Тут мы создаём Service типа NodePort
, и Ingress
с типом ALB.
Kubernetes создаст Ingress
объект, его увидит alb-ingress-controller, запустит создание AWS ALB с правилами роутинга, описанными в spec
нашего Ingress
, создаст Service объект типа NodePort
, откроет на WorkeNodes TCP-порты, и начнёт редиректить трафик от клиентов — через балансировщик — на NodePort EC2 — через Service — к подам.
Проверим.
Сервис:
Ingress
:
И URL этого балансировщика:
Path-based routing
В примере выше мы роутим весь трафик с нашего ALB на единый Service и его поды.
Используя Ingress
и его правила — мы можем описать поведение определяющее на какой бекенд-Service перенаправлять трафик в зависимости от, например, URI запроса.
Запустим два NGINX: