Как Service находит поды:
Service ничего не знает про Deployment. Он отбирает поды по меткам через selector. Kubernetes следит за подходящими подами и ведёт список их адресов в объектах EndpointSlice, а kube-proxy на каждой ноде превращает этот список в правила iptables или IPVS. В итоге трафик на IP сервиса прозрачно уходит на один из живых подов.
Возьмём Deployment из главы 4 с меткой app: backend и контейнером, который слушает порт 8080. Сервис к нему:
Код: Выделить всё
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 8080
Применяем и проверяем:
Код: Выделить всё
kubectl apply -f backend-svc.yaml
kubectl get svc backend
kubectl get endpointslices -l kubernetes.io/service-name=backend
DNS вместо IP:
Запоминать ClusterIP не нужно. Внутренний DNS кластера (CoreDNS) даёт каждому сервису имя. Из того же namespace под обращается просто к backend, из другого к backend.default.svc.cluster.local. Проверить можно из временного пода:
Код: Выделить всё
kubectl run tmp --rm -it --image=busybox:1.36 -- sh
wget -qO- http://backend
NodePort и LoadBalancer, доступ снаружи:
ClusterIP снаружи кластера недоступен. NodePort открывает порт из диапазона 30000-32767 на каждой ноде:
Код: Выделить всё
apiVersion: v1
kind: Service
metadata:
name: backend-ext
spec:
type: NodePort
selector:
app: backend
ports:
- port: 80
targetPort: 8080
nodePort: 30080
Для локальной отладки чаще всего хватает port-forward, без всяких NodePort:
Код: Выделить всё
kubectl port-forward svc/backend 8080:80
Пустые endpoints. Селектор сервиса должен совпадать с метками в template Deployment, а не с метками самого объекта Deployment. Сравните вывод kubectl get pods --show-labels с selector сервиса.
Приложение слушает 127.0.0.1. Endpoints на месте, а соединение отваливается. Контейнер должен слушать 0.0.0.0, иначе трафик извне пода до него не дойдёт.
Перепутанные порты. port это вход в сервис, targetPort это порт контейнера, nodePort это порт на ноде. Если targetPort не указан, он равен port, и при несовпадении с портом приложения получите connection refused.
Долгоживущие соединения. Балансировка работает на уровне TCP-соединений, а не запросов. Один gRPC-канал или websocket прилипнет к одному поду на всё время жизни, сколько бы реплик ни было.
Что усвоили:
Service даёт стабильный адрес и DNS-имя поверх эфемерных подов, отбирает их по меткам, а тип (ClusterIP, NodePort, LoadBalancer) определяет, откуда к нему можно достучаться. Для нормального HTTP-трафика снаружи, с доменами и TLS, есть инструмент получше, чем NodePort, и это Ingress, до него дойдём в главе 7. А в следующей главе вынесем конфигурацию приложения в ConfigMap и Secret.