Зарегистрируйтесь, чтобы продолжить обучение

Сервис и доступ к приложению Kubernetes

Сервис и доступ к приложению

Когда вы развёртываете приложение в Kubernetes, возникает важная проблема: каждый Pod получает динамический IP-адрес, который меняется при перезапуске. Более того, при использовании нескольких реплик нужен механизм балансировки нагрузки между ними. Service решает эти задачи, обеспечивая стабильную точку входа для доступа к приложениям.

Проблема динамических IP-адресов

Представьте, что у вас есть frontend-приложение, которое обращается к backend API. Backend развёрнут с помощью Deployment с тремя репликами. Каждый Pod имеет свой IP-адрес, например 10.244.1.5, 10.244.2.8, 10.244.1.12. Frontend должен знать эти адреса для отправки запросов.

Но что происходит, когда Pod перезапускается? Он получает новый IP-адрес. Если узел выходит из строя, все Pod на нём пересоздаются на других узлах с новыми IP. При масштабировании добавляются новые Pod с новыми адресами. Frontend должен постоянно отслеживать эти изменения и обновлять список адресов.

Service решает эту проблему, создавая стабильную точку входа с постоянным IP-адресом и DNS-именем. Frontend обращается к Service, а тот автоматически распределяет запросы между доступными Pod backend.

Что такое Service

Service — это абстракция, которая определяет логический набор Pod и политику доступа к ним. Service использует селекторы меток для выбора Pod, точно так же, как Deployment. Когда вы создаёте Service, Kubernetes автоматически настраивает балансировку нагрузки и DNS-запись.

Давайте создадим простой Service для Nginx Deployment из предыдущего урока:

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

В этом манифесте селектор app: nginx указывает, что Service должен направлять трафик на Pod с такой меткой. Секция ports определяет, что Service слушает порт 80 и направляет трафик на порт 80 в Pod. Параметр type: ClusterIP создаёт внутренний IP-адрес, доступный только внутри кластера.

Создайте Service и посмотрите на результат:

# Создать Deployment (если ещё не создан)
kubectl apply -f nginx-deployment.yaml

# Создать Service
kubectl apply -f nginx-service.yaml

# Посмотреть Service
kubectl get service

# Подробная информация
kubectl describe service nginx-service

Команда kubectl get service покажет внутренний IP-адрес Service (ClusterIP) и порты. Этот IP-адрес остаётся неизменным в течение всей жизни Service, даже если Pod перезапускаются или масштабируются.

Типы Service

Kubernetes предоставляет несколько типов Service для различных сценариев использования. Понимание их различий критически важно для правильной организации сетевого доступа.

ClusterIP

ClusterIP — это тип Service по умолчанию. Он создаёт внутренний IP-адрес, доступный только внутри кластера. Используйте ClusterIP для внутренней коммуникации между микросервисами:

apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
  type: ClusterIP

Frontend-приложение сможет обращаться к этому Service по имени backend-service или по IP-адресу ClusterIP. Но доступ извне кластера невозможен — ClusterIP виден только внутренним компонентам.

NodePort

NodePort открывает порт на всех узлах кластера и направляет трафик на Service. Это позволяет получить доступ к приложению извне, используя IP любого узла и указанный порт:

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30080
  type: NodePort

Kubernetes откроет порт 30080 на всех узлах. Обращение к <node-ip>:30080 будет перенаправлено на Pod с меткой app: web-app. Если не указать nodePort, Kubernetes выберет случайный порт из диапазона 30000-32767.

NodePort подходит для тестирования и простых сценариев, но имеет ограничения. Вы используете порты узлов напрямую, что может создать конфликты. Клиенты должны знать IP-адреса узлов, что неудобно в динамической среде. Для production-окружений лучше использовать LoadBalancer или Ingress.

LoadBalancer

LoadBalancer создаёт внешний балансировщик нагрузки, который распределяет трафик между узлами. Этот тип Service работает только в облачных окружениях, которые поддерживают создание балансировщиков (AWS, GCP, Azure):

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

Когда вы создаёте Service типа LoadBalancer, облачный провайдер автоматически создаёт внешний балансировщик нагрузки и назначает ему публичный IP-адрес. Трафик поступает на балансировщик, затем на NodePort на узлах, и наконец на Pod.

# Создать Service
kubectl apply -f web-service-lb.yaml

# Посмотреть внешний IP
kubectl get service web-service

Поле EXTERNAL-IP покажет публичный адрес балансировщика. Пока балансировщик создаётся, вы увидите <pending>. Через минуту-две появится реальный IP-адрес, по которому приложение будет доступно из интернета.

ExternalName

ExternalName — специальный тип Service, который создаёт DNS-запись типа CNAME, указывающую на внешний сервис:

apiVersion: v1
kind: Service
metadata:
  name: external-database
spec:
  type: ExternalName
  externalName: database.example.com

Когда приложение обращается к external-database, DNS вернёт database.example.com. Это полезно для интеграции внешних сервисов в кластер без изменения кода приложения.

DNS в Kubernetes

Kubernetes автоматически создаёт DNS-записи для всех Service. Это позволяет приложениям обращаться друг к другу по именам вместо IP-адресов, что делает конфигурацию более читаемой и устойчивой к изменениям.

Каждый Service получает DNS-имя в формате <service-name>.<namespace>.svc.cluster.local. Если Service и Pod находятся в одном namespace, можно использовать короткое имя:

# Полное имя
curl http://backend-service.default.svc.cluster.local:8080

# Короткое имя (в том же namespace)
curl http://backend-service:8080

Для демонстрации создайте временный Pod и проверьте DNS:

# Запустить временный Pod
kubectl run test-pod --image=curlimages/curl --rm -it --restart=Never -- sh

# Внутри Pod проверить DNS
nslookup nginx-service
curl http://nginx-service

DNS-сервер в Kubernetes (обычно CoreDNS) автоматически обновляет записи при создании, изменении или удалении Service. Это обеспечивает актуальность сервисной топологии без ручного вмешательства.

Endpoints и балансировка нагрузки

Когда вы создаёте Service, Kubernetes автоматически создаёт объект Endpoints, который содержит список IP-адресов Pod, соответствующих селектору Service:

# Посмотреть endpoints
kubectl get endpoints nginx-service

# Подробная информация
kubectl describe endpoints nginx-service

Service использует этот список для балансировки нагрузки. По умолчанию Kubernetes использует простой round-robin алгоритм — запросы распределяются последовательно между всеми доступными Pod. Это обеспечивает равномерное распределение нагрузки.

Если Pod становится неготовым (не проходит readinessProbe), его IP-адрес удаляется из Endpoints, и Service перестаёт направлять на него трафик. Когда Pod снова становится готовым, его адрес возвращается в список.

Session Affinity

Иногда нужно, чтобы все запросы от одного клиента обрабатывались одним Pod. Например, если приложение хранит сессию в памяти. Для этого используйте sessionAffinity:

apiVersion: v1
kind: Service
metadata:
  name: sticky-service
spec:
  selector:
    app: web-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

С параметром sessionAffinity: ClientIP все запросы с одного IP-адреса будут направляться на один и тот же Pod в течение указанного времени (по умолчанию 3 часа).

Headless Service

Иногда балансировка нагрузки не нужна, и вы хотите получить прямой доступ ко всем Pod. Для этого создайте headless Service, указав clusterIP: None:

apiVersion: v1
kind: Service
metadata:
  name: headless-service
spec:
  clusterIP: None
  selector:
    app: database
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432

Headless Service не получает ClusterIP. Вместо этого DNS-запрос возвращает IP-адреса всех Pod. Это полезно для stateful-приложений, таких как базы данных, где нужен прямой доступ к конкретным инстансам.

# DNS-запрос вернёт все IP-адреса Pod
nslookup headless-service

Доступ к приложению извне: Ingress

NodePort и LoadBalancer решают задачу внешнего доступа, но имеют недостатки. NodePort использует нестандартные порты, LoadBalancer требует отдельного балансировщика для каждого Service, что дорого. Ingress решает эти проблемы.

Ingress — это объект, который управляет внешним доступом к Service на основе HTTP/HTTPS правил. Он позволяет использовать один балансировщик для нескольких Service, поддерживает виртуальные хосты и маршрутизацию по путям:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: backend-service
                port:
                  number: 8080
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

Этот Ingress направляет запросы к example.com/api на backend-service, а все остальные запросы — на frontend-service. Для работы Ingress требуется Ingress Controller — компонент, который реализует правила маршрутизации.

Популярные Ingress Controller: NGINX Ingress Controller, Traefik, HAProxy. В облачных окружениях часто используются встроенные контроллеры (AWS ALB Ingress Controller, GCE Ingress Controller).

Установка NGINX Ingress Controller:

# Установить с помощью Helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx

# Или с помощью манифеста
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml

После установки контроллера создайте Ingress:

kubectl apply -f web-ingress.yaml

# Посмотреть Ingress
kubectl get ingress

# Подробная информация
kubectl describe ingress web-ingress

TLS/SSL терминация

Ingress поддерживает TLS-терминацию — он может обрабатывать HTTPS-трафик и перенаправлять его на Service по HTTP:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress-tls
spec:
  tls:
    - hosts:
        - example.com
      secretName: tls-secret
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

Сертификат и ключ должны быть сохранены в Secret:

# Создать Secret с сертификатом
kubectl create secret tls tls-secret \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem

Ingress Controller автоматически настроит TLS и будет принимать HTTPS-запросы на порту 443.

Практический пример: многоуровневое приложение

Давайте создадим полноценный пример с frontend, backend и database:

# Backend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: example/backend:1.0
          ports:
            - containerPort: 8080
---
# Backend Service
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
  type: ClusterIP
---
# Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: frontend
          image: example/frontend:1.0
          ports:
            - containerPort: 80
          env:
            - name: BACKEND_URL
              value: "http://backend-service:8080"
---
# Frontend Service
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP
---
# Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

В этом примере frontend обращается к backend через Service по имени backend-service. Ingress принимает внешний трафик и направляет его на frontend-service. Backend недоступен извне, так как использует ClusterIP.

Отладка сетевых проблем

При проблемах с сетевым доступом используйте следующие команды для диагностики:

# Проверить Service
kubectl get service
kubectl describe service <service-name>

# Проверить Endpoints
kubectl get endpoints <service-name>

# Проверить DNS изнутри Pod
kubectl run test-pod --image=curlimages/curl --rm -it --restart=Never -- sh
nslookup <service-name>

# Проверить доступность Service
kubectl run test-pod --image=curlimages/curl --rm -it --restart=Never -- curl http://<service-name>:<port>

# Посмотреть логи Ingress Controller
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

Частые проблемы: неправильный селектор в Service (не совпадает с метками Pod), Pod не готов (не проходит readinessProbe), порты не совпадают (port в Service не соответствует containerPort в Pod), Ingress Controller не установлен или работает некорректно.

Лучшие практики

При работе с Service следуйте рекомендациям для обеспечения надёжности и производительности. Используйте ClusterIP для внутренней коммуникации между микросервисами — это быстрее и безопаснее, чем внешний доступ.

Для внешнего доступа предпочитайте Ingress вместо LoadBalancer — это экономичнее и даёт больше контроля над маршрутизацией. LoadBalancer используйте только для не-HTTP протоколов или когда Ingress недоступен.

Добавляйте readinessProbe ко всем Pod. Это гарантирует, что Service направляет трафик только на готовые Pod, предотвращая ошибки при запуске или обновлении.

Используйте DNS-имена вместо IP-адресов в конфигурации приложений. IP-адреса могут меняться, DNS-имена остаются стабильными.

Настройте мониторинг Service и Endpoints. Отслеживайте количество доступных Pod и время отклика. Используйте Service Mesh (Istio, Linkerd) для продвинутых сценариев маршрутизации, мониторинга и безопасности.

Заключение

Service — это ключевой компонент сетевой модели Kubernetes, обеспечивающий стабильный доступ к Pod. ClusterIP для внутренней коммуникации, NodePort и LoadBalancer для внешнего доступа, Ingress для HTTP-маршрутизации — каждый тип Service решает свои задачи.

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

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff