04. Service, DNS, Ingress и NetworkPolicy
Незнакомый термин? Откройте словарь Kubernetes и терминов курса.
Назначение, предварительные знания и результаты обучения
Нужны главы 00–03, работающий python-api и понимание меток/селекторов. После
главы вы
сможете:
- проследить HTTP-пакет от клиента до процесса FastAPI;
- различать IP-адрес Pod, виртуальный IP Service, EndpointSlice и DNS-запись;
- выбрать
ClusterIP,NodePort,LoadBalancer, Service без ClusterIP илиExternalName; - доказать несовпадение селектора через EndpointSlice;
- объяснить CNI и плоскость данных Service без привязки к реализации;
- использовать Ingress и сравнить его с Gateway API;
- написать и проверить NetworkPolicy, не предполагая, что она применяется.
Ментальная модель: идентичность отделена от местоположения
Каждый Pod получает маршрутизируемый внутри кластера IP-адрес от дополнения
CNI (Container Network Interface) и его плоскости данных. Замена Pod меняет
местоположение. Service даёт стабильную виртуальную идентичность IP/DNS и
выбирает целевые Pods по меткам. Контроллер публикует готовые адреса в
EndpointSlices. Перенаправление Service может быть реализовано через iptables,
IPVS, eBPF или иначе; не стройте диагностику
только вокруг kube-proxy.
внешний клиент
→ граница/LB → контроллер Ingress или Gateway
→ Service python-api:80 (стабильный VIP/DNS)
→ EndpointSlice с готовыми PodIP:8000
→ сокет uvicorn → маршрут FastAPI
На каждом переходе свои доказательства и виды отказа:
| Уровень | Объект или доказательство |
|---|---|
| Имя | запрос CoreDNS, /etc/resolv.conf |
| Состав целевых ресурсов | селектор Service, адреса и conditions EndpointSlice |
| Политика | исходящий egress и входящий ingress NetworkPolicy |
| Маршрут и L7 | status Ingress/Gateway, журналы и конфигурация контроллера |
| Процесс | IP и порт Pod, прослушиваемый адрес, журналы и проверка приложения |
Полные манифесты
Service
apiVersion: v1
kind: Service
metadata:
name: python-api
namespace: kube-course
labels:
app.kubernetes.io/name: python-api
app.kubernetes.io/part-of: kube-python-course
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: course
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
Именованный targetPort: http разрешается по имени порта контейнера каждого
endpoint.
Селектор Service не «проверяет» Deployment: он независимо выбирает любые Pods.
Типы Service:
ClusterIP— стабильный внутренний виртуальный IP, тип по умолчанию;- без ClusterIP (
clusterIP: None) — DNS возвращает адреса endpoints без виртуального IP; NodePort— порт на каждом узле; обычно строительный блок, а не предпочтительная внешняя точка входа;LoadBalancer— запрос внешнего LB у реализации или облачного контроллера;ExternalName— DNS CNAME без прокси и селекторов.
API LoadBalancer не создаёт облачный LB сам: нужен контроллер. Старое поле
ExternalIP небезопасно в мультитенантных кластерах и устарело в v1.36; курс его
не использует.
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: python-api
namespace: kube-course
labels:
app.kubernetes.io/name: python-api
spec:
rules:
- host: python-api.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: python-api
port:
name: http
Ingress v1 имеет стабильный статус, но API заморожен; Kubernetes рекомендует
Gateway API. Принятый Ingress без контроллера ничего не маршрутизирует. В
эксплуатационной среде задайте реальный ingressClassName и TLS. Локальный kind можно
дополнить cloud-provider-kind v0.9.0+, который поддерживает Ingress/Gateway;
это дополнение, а не часть ядра.
Gateway API — семейство CRDs (GatewayClass, Gateway, HTTPRoute) плюс
контроллер. Оно лучше разделяет владельца инфраструктуры и владельца маршрута,
даёт переносимый status и привязку, а также расширенные маршруты. Наличие
gateway.networking.k8s.io проверяйте через обнаружение API; не применяйте пользовательский ресурс до
установки совместимой реализации.
NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: python-api
namespace: kube-course
labels:
app.kubernetes.io/name: python-api
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: course
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-course
ports:
- protocol: TCP
port: 8000
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Политика выбирает целевые Pods. Политики складываются: нет порядка правил и явного запрета; отсутствие разрешающего правила после изоляции означает запрет. Соединение должны разрешать исходящий egress источника и входящий ingress цели. CNI обязан поддерживать NetworkPolicy. Сеть kind по умолчанию может принять объект и не применять его — принятый ресурс не является доказательством.
DNS и EndpointSlices
Service FQDN:
python-api.kube-course.svc.cluster.local
Из того же namespace достаточно python-api; из другого —
python-api.kube-course. Суффиксы поиска и ndots влияют на число запросов.
CoreDNS — дополнение для обнаружения сервисов, обычно Service kube-dns в
kube-system.
EndpointSlice discovery.k8s.io/v1 масштабируемо представляет целевые ресурсы.
Готовый Pod обычно имеет condition ready: true; провал readiness убирает его
из обычного потока трафика. Не редактируйте управляемый контроллером объект
EndpointSlice вручную.
Практическое упражнение: Service → DNS → целевой адрес
Подготовка
kubectl apply -f manifests/base/configmap.yaml
kubectl apply -f manifests/base/secret.example.yaml
kubectl apply -f manifests/base/serviceaccount.yaml
kubectl apply -f manifests/base/deployment.yaml
kubectl apply -f manifests/base/service.yaml
kubectl rollout status deployment/python-api -n kube-course --timeout=180s
kubectl get service python-api -n kube-course
kubectl get endpointslice -n kube-course \
-l kubernetes.io/service-name=python-api -o wide
Ожидаются ClusterIP и три готовых адреса на порту 8000. Старый объект
Endpoints остаётся представлением для совместимости; для анализа используйте
EndpointSlice.
Локальный доступ:
kubectl port-forward -n kube-course service/python-api 8080:80
В другом терминале:
for _ in 1 2 3 4 5; do
curl --fail --silent http://127.0.0.1:8080/; printf '\n'
done
port-forward service/... выбирает целевой Pod для туннеля; это не полноценная
проверка плоскости данных Service и балансировки нагрузки в кластере. Для неё нужен
клиент внутри кластера:
apiVersion: v1
kind: Pod
metadata:
name: dns-client
namespace: kube-course
labels:
app.kubernetes.io/name: dns-client
spec:
restartPolicy: Never
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: client
image: busybox:1.37.0
command: ["sh", "-c", "sleep 3600"]
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
kubectl apply -f /tmp/dns-client.yaml
kubectl wait pod/dns-client -n kube-course \
--for=condition=Ready --timeout=120s
kubectl exec -n kube-course dns-client -- \
nslookup python-api.kube-course.svc.cluster.local
kubectl exec -n kube-course dns-client -- \
wget -qO- http://python-api/
Ожидаются ClusterIP Service, JSON-ответ и код завершения 0.
Дополнительная лабораторная работа с Ingress
Установите и запустите cloud-provider-kind v0.9.0+ по инструкции его выпуска
в отдельном терминале хоста; не используйте плавающую версию в автоматизации.
go install sigs.k8s.io/cloud-provider-kind@v0.9.0
"$(go env GOPATH)/bin/cloud-provider-kind"
Затем:
kubectl apply -f manifests/base/ingress.yaml
kubectl get ingress python-api -n kube-course -w
INGRESS_IP="$(kubectl get ingress python-api -n kube-course \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
curl --fail --header 'Host: python-api.local' "http://${INGRESS_IP}/"
Не заявляйте об успехе, пока status не содержит адрес и curl не вернул ответ
приложения. Если реализация требует IngressClass, создайте или укажите
документированный класс, а не угадывайте аннотацию.
Очистка:
kubectl delete pod dns-client -n kube-course
kubectl delete ingress python-api -n kube-course --ignore-not-found
Service и Deployment оставьте для следующих лабораторных работ.
Самостоятельная задача
Создайте Service без ClusterIP python-api-headless с тем же селектором. Сравните
nslookup обычного имени и имени без ClusterIP, EndpointSlices и поведение после
масштабирования Deployment до одной и трёх реплик. Критерий приёмки: объяснено,
где находится виртуальный IP, а где список адресов Pod, и почему клиенту
Service без ClusterIP нужны собственные правила балансировки и повторов.
Сломанные сценарии
Несовпадение селектора
kubectl apply -f manifests/broken/04-service-selector.yaml
kubectl get service broken-selector -n kube-course -o yaml
kubectl get endpointslice -n kube-course \
-l kubernetes.io/service-name=broken-selector -o yaml
kubectl get pods -n kube-course --show-labels
Service принят и имеет ClusterIP, но готовые endpoints отсутствуют. curl сообщает
тайм-аут или ошибку соединения, а журналы приложения пусты. Исправьте
python-ap1 → python-api, повторите проверку EndpointSlice и запрос. Это
доказательнее перезапуска Pods.
DNS/NetworkPolicy
Примените 08-deny-dns.yaml к dns-client. Если nslookup продолжает
работать, сначала проверьте поддержку CNI: причиной может быть отсутствие
применения политики, а не неверный YAML:
kubectl get networkpolicy -n kube-course
kubectl -n kube-system get pods -o wide
kubectl exec -n kube-course dns-client -- nslookup kubernetes.default.svc
На CNI с поддержкой политик DNS-запрос завершится тайм-аутом. Восстановление:
kubectl delete -f manifests/broken/08-deny-dns.yaml
Для полноценной лабораторной установите поддерживаемый провайдер NetworkPolicy до создания кластера согласно официальным инструкциям провайдера и kind; смена CNI в работающем учебном кластере обычно означает пересоздание. Сохраните результат контрольной проверки.
Дерево диагностических решений
HTTP-запрос завершается ошибкой
├─ имя DNS не разрешается
│ ├─ неверный /etc/resolv.conf/search → политика/конфигурация DNS Pod
│ ├─ Service/Pods CoreDNS неисправны → данные дополнения
│ └─ исходящий UDP/TCP 53 запрещён → NetworkPolicy
├─ DNS разрешает Service, но соединение не устанавливается
│ ├─ EndpointSlice пуст → селектор/readiness
│ ├─ неверный `port`/`targetPort`/`name` → Service и контейнер
│ ├─ прямой запрос к PodIP не проходит → прослушиватель/проверка/политика приложения
│ └─ PodIP работает, VIP нет → плоскость данных Service/CNI
└─ ошибка только в Ingress
├─ нет контроллера/class/status → у ресурса нет согласующего контроллера
├─ маршрут/цель не совпадают → status Ingress/HTTPRoute
├─ NetworkPolicy блокирует контроллер → исходный namespace/порт
└─ поля `host`/`path` или TLS не совпадают → данные L7/журналы контроллера
Типичные ошибки: селектор Service отличается одним символом; приложение слушает
только 127.0.0.1; port перепутан с targetPort; провал readiness удаляет
все endpoints; NetworkPolicy применена на CNI без поддержки; разрешён UDP 53,
но не TCP 53; Ingress не имеет контроллера; LoadBalancer ожидается в кластере
без облачной интеграции.
Эксплуатационные компромиссы, безопасность и стоимость
Каждый внешний LB или IPv4-адрес может стоить денег; объединяйте маршруты L7 осознанно, учитывая радиус поражения. NodePort расширяет поверхность атаки. Завершение TLS, исходный IP и тайм-ауты зависят от реализации. Политика default-deny снижает возможность бокового перемещения, но требует правил для DNS, исходящих зависимостей и наблюдаемости. NetworkPolicy действует на L3/L4, а не авторизует приложение. Контроллер Gateway/Ingress — привилегированная общая точка входа: изолируйте namespaces, фиксируйте версии образов, проверяйте межпространственные ссылки и ограничения частоты запросов.
Самопроверка
- Почему ClusterIP с пустым EndpointSlice бесполезен?
- Кто создаёт EndpointSlices?
- Что меняет провал readiness?
- Почему принятая NetworkPolicy может ничего не фильтровать?
- Чем
portотличается отtargetPort? - Почему Ingress — не контроллер?
- Какие две стороны политики нужны для соединения?
Ответы: 1) нет целевых ресурсов; 2) контроллер Service/EndpointSlice; 3) участие в трафике Service; 4) CNI может не поддерживать применение политик; 5) сторона Service и сторона Pod; 6) это декларативный объект маршрута, реализация поставляется отдельно; 7) исходящий egress источника и входящий ingress цели.
Резюме и источники
Диагностируйте сеть по каждому переходу: имя → Service → EndpointSlice → политика → порт Pod → процесс → L7. Далее: хранилище и состояние.