Pod эфемерны — создаются, уничтожаются и пересоздаются контроллерами. Каждый новый Pod получает новый IP. Обращаться к Pod по IP напрямую ненадёжно. Service решает эту проблему: даёт стабильный ClusterIP и DNS-имя, которые живут независимо от Pod.
[source: kubernetes.io/docs/concepts/services-networking/service/]
Что такое Service
Service — абстракция, которая предоставляет постоянную точку доступа к набору Pod. Service определяет набор Pod через Label Selector и обеспечивает балансировку нагрузки между ними.
Service получает:
- Постоянный ClusterIP (не меняется за всё время жизни Service)
- DNS-имя:
<name>.<namespace>.svc.cluster.local
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- port: 80 # порт Service
targetPort: 8080 # порт контейнера
protocol: TCP
port — порт, на котором Service принимает трафик. targetPort — порт контейнера, куда трафик перенаправляется. Если targetPort не указан, используется значение port.
Типы Service
ClusterIP (по умолчанию)
Доступен только внутри кластера. Подходит для внутренней коммуникации между сервисами.
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP
selector:
app: backend
ports:
- port: 80
targetPort: 8080
NodePort
Открывает порт на каждом узле кластера. Трафик на <NodeIP>:<NodePort> маршрутизируется к Service. Диапазон портов: 30000–32767.
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # необязательно — назначится автоматически
NodePort автоматически создаёт ClusterIP. Маршрут: клиент → NodeIP:30080 → ClusterIP:80 → Pod:8080.
LoadBalancer
Создаёт внешний балансировщик нагрузки у облачного провайдера (AWS, GCP, Azure). Автоматически создаёт NodePort и ClusterIP.
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080
После создания в status.loadBalancer.ingress появится внешний IP или hostname:
kubectl get svc web-service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# web-service LoadBalancer 10.96.12.34 203.0.113.50 80:31234/TCP 5m
На bare-metal кластерах LoadBalancer без cloud-провайдера зависнет в <pending>. Решение — MetalLB или аналоги.
ExternalName
Маппинг Service на внешнее DNS-имя (CNAME). Не создаёт прокси и ClusterIP.
apiVersion: v1
kind: Service
metadata:
name: external-api
spec:
type: ExternalName
externalName: api.external-provider.com
Обращение к external-api.default.svc.cluster.local вернёт CNAME на api.external-provider.com. Используется для интеграции с внешними сервисами без хардкода URL в Pod.
Сводка типов
Service без Selector
Используется для интеграции с внешними сервисами или ресурсами вне кластера. Endpoints задаются вручную через EndpointSlice (stable с Kubernetes 1.21; legacy Endpoints deprecated с 1.33, но ещё доступен для совместимости).
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
ports:
- port: 5432
targetPort: 5432
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: external-db-1
labels:
kubernetes.io/service-name: external-db
addressType: IPv4
ports:
- port: 5432
endpoints:
- addresses: ["10.0.0.100"]
- addresses: ["10.0.0.101"]
Несколько портов
При объявлении нескольких портов каждый должен иметь имя:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- name: http
port: 80
targetPort: 8080
- name: metrics
port: 9090
targetPort: 9090
DNS
[source: kubernetes.io/docs/concepts/services-networking/dns-pod-service/]
Каждый Service получает DNS-запись:
<service-name>.<namespace>.svc.cluster.local
# Внутри того же namespace — достаточно имени
curl http://backend-service
curl http://backend-service:8080
# Из другого namespace
curl http://backend-service.production.svc.cluster.local
# Краткая форма (без .svc.cluster.local) тоже работает
curl http://backend-service.production
DNS в кластере обеспечивает CoreDNS — он работает как Deployment в kube-system и обслуживает DNS-запросы Pod.
Роль kube-proxy
kube-proxy работает на каждом узле и реализует маршрутизацию трафика к Pod. При создании или изменении Service kube-proxy обновляет правила:
- iptables — по умолчанию, правила NAT для перенаправления трафика
- nftables — stable с Kubernetes 1.33, современная Linux-замена для iptables/IPVS режимов
- IPVS — исторический high-performance режим; deprecated в Kubernetes 1.35
[source: kubernetes.io/docs/reference/networking/virtual-ips/]
При замене kube-proxy (например, Cilium eBPF mode) Service API остаётся, меняется только реализация маршрутизации.
Основные команды
# Создать Service императивно
kubectl expose deployment nginx --port=80 --target-port=8080 --type=ClusterIP
kubectl expose deployment nginx --port=80 --type=NodePort
# Список Service
kubectl get services
kubectl get svc
kubectl get svc -o wide
# Подробности
kubectl describe svc my-service
# EndpointSlice (какие Pod стоят за Service) — stable API
kubectl get endpointslices -l kubernetes.io/service-name=my-service
# Legacy Endpoints (deprecated с 1.33, но всё ещё работает)
kubectl get endpoints my-service
# Создать из манифеста
kubectl apply -f service.yaml
Headless Service
clusterIP: None — не балансирует трафик, даёт DNS-записи для каждого Pod напрямую. Используется StatefulSet (глава 11).
Типичные ошибки
Service создан, но EndpointSlice пуст. kubectl get endpointslices -l kubernetes.io/service-name=my-service пуст. Причина — selector не совпадает ни с одним Pod. Проверить: kubectl get pods --show-labels и сравнить с spec.selector Service.
Путаница port и targetPort. port — порт Service, куда стучится клиент. targetPort — порт контейнера. Ошибка: поставить одинаковые значения, а приложение слушает на другом порту.
LoadBalancer на bare-metal без MetalLB. Без cloud-провайдера или MetalLB EXTERNAL-IP зависнет в <pending>. Service работает через NodePort, но внешний IP не появится.
Использовать NodePort с фиксированным портом в production. Хардкод nodePort: 30080 конфликтует если порт уже занят другим Service. Лучше дать Kubernetes назначить порт автоматически.
Обращаться к Pod по IP напрямую. IP Pod меняется при перезапуске. Всегда ходить через Service.
Альтернативы
Ingress — L7-маршрутизация по hostname и пути для HTTP/HTTPS через единую точку входа. Поверх Service ClusterIP (глава 07).
Gateway API — современная замена Ingress с более богатой моделью маршрутизации (глава 07).
Service Mesh (Istio, Linkerd) — управление трафиком между сервисами с mTLS, circuit breaking, observability. Работает поверх Service, не заменяет его.