← Back to notes

Services


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:30080ClusterIP:80Pod: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, не заменяет его.


05: ReplicaSet и Deployment

07: Ingress

Services | Aleksandr Suprun