Урок 04 · Kubernetes на практике

Service

Что-то не запустилось или осталось непонятным? Задайте вопрос в комментариях к разбору этого урока в канале — разберёмся вместе.

В уроке 3 вы удаляли Pod и видели, как ReplicaSet создаёт новый взамен — с другим именем и, важно, с другим IP-адресом. Пока вы обращались к нему через kubectl port-forward, это было незаметно. Но что если к вашему сервису должен обращаться кто-то другой — другой Pod, или вы сами из браузера — и делать это не разово, а постоянно? Адрес, который меняется при каждом перезапуске, для этого не годится. Нужен Service — стабильный адрес, который не меняется никогда, сколько бы Pod'ов ни падало и пересоздавалось за ним.

Зачем нужен Service

У Service две задачи одновременно:

  • Стабильный адрес. IP самого Service не меняется всё время его существования — в отличие от IP отдельных Pod'ов, которые эфемерны по определению (см. урок 3).
  • Балансировка нагрузки. Если за Service стоит два Pod'а, как в нашем Deployment из урока 3, — Service сам решает, какому из них отдать очередной запрос, а вызывающему об этом думать не нужно вовсе.

Находит «свои» Pod'ы Service точно так же, как ReplicaSet в прошлом уроке — через label selector. Тот же принцип, только на этот раз ярлыки сопоставляет не контроллер, а сетевой объект.

Схема: запрос идёт на стабильный адрес Service, а он распределяет его между двумя Pod'ами по label selector ОТКУДА-ТО Клиент браузер / Pod не знает, сколько Pod'ов стабильный IP СЕТЬ Service app: hello-fastapi выбирает, кому отдать POD × 2 Ваш контейнер hello-fastapi #1 hello-fastapi #2 IP этих двух клиенту не важен
Клиент всегда обращается к одному и тому же адресу Service — какой конкретно Pod ответит и сколько их вообще, решает сам Kubernetes.
Как это устроено изнутри На каждой Node фоново работает kube-proxy — он следит за всеми Service в кластере и обновляет сетевые правила так, чтобы трафик на адрес Service прозрачно уходил на один из подходящих по label selector Pod'ов. Разбирать сами правила незачем — но полезно понимать, что «стабильный адрес» не магия, а обычный сетевой агент, работающий на каждой ноде.

ClusterIP и NodePort

У Service есть несколько типов — в этом уроке нужны только два:

  • ClusterIP (по умолчанию) — адрес виден только изнутри кластера. Это основной способ, которым один Pod находит другой: в лоб, по IP, Pod'ы друг с другом никогда не говорят.
  • NodePort — то же самое, что ClusterIP, плюс сервис дополнительно открывается на отдельном порту каждой Node, то есть становится доступен снаружи кластера. Через него мы и заглянем в свой сервис из браузера.

Полноценный внешний доступ по-настоящему принято настраивать через отдельный объект Ingress — он будет в уроке 7. NodePort сейчас — учебный короткий путь, чтобы своими глазами увидеть работающую балансировку, не забегая вперёд.

Практика: откройте сервис через стабильный адрес

Deployment из урока 3 (hello-fastapi, 2 реплики) должен быть уже запущен — проверьте: kubectl get pods.

  1. Создайте файл service.yaml:
    apiVersion: v1
    kind: Service
    metadata:
      name: hello-fastapi
    spec:
      type: NodePort
      selector:
        app: hello-fastapi
      ports:
        - port: 8000
          targetPort: 8000

    selector здесь — тот же ярлык app: hello-fastapi, что вы указывали в Deployment из урока 3. Service найдёт по нему оба Pod'а автоматически.

  2. Примените манифест:
    kubectl apply -f service.yaml
  3. Посмотрите, что получилось:
    kubectl get services
    У hello-fastapi появится свой CLUSTER-IP — тот самый стабильный адрес — и в колонке PORT(S) будет виден дополнительный NodePort вида 8000:3XXXX/TCP.
  4. minikube умеет сам находить правильный адрес и порт для NodePort-сервиса — не нужно искать их вручную:
    minikube service hello-fastapi --url
    Откройте полученный адрес с припиской /docs в браузере. Это тот же интерфейс FastAPI, что и раньше, — но теперь вы обращаетесь не к конкретному Pod'у напрямую, а к Service.
Проверьте стабильность адреса Откройте kubectl get pods, удалите любой из двух Pod'ов (kubectl delete pod <имя>) и, не меняя ничего, обновите страницу /docs в браузере ещё раз. Она продолжит отвечать — хотя один из Pod'ов позади Service только что был полностью заменён на новый, с другим IP. В уроке 3 для этого пришлось бы заново набирать kubectl port-forward — команда была привязана к конкретному Pod'у. Service от такой привязки не страдает в принципе.
Лайфхак Если один Pod вашего приложения захочет обратиться к другому сервису внутри кластера — например, к базе данных, поднятой отдельным Deployment'ом, — никогда не прописывайте IP Pod'а в коде или конфиге. Обращайтесь по имени Service (hello-fastapi, как в этом уроке) — Kubernetes сам резолвит его через встроенный DNS в актуальный адрес, даже если Pod'ы внутри полностью пересоздались.

Проверьте себя

Зачем нужен Service, если у Deployment уже есть Pod'ы, которые он поддерживает?

Напишите команду, которая применит манифест service.yaml к кластеру:

Напишите команду, которая выдаст доступный извне URL для NodePort-сервиса hello-fastapi в minikube:

Основной источник для этого урока: Kubernetes Docs: Service (официальная документация).