Kube/Pythonproduction course
Курс · RU04-services-dns-and-ingress.md

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
Маршрут и L7status 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-ap1python-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, фиксируйте версии образов, проверяйте межпространственные ссылки и ограничения частоты запросов.

Самопроверка

  1. Почему ClusterIP с пустым EndpointSlice бесполезен?
  2. Кто создаёт EndpointSlices?
  3. Что меняет провал readiness?
  4. Почему принятая NetworkPolicy может ничего не фильтровать?
  5. Чем port отличается от targetPort?
  6. Почему Ingress — не контроллер?
  7. Какие две стороны политики нужны для соединения?

Ответы: 1) нет целевых ресурсов; 2) контроллер Service/EndpointSlice; 3) участие в трафике Service; 4) CNI может не поддерживать применение политик; 5) сторона Service и сторона Pod; 6) это декларативный объект маршрута, реализация поставляется отдельно; 7) исходящий egress источника и входящий ingress цели.

Резюме и источники

Диагностируйте сеть по каждому переходу: имя → Service → EndpointSlice → политика → порт Pod → процесс → L7. Далее: хранилище и состояние.