- Сервис и доступ к приложению
- Проблема динамических IP-адресов
- Что такое Service
- Типы Service
- DNS в Kubernetes
- Endpoints и балансировка нагрузки
- Session Affinity
- Headless Service
- Доступ к приложению извне: Ingress
- TLS/SSL терминация
- Практический пример: многоуровневое приложение
- Отладка сетевых проблем
- Лучшие практики
- Заключение
Сервис и доступ к приложению
Когда вы развёртываете приложение в 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-адресах. Балансировка нагрузки распределяет трафик между репликами автоматически.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.