K8s Q A Services: различия между версиями
Sirmax (обсуждение | вклад) |
Sirmax (обсуждение | вклад) |
||
(не показано 7 промежуточных версий этого же участника) | |||
Строка 94: | Строка 94: | ||
==Типы Сервисов== |
==Типы Сервисов== |
||
(ТУТ НАДО ПОДРОБНЕЕ!!!!!!!) |
(ТУТ НАДО ПОДРОБНЕЕ!!!!!!!) |
||
+ | ===ClusterIP=== |
||
− | + | предоставляет доступ к сервису на внутреннем IP-адресе кластера (сервис доступен только внутри кластера). Тип ClusterIP используется по умолчанию; |
|
⚫ | |||
+ | <BR> |
||
⚫ | |||
+ | Под капотом для каждого такого сервиса создаются правила в iptables которые и перенаправляют трафик - например (случайный сервис с живого кластера) |
||
⚫ | |||
+ | |||
+ | <PRE> |
||
+ | kubectl get svc -A -o wide --show-labels | grep tungstenfabric-operator-metrics |
||
+ | NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR LABELS |
||
+ | tf tungstenfabric-operator-metrics ClusterIP 10.232.100.45 <none> 8383/TCP 222d name=tungstenfabric-operator app.kubernetes.io/managed-by=Helm |
||
+ | </PRE> |
||
+ | Тут видно что |
||
+ | * ClusterIP 10.232.100.45 |
||
+ | |||
+ | |||
+ | <PRE> |
||
+ | kubectl -n tf get endpoints tungstenfabric-operator-metrics |
||
+ | NAME ENDPOINTS AGE |
||
+ | tungstenfabric-operator-metrics 192.168.107.224:8383 222d |
||
+ | </PRE> |
||
+ | * 192.168.107.224 - адрес единственного POD |
||
+ | |||
+ | <PRE> |
||
+ | kubectl get pod -A -o wide | grep 192.168.107.224 |
||
+ | tf tungstenfabric-operator-66b776cc76-c5xvt 2/2 Running 2 (24d ago) 37d 192.168.107.224 k8s-control-1 <none> <none> |
||
+ | </PRE> |
||
+ | Этот под запущен на ноде <code>k8s-control-1</code> |
||
+ | <BR> |
||
+ | На любой произвольной ноде можно видеть (iptables-save показывает правила из всех таблиц, в том числе nat, а не только filter) |
||
+ | <PRE> |
||
+ | iptables-save | grep 10.232.100.45 |
||
+ | -A KUBE-SERVICES -d 10.232.100.45/32 -p tcp -m comment --comment "tf/tungstenfabric-operator-metrics cluster IP" -m tcp --dport 8383 -j KUBE-SVC-I663JZWESJMFGRNU |
||
+ | -A KUBE-SVC-I663JZWESJMFGRNU ! -s 192.168.104.0/21 -d 10.232.100.45/32 -p tcp -m comment --comment "tf/tungstenfabric-operator-metrics cluster IP" -m tcp --dport 8383 -j KUBE-MARK-MASQ |
||
+ | </PRE> |
||
+ | * -j KUBE-SVC-I663JZWESJMFGRNU - трафик перенаправлен в эту цепочку (-j ==jump) |
||
+ | <PRE> |
||
+ | iptables -t nat -nL KUBE-SVC-I663JZWESJMFGRNU -xv | column -t |
||
+ | Chain KUBE-SVC-I663JZWESJMFGRNU (1 references) |
||
+ | pkts bytes target prot opt in out source destination |
||
+ | 2 120 KUBE-MARK-MASQ tcp -- * * !192.168.104.0/21 10.232.100.45 /* tf/tungstenfabric-operator-metrics cluster IP */ tcp dpt:8383 |
||
+ | 2 120 KUBE-SEP-NGQNQI7MJHBZTPB6 all -- * * 0.0.0.0/0 0.0.0.0/0 /* tf/tungstenfabric-operator-metrics -> 192.168.107.224:8383 */ |
||
+ | </PRE> |
||
+ | (про маркировку пакетов - ctmark = (ctmark AND NOT mask) XOR value ) |
||
+ | https://itecnote.com/tecnote/linux-some-questions-about-set-xmark-in-iptables/ |
||
+ | <BR> |
||
+ | Проверить цепочку KUBE-SEP-NGQNQI7MJHBZTPB6 |
||
+ | <PRE> |
||
+ | iptables -t nat -nL KUBE-SEP-NGQNQI7MJHBZTPB6 -xv | column -t |
||
+ | Chain KUBE-SEP-NGQNQI7MJHBZTPB6 (1 references) |
||
+ | pkts bytes target prot opt in out source destination |
||
+ | 0 0 KUBE-MARK-MASQ all -- * * 192.168.107.224 0.0.0.0/0 /* tf/tungstenfabric-operator-metrics */ |
||
+ | 1 60 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* tf/tungstenfabric-operator-metrics */ tcp to:192.168.107.224:8383 |
||
+ | </PRE> |
||
+ | В конце видим DNAT на адрес пода 192.168.107.224 |
||
+ | |||
+ | ===NodePort=== |
||
⚫ | предоставляет доступ к сервису на IP-адресе каждого узла (ноды) кластера, на статическом порту (из диапазона 30000-32767). Автоматически создастся и сервис типа ClusterIP, на который будут маршрутизироваться запросы с NodePort. Взаимодействовать с сервисом можно также из-за пределов кластера, используя в качестве адреса <NodeIP>:<NodePort>; |
||
+ | ===LoadBalancer=== |
||
+ | предоставляет доступ к сервису используя балансировщик (load balancer) облачного провайдера. |
||
⚫ | |||
+ | |||
+ | ===ExternalName=== |
||
⚫ | |||
+ | (А через какой резолвер идет резолв?????) |
||
+ | ===Headless=== |
||
+ | Headless-сервис: это сервис, который не использует отдельный IP-адрес для маршрутизации запросов (ClusterIP: None). В этом случае под DNS-именем сервиса видны IP всех Pod, которые в этот сервис входят. |
||
+ | <BR> |
||
+ | Headless-сервисы полезны, когда приложение само должно управлять тем, к какому Pod подключаться, например: |
||
+ | * mongodb-клиент использует IP сервера, с которым он работает, для того, чтобы запросы для одного курсора шли на один хост (курсор «живёт» на mongos). В случае использования ClusterIP могут «теряться» курсоры даже для коротких запросов. |
||
+ | * gRPC-клиенты держат по одному соединению с сервисами и сами управляют запросами, мультиплексируя запросы к одному серверу. В случае использования ClusterIP клиент может создать одно подключение и нагружать ровно один Pod сервера. |
||
+ | |||
+ | О проблемах можно читать тут: https://habr.com/ru/companies/joom/articles/563310/ |
||
=Service и Endpoint - чуть подробнее= |
=Service и Endpoint - чуть подробнее= |
||
Еще одна интересная заметка которую я утащил сюда |
Еще одна интересная заметка которую я утащил сюда |
||
https://medium.com/@zzeett694/kubernetes-endpoints-9e3ee398c896 |
https://medium.com/@zzeett694/kubernetes-endpoints-9e3ee398c896 |
||
− | ==Как взаимодействуют сервисы и поды=== |
+ | ===Как взаимодействуют сервисы и поды=== |
Сервисы(services) кубрнетаса не взаимодействуют с Pod напрямую, помимо самого сервиса кубернетес создает так же абстракцию |
Сервисы(services) кубрнетаса не взаимодействуют с Pod напрямую, помимо самого сервиса кубернетес создает так же абстракцию |
||
Endpoints которая является промежуточной в цепочке взаимодействия. Цепочка событий выглядит так: |
Endpoints которая является промежуточной в цепочке взаимодействия. Цепочка событий выглядит так: |
Текущая версия на 18:50, 8 января 2024
Services
Общая информация
Поды в кластере Kubernetes смертны - они создаются (рождаются), но когда под по какой-либо причине умирает, то он не воскресает. И хотя каждый под при создании получает свой собственный IP-адрес, этот адрес нельзя назвать постоянным и стабильным вследствие “смертности” подов.
К примеру, ReplicaSet может динамически изменять количество подов в кластере (путем масштабирования), при этом новые поды могут быть запущены на других узлах (нодах) кластера - в этом случае IP-адрес пода наверняка изменится.
Это порождает проблему: если некий набор подов (назовем их backends) предоставляют функционал другому набору подов (назовем их frontends) внутри кластера Kubernetes, то как frontend-поды узнают адреса backend-подов и взаимодействуют с ними?
Сервис в Kubernetes - это абстракция, определяющая логический набор подов и политику доступа к ним (иногда такой набор подов еще называют микросервисом). Как правило, этот набор подов определяется на основе меток (присваиваются в момент создания подов) и селекторов.
Например, backend - это 3 реплики подов, занимающихся обработкой изображений. Все три пода абсолютно идентичны с функциональной точки зрения (так как это реплики), поэтому frontend не должен беспокоиться на какой именно backend-под попадет запрос - это работа сервиса.
Простейший пример сервиса может выглядеть так:
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 (например, если вы только переносите свою инфраструктуру). (КАК?)
В любом из предложенных вариантов следует создать сервис без селекторов, например:
kind: Service apiVersion: v1 metadata: name: my-service spec: ports: - protocol: TCP port: 80 targetPort: 9376
и, так как селекторов нет, соотвествующий объект Endpoints не создастся автоматически. Его необходимо создать вручную, тем самым указав связь между сервисом и конечной точкой (бекендом), например так:
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.
Для многих сервисов обычным делом является использование (открытие) нескольких портов. В таком случае, все что необходимо - присвоить имена всем портам, например:
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 используется по умолчанию;
Под капотом для каждого такого сервиса создаются правила в iptables которые и перенаправляют трафик - например (случайный сервис с живого кластера)
kubectl get svc -A -o wide --show-labels | grep tungstenfabric-operator-metrics NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR LABELS tf tungstenfabric-operator-metrics ClusterIP 10.232.100.45 <none> 8383/TCP 222d name=tungstenfabric-operator app.kubernetes.io/managed-by=Helm
Тут видно что
- ClusterIP 10.232.100.45
kubectl -n tf get endpoints tungstenfabric-operator-metrics NAME ENDPOINTS AGE tungstenfabric-operator-metrics 192.168.107.224:8383 222d
- 192.168.107.224 - адрес единственного POD
kubectl get pod -A -o wide | grep 192.168.107.224 tf tungstenfabric-operator-66b776cc76-c5xvt 2/2 Running 2 (24d ago) 37d 192.168.107.224 k8s-control-1 <none> <none>
Этот под запущен на ноде k8s-control-1
На любой произвольной ноде можно видеть (iptables-save показывает правила из всех таблиц, в том числе nat, а не только filter)
iptables-save | grep 10.232.100.45 -A KUBE-SERVICES -d 10.232.100.45/32 -p tcp -m comment --comment "tf/tungstenfabric-operator-metrics cluster IP" -m tcp --dport 8383 -j KUBE-SVC-I663JZWESJMFGRNU -A KUBE-SVC-I663JZWESJMFGRNU ! -s 192.168.104.0/21 -d 10.232.100.45/32 -p tcp -m comment --comment "tf/tungstenfabric-operator-metrics cluster IP" -m tcp --dport 8383 -j KUBE-MARK-MASQ
- -j KUBE-SVC-I663JZWESJMFGRNU - трафик перенаправлен в эту цепочку (-j ==jump)
iptables -t nat -nL KUBE-SVC-I663JZWESJMFGRNU -xv | column -t Chain KUBE-SVC-I663JZWESJMFGRNU (1 references) pkts bytes target prot opt in out source destination 2 120 KUBE-MARK-MASQ tcp -- * * !192.168.104.0/21 10.232.100.45 /* tf/tungstenfabric-operator-metrics cluster IP */ tcp dpt:8383 2 120 KUBE-SEP-NGQNQI7MJHBZTPB6 all -- * * 0.0.0.0/0 0.0.0.0/0 /* tf/tungstenfabric-operator-metrics -> 192.168.107.224:8383 */
(про маркировку пакетов - ctmark = (ctmark AND NOT mask) XOR value )
https://itecnote.com/tecnote/linux-some-questions-about-set-xmark-in-iptables/
Проверить цепочку KUBE-SEP-NGQNQI7MJHBZTPB6
iptables -t nat -nL KUBE-SEP-NGQNQI7MJHBZTPB6 -xv | column -t Chain KUBE-SEP-NGQNQI7MJHBZTPB6 (1 references) pkts bytes target prot opt in out source destination 0 0 KUBE-MARK-MASQ all -- * * 192.168.107.224 0.0.0.0/0 /* tf/tungstenfabric-operator-metrics */ 1 60 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* tf/tungstenfabric-operator-metrics */ tcp to:192.168.107.224:8383
В конце видим DNAT на адрес пода 192.168.107.224
NodePort
предоставляет доступ к сервису на IP-адресе каждого узла (ноды) кластера, на статическом порту (из диапазона 30000-32767). Автоматически создастся и сервис типа ClusterIP, на который будут маршрутизироваться запросы с NodePort. Взаимодействовать с сервисом можно также из-за пределов кластера, используя в качестве адреса <NodeIP>:<NodePort>;
LoadBalancer
предоставляет доступ к сервису используя балансировщик (load balancer) облачного провайдера. При этом автоматически создаются сервисы типа NodePort и ClusterIP, на которые будут маршрутизироваться запросы с балансировщика;
ExternalName
особый случай - сопоставляет имя сервиса с содержимым поля externalName (например, foo.bar.example.com), возвращая CNAME запись. Никакого проксирования не происходит. (А через какой резолвер идет резолв?????)
Headless
Headless-сервис: это сервис, который не использует отдельный IP-адрес для маршрутизации запросов (ClusterIP: None). В этом случае под DNS-именем сервиса видны IP всех Pod, которые в этот сервис входят.
Headless-сервисы полезны, когда приложение само должно управлять тем, к какому Pod подключаться, например:
- mongodb-клиент использует IP сервера, с которым он работает, для того, чтобы запросы для одного курсора шли на один хост (курсор «живёт» на mongos). В случае использования ClusterIP могут «теряться» курсоры даже для коротких запросов.
- gRPC-клиенты держат по одному соединению с сервисами и сами управляют запросами, мультиплексируя запросы к одному серверу. В случае использования ClusterIP клиент может создать одно подключение и нагружать ровно один Pod сервера.
О проблемах можно читать тут: https://habr.com/ru/companies/joom/articles/563310/
Service и Endpoint - чуть подробнее
Еще одна интересная заметка которую я утащил сюда https://medium.com/@zzeett694/kubernetes-endpoints-9e3ee398c896
Как взаимодействуют сервисы и поды
Сервисы(services) кубрнетаса не взаимодействуют с Pod напрямую, помимо самого сервиса кубернетес создает так же абстракцию Endpoints которая является промежуточной в цепочке взаимодействия. Цепочка событий выглядит так:
Service -> Endpoints -> Pod
Для наглядности создадим один pod с простым web сервером возвращающим hostname pod-a и сервис с типом ClusterIP который с ним взаимодействует: Создание Deployment и сервиса для общения с ним
--- apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: simple-web-app image: zet694/simple-web-app resources: limits: memory: "128Mi" cpu: "500m" ports: - name: http containerPort: 8888 --- apiVersion: v1 kind: Service metadata: name: mysvc spec: selector: app: myapp ports: - port: 80 targetPort: http
Исследуем наш сервис:
$ kubectl describe svc mysvc Name: mysvc Namespace: default Labels: <none> Annotations: <none> Selector: app=myapp Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.101.248.27 IPs: 10.101.248.27 LoadBalancer Ingress: localhost Port: <unset> 80/TCP TargetPort: http/TCP Endpoints: 10.1.1.99:8888 Session Affinity: None Events: <none>
Значение поля endpoints содержит внутренний ip нашего единственного Pod который был выбран через селектор указанный в сервисе. Проверим это путем масштабирования количества Pod-ов до 3 экземпляров для этого используем команду:
$ kubectl scale deployment myapp --replicas=3 deployment.apps/myapp scaled
Теперь убедимся что колличество endpoints изменилось до 3 IP адрессов (ровно столько сколко Pod содержат label соотвествующий указаному в селекторе нашего сервиса):
$ kubectl describe svc mysvc Name: mysvc Namespace: default Labels: <none> Annotations: <none> Selector: app=myapp Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.101.248.27 IPs: 10.101.248.27 LoadBalancer Ingress: localhost Port: <unset> 80/TCP TargetPort: http/TCP Endpoints: 10.1.1.102:8888,10.1.1.103:8888,10.1.1.99:8888 Session Affinity: None Events: <none>
Видим что количество Endpoints соответствует количеству Pod-ов, теперь убедимся что это действительно IP наших Pod-ов:
Проверяем внутренний IP наших Pod-ов
Вывод опущен - лень копировать картинку
Из вывода понятно что IP адреса Pod-ов совпадают с теми что указаны в Endpoints.Ресурс Endpoints является списком IP адрессов и портов предоставляющий доступ к службе, взаимодействовать с ним можно посредствам уже знакомых нам команд:
$ kubectl get endpoints NAME ENDPOINTS AGE mysvc 10.1.1.102:8888,10.1.1.103:8888,10.1.1.99:8888 48m
Из всего сказанного можно сделать вывод что при создании сервиса так же создается endpoints который автоматически добавляет и удаляет IP-адреса “подов” по меткам указанным в родительском сервисе.
Service без Selector
В случае если вы создаете сервис без указания селектора(selector) по которому должны будут обнаружены нужные поды, абстракция endpoint не создается так как кубернетес не знает куда направлять ваши запросы из сервиса, вы должны сами создать endpoints указав перечень необходимых IP адресов.
Внимание! Имя сервиса и имя endpoints должны совпадать.
Создадим сервис без указания selectors:
apiVersion: v1 kind: Service metadata: name: mysvc-without-selector spec: ports: - port: 80 targetPort: http
Убедимся что сервис создан и не имеет endpoints:
$ kubectl describe svc mysvc-without-selector Name: mysvc-without-selector Namespace: default Labels: <none> Annotations: <none> Selector: <none> Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.110.204.181 IPs: 10.110.204.181 Port: <unset> 80/TCP TargetPort: http/TCP Endpoints: <none> Session Affinity: None Events: <none>
Теперь создадим endpoints с тем же именем что и наш сервис:
apiVersion: v1 kind: Endpoints metadata: name: mysvc-without-selector subsets: - addresses: - ip: 185.199.110.153 - ip: 77.88.55.80 ports: - port: 80
Правила создания Endpoint:
- Имя Endpoints и Service должны совпадать.
- Должен быть указан один или более IP.
- Должен быть указан порт.
Таким образом мы можем создать сервис с теми endpoints которые необходимы нам, например для взаимодействия с ресурсами который находятся за пределами кластера.
Все запросы будут балансировать между указанным списком IP.
Если вам необходимо использовать существующий сервис для взаимодействия с ресурсами за пределами кластера — удалите селектор и создайте endpoints с тем же именем.
Если точка назначения находится внутри вашего кластера, просто создайте селектор внутри службы и кубернетес будет автоматически определять IP. Главным преимуществом является то что адрес сервиса внутри приложения остается неизменным, но конечный пункт назначения вынесен на уровень инфрастуктуры.
Использование сервиса для внешних ресурсов
Указанный выше пример для использования сервиса как точку обмена с внешними сервисами имеющими DNS имя не рекомендуется использовать, существует более простой и понятный способ — ExternalName. Для этого достаточно создать сервис с типом ExternalName, указав в его поле DNS имя необходимого внешнего сервиса и порт. Вот небольшой пример:
Пример ExternalName сервиса
apiVersion: v1 kind: Service metadata: name: external-svc spec: type: ExternalName externalName: neverssl.com ports: - port: 80
Обрататите внимание что ExternalName сервис:
- Реализуются исключительно на DNS уровне (создается CNAME запись).
- Не имеет внутреннего IP.
- Подключение к внешней службе происходит в обход внутреннего прокси.
- CNAME указывает на полное имя, вместо IP адреса.