← Весь справочник

Kubernetes: Service, Ingress, NetworkPolicy

Сеть в Kubernetes — типы Service, порты, Ingress с путями и TLS, NetworkPolicy и default-deny

Поды появляются и исчезают, их IP меняются. Поэтому к ним обращаются не напрямую, а через объекты сети:

ОбъектЗачем
Serviceпостоянное имя и IP для группы подов, выбранных по меткам; балансирует запросы между ними
IngressHTTP(S)-маршрутизация снаружи кластера: домен и путь → Service; TLS
NetworkPolicyфайрвол между подами: кто к кому может ходить

Внутри кластера Service доступен по DNS-имени <сервис> из своего неймспейса и <сервис>.<неймспейс>.svc.cluster.local из любого.

Синтаксис

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector: {app: web}     # на какие поды слать трафик (по меткам)
  ports:
    - port: 80             # порт сервиса
      targetPort: 8080     # порт в контейнере

Заготовки Service и Ingress генерирует kubectl create service … и kubectl create ingress … с --dry-run=client -o yaml. Для NetworkPolicy генератора нет — пиши руками.

Service: типы

spec.typeКак доступенЗаметки
ClusterIPвиртуальный IP внутри кластерапо умолчанию
ClusterIP + clusterIP: Noneheadless: IP нет, DNS-имя сервиса возвращает IP подовдля StatefulSet и клиентской балансировки
NodePortпорт на каждом узле: IP-узла:nodePortзаодно получает ClusterIP
LoadBalancerвнешний балансировщик облаказаодно NodePort и ClusterIP; без облачного контроллера EXTERNAL-IP останется <pending>
ExternalNameDNS-псевдоним (CNAME) на внешний хост из externalNameбез селектора и портов-прокси

Service: поля

ПолеЧто значитПо умолчанию / заметки
spec.selectorметки подов, на которые идёт трафикбез селектора Kubernetes не знает подов: адреса задают вручную (EndpointSlice)
spec.ports[].portпорт, на котором слушает сервисобязательно
spec.ports[].targetPortпорт контейнера: число или имя из containerPortравен port
spec.ports[].nodePortпорт на узлах для NodePort/LoadBalancerвыбирается сам из 30000–32767
spec.ports[].protocolTCP / UDP / SCTPTCP
spec.ports[].nameимя портаобязательно, если портов больше одного
spec.clusterIPконкретный IP или None (headless)выдаётся автоматически
spec.externalNameхост для ExternalName
spec.sessionAffinityClientIP — один клиент всегда на один подNone
spec.externalTrafficPolicyCluster / Local — для внешнего трафика только поды своего узла, сохраняется IP клиентаCluster
spec.publishNotReadyAddressesвключать в DNS поды, которые не Readyfalse

Ingress

Ingress — только правила. Работают они, если в кластере установлен Ingress-контроллер (ingress-nginx, Traefik и др.).

ПолеЧто значитПо умолчанию / заметки
spec.ingressClassNameкакой контроллер обрабатывает этот Ingress (nginx, traefik)класс, помеченный в кластере как default
spec.rules[].hostдомен: example.com, *.example.comбез host — любой домен
spec.rules[].http.paths[].pathпуть URL
spec.rules[].http.paths[].pathTypePrefix / Exact / ImplementationSpecificобязательно
…paths[].backend.service.nameкуда направить запрос
…paths[].backend.service.port.number / .nameпорт сервиса (не контейнера) — числом или именемодно из двух
spec.defaultBackendкуда идут запросы, не подошедшие ни под одно правилозависит от контроллера
spec.tls[].hostsдля каких доменов включить HTTPS
spec.tls[].secretNameSecret типа kubernetes.io/tls с сертификатом и ключомсм. k8s-config
metadata.annotationsнастройки конкретного контроллера: nginx.ingress.kubernetes.io/rewrite-target и т. п.

pathType:

ЗначениеЧто совпадает с path: /api
Exactтолько /api
Prefix/api, /api/, /api/users — по целым сегментам пути; /apiv2 не совпадает
ImplementationSpecificкак решит контроллер

Если путей несколько, выигрывает самый длинный совпавший.

NetworkPolicy

Пока на под не действует ни одна политика, ему разрешено всё. Как только его выбирает хотя бы одна политика нужного направления (Ingress — входящий трафик, Egress — исходящий), в этом направлении разрешено только то, что разрешают политики. Запрещающих правил нет — политики только добавляют разрешения.

ПолеЧто значитПо умолчанию / заметки
spec.podSelectorк каким подам своего неймспейса применяется политикаобязательно; {} — ко всем
spec.policyTypes[Ingress], [Egress] или обаесли не указано: Ingress, плюс Egress, если есть раздел egress
spec.ingress[].from[]от кого разрешён входящий трафикпустой список правил = запрещено всё входящее
spec.ingress[].ports[]{protocol: TCP, port: 8080}; port — число или имя порта; endPort для диапазонабез ports — все порты
spec.egress[].to[], spec.egress[].ports[]куда разрешён исходящий трафик

Элементы from / to:

ЭлементЧто выбирает
podSelector: {matchLabels: …}поды с этими метками в том же неймспейсе
namespaceSelector: {matchLabels: …}все поды неймспейсов с этими метками; у каждого неймспейса есть метка kubernetes.io/metadata.name: <имя>
podSelector + namespaceSelector в одном элементеподы с метками и в подходящих неймспейсах
ipBlock: {cidr: 10.0.0.0/8, except: [10.1.0.0/16]}диапазон IP (обычно — адреса вне кластера)

Разные элементы списка from соединяются через «или», а селекторы внутри одного элемента — через «и».

Примеры

# Service разных типов: --tcp=порт_сервиса:порт_контейнера
kubectl create service clusterip web --tcp=80:8080 --dry-run=client -o yaml > svc.yaml
kubectl create service nodeport web --tcp=80:8080 --node-port=30080 --dry-run=client -o yaml
kubectl create service clusterip db --clusterip=None --dry-run=client -o yaml   # headless
kubectl create service externalname mail --external-name=smtp.example.com --dry-run=client -o yaml

# генератор берёт селектор app=<имя сервиса>; если у подов другие метки — поправь
yq -i '.spec.selector = {"app": "frontend"}' svc.yaml

# Ingress: путь с * — Prefix, без * — Exact; tls=секрет включает HTTPS для host
kubectl create ingress site --class=nginx \
  --rule='example.com/api*=api:80' \
  --rule='example.com/*=web:80,tls=site-tls' \
  --dry-run=client -o yaml > ingress.yaml

# посмотреть пути и типы
yq '.spec.rules[].http.paths[] | .path + " " + .pathType + " -> " + .backend.service.name' ingress.yaml

kubeconform -strict -summary svc.yaml ingress.yaml

# в кластере (здесь не сработают):
# kubectl get svc,endpointslices -l app=web     # нашлись ли поды за сервисом
# kubectl get ingress site
# kubectl run tmp --rm -it --image=busybox -- wget -qO- http://web

Сервис с двумя именованными портами, targetPort — по имени порта контейнера:

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  type: NodePort
  selector: {app: api}
  ports:
    - name: http
      port: 80
      targetPort: http        # containerPort с name: http
      nodePort: 30080
    - name: metrics
      port: 9090
      targetPort: 9090

Ingress с HTTPS и двумя сервисами:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: site
spec:
  ingressClassName: nginx
  tls:
    - hosts: [example.com]
      secretName: site-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port: {number: 80}
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port: {name: http}

Запретить всё входящее в неймспейсе, затем разрешить точечно:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}             # все поды неймспейса
  policyTypes: [Ingress]      # правил ingress нет — входящее запрещено
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-from-api
spec:
  podSelector:
    matchLabels: {app: db}
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels: {app: api}
        - namespaceSelector:          # отдельный элемент: «или» — весь неймспейс monitoring
            matchLabels:
              kubernetes.io/metadata.name: monitoring
      ports:
        - {protocol: TCP, port: 5432}

Исходящий трафик только к DNS и к своей базе:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-egress
spec:
  podSelector:
    matchLabels: {app: api}
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:                # тот же элемент: «и» — kube-dns в kube-system
            matchLabels: {k8s-app: kube-dns}
      ports:
        - {protocol: UDP, port: 53}
        - {protocol: TCP, port: 53}
    - to:
        - podSelector:
            matchLabels: {app: db}
      ports:
        - {protocol: TCP, port: 5432}

Подводные камни

  • Селектор сервиса не совпадает с метками подов — ошибки нет, сервис просто без адресов, запросы получают connection refused или таймаут. kubeconform такое не ловит. Первым делом сравни spec.selector сервиса с labels в шаблоне пода.
  • port и targetPort перепутаны. port — порт сервиса, к нему обращаются клиенты; targetPort — порт, который слушает контейнер. Ingress ссылается на port сервиса, а не на порт контейнера.
  • Порт строкой. port: "80" не пройдёт схему. А targetPort: "8080" пройдёт, но будет считаться именем порта.
  • Несколько портов без name API-сервер отвергнет, а схема пропустит.
  • nodePort вне 30000–32767 API отвергнет (это диапазон по умолчанию), схема — пропустит.
  • LoadBalancer без облака (minikube, kind, голое железо) навсегда в <pending>, если не установлен MetalLB или аналог.
  • kubectl create ingress без * в пути даёт pathType: Exact: правило example.com/=web:80 поймает только корень, а /about уже нет. Для «всего сайта» пиши /*.
  • pathType: Prefix сравнивает сегменты, а не строки: /api не совпадёт с /apiv2.
  • Ingress без контроллера ничего не делает, без ошибок. А без ingressClassName и без класса по умолчанию его может не подхватить ни один контроллер.
  • podSelector в NetworkPolicy обязателен по схеме: политику «на все поды» пиши явно podSelector: {}.
  • Egress default-deny ломает DNS. Запретил весь исходящий трафик — поды перестали резолвить имена сервисов. Разреши UDP и TCP 53 к kube-dns.
  • Один дефис меняет смысл. - namespaceSelector: … и - podSelector: … отдельными элементами — это «или», а podSelector без своего дефиса под namespaceSelector — «и». Отступы проверяй особенно внимательно.
  • NetworkPolicy работает, только если сетевой плагин их поддерживает (Calico, Cilium и др.). С плагином без поддержки (например, flannel) политики принимаются API, но ничего не блокируют.
  • podSelector и namespaceSelector не выбирают узлы и внешние адреса. Для трафика извне кластера нужен ipBlock.

См. также

kubectl · kubeconform · yq · k8s-workloads · k8s-config · nginx · dig · curl · openssl