open atlas
↑ К треку
Деплой и инфра DEP · 10 · 01

Service и Ingress

Pod-ы эфемерны и меняют IP, поэтому до них ходят через Service: стабильный виртуальный IP, балансирующий по pod-ам с подходящим label selector, а Ingress — общий HTTP-роутер впереди. Несовпадение selector — ноль endpoint-ов.

DEP Middle ◷ 17 min
Уровень
ОсновыJuniorMiddleSenior

Раскатка зелёная. Pod-ы в статусе Running, Deployment рапортует, что все реплики готовы, — и всё же каждый запрос к приложению висит и отваливается по таймауту. kubectl get endpoints api печатает одну леденящую строку: api <none>. Service указывает в пустоту. Кто-то переименовал label у pod-а с app: api на app: api-svc и не тронул selector у Service — поэтому Service не находит ни одного pod-а, строит пустой список endpoint-ов и тихо съедает трафик. С pod-ами всё хорошо. Сломана проводка между ними и Service.

Почему нельзя обращаться к pod-у напрямую

Первый инстинкт при деплое в Kubernetes — взять IP pod-а и звонить на него напрямую. Именно этот инстинкт и приводит к провалу из хука выше. Вот почему это никогда не будет работать надёжно.

Pod — самая дешёвая и одноразовая вещь в Kubernetes. Планировщик убивает его, перепланирует, масштабирует вверх и вниз, по своей прихоти переносит на другой узел — и каждый раз, возвращаясь, он получает новый IP из сети pod-ов. Нет стабильного адреса, который можно зашить, нет DNS-имени, переживающего раскатку, нет гарантии, что IP, который ты разрезолвил секунду назад, всё ещё принадлежит живому pod-у. Звонить напрямую на IP pod-а — баг, ждущий следующего перепланирования.

Service — это фикс. Это API-объект, дающий набору pod-ов одну стабильную идентичность: виртуальный IP (ClusterIP) и DNS-имя (<service>.<namespace>.svc.cluster.local), которые живут всё время существования Service, независимо от того, какие pod-ы стоят за ним прямо сейчас. Клиенты ходят на Service; Service балансирует каждое соединение по здоровым pod-ам. Pod-ы под ним сменяются; адрес впереди не двигается.

Механизм selector → endpoints

Связь между Service и его pod-ами — это label-ы, а не IP. Service объявляет selector — набор ключей/значений label-ов, — и Kubernetes непрерывно следит за pod-ами, которые (а) совпадают по каждому label из selector и (б) Ready (проходят свой readiness-probe). Для каждого такого pod-а он записывает его IP и порт в EndpointSlice (современный шардированный список точек назначения, пришедший на смену единому объекту Endpoints). Этот EndpointSlice и есть список целей балансировки; kube-proxy (агент уровня ядра на каждом узле) программирует data plane (правила iptables или IPVS) по нему.

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api            # должен точно совпадать с label-ами pod-ов
  ports:
    - name: http
      port: 80          # виртуальный порт Service
      targetPort: 8080  # порт контейнера в pod-е

Из этого следуют две вещи. Во-первых, сопоставление динамическое: масштабируешь Deployment вверх — и новые Ready-pod-ы добавляются в slice за мгновения; pod, проваливший readiness-probe, удаляется, и трафик перестаёт на него идти без всякого удаления вручную. Во-вторых — и это провал, который кусает всех — если selector не находит ни одного Ready-pod-а, EndpointSlice пуст, и Service маршрутизирует в никуда. У него всё ещё есть ClusterIP, он всё ещё резолвится в DNS, всё ещё принимает соединения — а потом отказывает или отваливается по таймауту, потому что нет бэкенда, куда форвардить.

Почему это работает

«Ready» здесь делает реальную работу. Pod может быть Running, но не Ready — он попадает в EndpointSlice только когда его readiness-probe проходит. Это намеренно: во время rolling update новые pod-ы держат вне Service, пока они реально не смогут обслуживать, а старые вынимают из slice до завершения. Ошибись с readiness-probe — и отгрузишь Service, у которого pod-ы запущены, но endpoint-ов ноль: та же чёрная дыра, причина тоньше.

Типы Service: как далеко дотягивается адрес

У каждого Service есть тип, решающий, кто до него дотянется. Они стакаются: каждый строится на предыдущем.

ТипОбластьВнешний?ЦенаКогда использовать
ClusterIP (по умолчанию)Только внутренний виртуальный IPНетБесплатноService-to-service внутри кластера
NodePortПорт (30000–32767) на каждом узлеДа, грубоБесплатноБазовый внешний доступ, dev, за своим LB
LoadBalancerПоднимает облачный балансировщикДаОдин платный LB на ServiceЕдиная L4-точка входа для одного Service
headless (clusterIP: None)DNS возвращает IP pod-ов, без виртуального IPНетБесплатноStatefulSet, адресация по pod-у, клиентская балансировка
Ingress (не тип Service)L7 HTTP(S)-роутер над многими ServiceДаОдин LB на все маршрутыHost/path-маршрутизация + TLS на весь кластер

Все эти типы — одно решение: как далеко за пределы кластера должен дотягиваться трафик? Начни с ClusterIP для всего внутреннего; тянись к Ingress — а не к LoadBalancer — как только нужна HTTP-маршрутизация в публичный интернет.

ClusterIP — это дефолт и рабочая лошадка: так твой API ходит к базе, к кэшу — всё по DNS-имени, всё внутри. NodePort открывает один и тот же порт на каждом узле и форвардит его на Service; это грубая ручка, чтобы завести трафик внутрь, полезная в dev или когда ты ставишь впереди свой балансировщик, но сырой URL node-ip:31000 пользователям не отдашь. LoadBalancer просит облачного провайдера поднять настоящий внешний балансировщик, указывающий на Service, — чисто, но ты платишь за и обслуживаешь один LB на Service, что быстро становится дорого и громоздко. headless вообще отказывается от виртуального IP: DNS возвращает индивидуальные IP pod-ов — ровно то, что нужно StatefulSet (база, брокер), чтобы клиенты могли адресовать конкретную реплику.

Ingress: одна парадная дверь для многих Service

Если LoadBalancer даёт один облачный LB на Service, то десять публичных сервисов стоят десяти балансировщиков — и при этом у тебя по-прежнему нет HTTP-осознанной маршрутизации. Ingress решает и то, и другое. Это L7 (HTTP/HTTPS) роутер, который стоит впереди твоих ClusterIP-Service и раскидывает запросы по host и path на нужный Service, централизованно терминирует TLS и делает всё это за одним общим балансировщиком. Одна точка входа, много бэкендов, гораздо дешевле, чем LB на сервис.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: site
spec:
  ingressClassName: nginx
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api      # маршрутизирует /api -> Service api
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web      # всё остальное -> Service web
                port:
                  number: 80

Ключевой подвох: объект Ingress — это лишь правила. Сам по себе он ничего не делает. Ты обязан запустить ingress-контроллер — nginx, Traefik, HAProxy, провайдерский, — который реально следит за объектами Ingress и превращает их в работающий обратный прокси. Нет контроллера — нет маршрутизации; YAML применяется чисто, а трафик всё равно идёт в никуда. Поле ingressClassName говорит Kubernetes, какой контроллер владеет этим Ingress.

Выбери лучший вариант

У тебя восемь публичных HTTP-сервисов, которым нужны host/path-маршрутизация и TLS. Как их выставить?

Классический провал: ноль endpoint-ов

Вернись к хуку. Сетевой баг Kubernetes №1 — это Service, чей selector не совпадает с label-ами его pod-ов. При apply ничего не падает — Service это валидный YAML, получает ClusterIP, резолвится в DNS. Но его selector не находит ни одного Ready-pod-а, поэтому EndpointSlice пуст, и каждое соединение отказывается или отваливается по таймауту. Тот же симптом пустых endpoint-ов даёт и pod, который совпал по label-ам, но не Ready (провальный или слишком строгий readiness-probe), или targetPort, не совпадающий с реальным портом контейнера.

Диагностика всегда одна и та же команда: kubectl get endpoints <svc> (или kubectl get endpointslices). Если там <none> или нет адресов — Service ни с кем не говорит: перестань отлаживать приложение и иди проверь, что selector у Service ровно равен label-ам pod-ов и что pod-ы Ready.

Викторина

У Service есть ClusterIP и он резолвится в DNS, но каждый запрос отваливается по таймауту. kubectl get endpoints показывает <none>. Pod-ы Running. Самая вероятная причина?

Вспомните перед уходом
  1. 01
    Пройди по механизму selector → endpoints: как Service решает, по каким pod-ам балансировать, и из-за чего список endpoint-ов пустеет?
  2. 02
    Зачем использовать Ingress вместо отдельного LoadBalancer на каждый сервис, и что должно работать, чтобы Ingress реально заработал?
Итог

Pod-ы эфемерны и получают новый IP при каждом перепланировании, поэтому ты никогда не обращаешься к ним напрямую — ставишь впереди Service. Теперь, когда встретишь kubectl get endpoints <svc> с результатом <none>, ты знаешь, куда смотреть: selector, label-ы и readiness-probe — именно в этом порядке. Service — это API-объект со стабильным ClusterIP и DNS-именем, который балансирует по набору pod-ов, выбранных через label selector: Kubernetes записывает IP pod-ов, совпавших по selector И при этом Ready, в EndpointSlice, а kube-proxy маршрутизирует трафик Service по этому живому списку. Тип управляет охватом: ClusterIP — только внутренний service-to-service, NodePort открывает порт на каждом узле для грубого внешнего доступа, LoadBalancer поднимает один платный облачный LB на Service, а headless-Service отбрасывает виртуальный IP, чтобы DNS возвращал IP pod-ов напрямую для StatefulSet. Поскольку LB-на-сервис дорог и не даёт HTTP-маршрутизации, впереди ставят Ingress как единую L7 парадную дверь: он маршрутизирует по host и path на многие ClusterIP-Service и централизованно терминирует TLS за одним общим балансировщиком — но это лишь правила, и нужен работающий ingress-контроллер (nginx, Traefik), чтобы их применять. Провал, кусающий сильнее всего, — самый простой: если selector у Service не совпадает с label-ами pod-ов (или pod-ы не Ready), EndpointSlice пуст, Service всё ещё резолвится, но маршрутизирует в никуда, и запросы тихо отваливаются по таймауту — поэтому первое, что нужно проверить, всегда kubectl get endpoints.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.