K8s Q A Services

Материал из noname.com.ua
Версия от 17:44, 8 января 2024; Sirmax (обсуждение | вклад) (Новая страница: «Категория:K8s Категория:K8s_Вопросы_И_Ответы =Services= Поды в кластере Kubernetes смертны - они...»)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к навигацииПерейти к поиску


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 используется по умолчанию;
  • NodePort: предоставляет доступ к сервису на IP-адресе каждого узла (ноды) кластера, на статическом порту (из диапазона 30000-32767). Автоматически создастся и сервис типа ClusterIP, на который будут маршрутизироваться запросы с NodePort. Взаимодействовать с сервисом можно также из-за пределов кластера, используя в качестве адреса <NodeIP>:<NodePort>;
  • LoadBalancer: предоставляет доступ к сервису используя балансировщик (load balancer) облачного провайдера. При этом автоматически создаются сервисы типа NodePort и ClusterIP, на которые будут маршрутизироваться запросы с балансировщика;
  • ExternalName: особый случай - сопоставляет имя сервиса с содержимым поля externalName (например, foo.bar.example.com), возвращая CNAME запись. Никакого проксирования не происходит.




Еще одна интересная заметка которую я утащил сюда 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-адреса “подов” по меткам указанным в родительском сервисе. В случае если вы создаете сервис без указания селектора(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 адреса.