Маршрутизация трафика Kubernetes с помощью Ingress

KubernetesBeginner
Практиковаться сейчас

Введение

Сервисы предоставляют стабильные точки доступа к приложениям внутри кластера. Ingress добавляет перед сервисами правила маршрутизации HTTP и позволяет нескольким приложениям использовать одну точку входа, различая их по имени хоста или URL-пути.

Вы изучите подготовленный контроллер ingress-nginx, развернёте два бэкенда, добавите маршрутизацию по хосту и пути, а затем проследите путь запроса через Ingress, Service и Pod.

На протяжении всей лабораторной работы держите в уме следующий маршрут запроса: клиент → точка входа ingress-nginx → правило Ingress → Service → готовый Pod. Если запрос завершается ошибкой, проверять эти связи по порядку гораздо проще, чем рассматривать Ingress как единый «чёрный ящик».

Изучение контроллера Ingress

На этом шаге вы изучите контроллер, уже предоставленный средой CKA, вместо установки его второй копии.

Объект Ingress содержит конфигурацию, а не сам процесс прокси. Он не будет работать, если совместимый контроллер не отслеживает этот объект. Для начала убедитесь, что присутствуют обе необходимые части инфраструктуры: IngressClass с именем nginx и доступный Deployment контроллера ingress-nginx.

cd /home/labex/project/ingress-routing
kubectl get ingressclasses
kubectl get deployments,pods,services -n ingress-nginx
kubectl wait --for=condition=Available deployment/ingress-nginx-controller -n ingress-nginx --timeout=60s

Ресурс Ingress хранит намерение маршрутизации. Контроллер ingress-nginx отслеживает ресурсы, у которых значение ingressClassName равно nginx, и настраивает фактический прокси.

Сервис контроллера публикует прокси через NodePort в этом локальном кластере. Позже вы определите этот порт, не прописывая его жёстко.

Подготовленная среда должна показать класс nginx, один доступный контроллер и сервис NodePort:

NAME    CONTROLLER
nginx   k8s.io/ingress-nginx

deployment.apps/ingress-nginx-controller   1/1   ...
service/ingress-nginx-controller           NodePort   ...   80:.../TCP,443:.../TCP

Два Pod со статусом Completed, которые также могут отображаться, — это уже завершившиеся установочные Job. Они не являются отказавшими репликами контроллера.

Развёртывание двух сервисов бэкенда

На этом шаге из одного многофайлового документа создайте два веб-приложения с одной репликой каждое и соответствующие сервисы ClusterIP.

Эти бэкенды используют концепции из курса «Kubernetes для начинающих», поэтому основное внимание уделяется не синтаксису Deployment, а связи между ресурсами и правилам маршрутизации. Файл содержит пять YAML-документов, разделённых ---: сначала Namespace, затем Deployment и Service для каждого приложения. Сопоставьте Service.spec.selector с меткой соответствующего Pod — они должны совпадать, прежде чем Ingress сможет направлять трафик.

cd /home/labex/project/ingress-routing
cat <<'EOF' > backends.yaml
apiVersion: v1
kind: Namespace
metadata: {name: practice-ingress}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: storefront, namespace: practice-ingress}
spec:
  replicas: 1
  selector: {matchLabels: {app: storefront}}
  template:
    metadata: {labels: {app: storefront}}
    spec:
      containers: [{name: web, image: nginx:1.27-alpine, imagePullPolicy: IfNotPresent, ports: [{name: http, containerPort: 80}]}]
---
apiVersion: v1
kind: Service
metadata: {name: storefront, namespace: practice-ingress}
spec: {selector: {app: storefront}, ports: [{name: http, port: 80, targetPort: http}]}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: reports, namespace: practice-ingress}
spec:
  replicas: 1
  selector: {matchLabels: {app: reports}}
  template:
    metadata: {labels: {app: reports}}
    spec:
      containers: [{name: web, image: httpd:2.4-alpine, imagePullPolicy: IfNotPresent, ports: [{name: http, containerPort: 80}]}]
---
apiVersion: v1
kind: Service
metadata: {name: reports, namespace: practice-ingress}
spec: {selector: {app: reports}, ports: [{name: http, port: 80, targetPort: http}]}
EOF

Примените все пять документов одной командой и дождитесь готовности обоих Deployment. Раздельное ожидание помогает понять, какой именно бэкенд неисправен, если проблема связана с образом или селектором:

kubectl apply -f backends.yaml
kubectl rollout status deployment/storefront -n practice-ingress --timeout=60s
kubectl rollout status deployment/reports -n practice-ingress --timeout=60s

Ingress направляет трафик только к сервисам, поэтому у каждого бэкенда уже должны быть готовые конечные точки.

Перед созданием правил маршрутизации явно проверьте это условие:

kubectl get services,endpointslices -n practice-ingress

У каждого сервиса должен быть соответствующий EndpointSlice с одним готовым адресом.

service/reports      ClusterIP   ...   80/TCP
service/storefront   ClusterIP   ...   80/TCP

endpointslice.../reports-...      IPv4   80   10.244.104...
endpointslice.../storefront-...   IPv4   80   10.244.104...

Суффиксы и адреса создаются автоматически и могут отличаться, но у каждого имени Service должен быть один непустой EndpointSlice. Ingress не может исправить сервис без бэкенда — эта проверка намеренно подтверждает работоспособность нижнего уровня заранее.

Добавление маршрутизации по хосту

На этом шаге направьте запросы к store.practice.test в сервис storefront.

Правило Ingress принимает три решения о маршрутизации: host, path и порт сервиса бэкенда. Параметр ingressClassName: nginx выбирает нужный контроллер. Аннотация rewrite зависит от конкретного контроллера: она изменяет путь запроса, передаваемого бэкенду, на /, который могут обработать оба демонстрационных веб-сервера.

cd /home/labex/project/ingress-routing
cat <<'EOF' > ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: practice-routing
  namespace: practice-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: store.practice.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: storefront
                port: {name: http}
EOF

Примените правило и с помощью describe изучите, как контроллер его интерпретировал, включая определённую конечную точку бэкенда:

kubectl apply -f ingress.yaml
kubectl describe ingress practice-routing -n practice-ingress

pathType: Prefix сопоставляет путь / и все его подпути. Аннотация rewrite направляет выбранный запрос к пути / на бэкенде, где демонстрационный веб-сервер предоставляет страницу. Хост используется как ключ HTTP-маршрутизации; для этого локального теста публичная DNS-запись не требуется.

Команда describe должна разрешить правило вплоть до адреса Pod:

Rules:
  Host                 Path  Backends
  store.practice.test
                       /   storefront:http (10.244.104...:80)

Пустое поле Address сразу после создания объекта в этой локальной среде не означает ошибку. Важными подтверждениями являются найденный бэкенд storefront:http и адрес его Pod.

Добавление маршрутизации по пути и проверка запросов

На этом шаге добавьте маршрут /reports и проверьте оба правила через точку входа узла.

Путь JSON Patch заканчивается на /-. Для массива - означает добавление нового элемента, а не замену существующего маршрута /. Новый бэкенд указывает на сервис reports, используя его именованный порт http.

Выполните это добавление один раз. В отличие от kubectl apply, добавление в /- не является идемпотентным: повторный запуск добавит ещё один идентичный элемент /reports, вместо того чтобы распознать уже существующий.

Следующие две команды определяют конечную точку вместо использования жёстко заданного адреса. minikube ip -p labex-v135 возвращает IP-адрес узла для указанного профиля Minikube, а подстановка команд $(...) сохраняет вывод каждой команды в переменную оболочки. Запрос JSONPath выбирает порт сервиса с именем http и извлекает его NodePort.

kubectl patch ingress practice-routing -n practice-ingress --type='json' -p='[{"op":"add","path":"/spec/rules/0/http/paths/-","value":{"path":"/reports","pathType":"Prefix","backend":{"service":{"name":"reports","port":{"name":"http"}}}}}]'
NODE_IP=$(minikube ip -p labex-v135)
HTTP_NODE_PORT=$(kubectl get service ingress-nginx-controller -n ingress-nginx -o jsonpath='{.spec.ports[?(@.name=="http")].nodePort}')
echo "Ingress endpoint: ${NODE_IP}:${HTTP_NODE_PORT}"

Эти две переменные позволяют не включать в инструкции адреса, зависящие от конкретной среды. Теперь отправьте запросы с заголовком host, который требуется правилом. В каждой команде curl параметр -s скрывает индикатор выполнения, --retry 5 повторяет запрос при временных сбоях, --retry-connrefused также повторяет запрос при первоначальном отказе в соединении, а --retry-delay 2 ждёт две секунды между попытками. Параметр -H задаёт HTTP-заголовок Host, а конвейер передаёт ответ в grep, чтобы вывести только текст, идентифицирующий страницу.

curl -s --retry 5 --retry-connrefused --retry-delay 2 -H 'Host: store.practice.test' "http://${NODE_IP}:${HTTP_NODE_PORT}/" | grep 'Welcome to nginx'
curl -s --retry 5 --retry-connrefused --retry-delay 2 -H 'Host: store.practice.test' "http://${NODE_IP}:${HTTP_NODE_PORT}/reports" | grep 'It works'

Два ответа подтверждают, что одна точка входа направляет запросы к разным сервисам.

Первый ответ поступает от NGINX, а второй — от Apache httpd. Отличающийся текст страниц намеренно служит доказательством того, что правило маршрутизации по пути выбрало другой бэкенд, а не просто вернуло код HTTP 200.

Ожидаемые строки:

<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
<title>It works! Apache httpd</title>
<p>It works!</p>

Этот результат также показывает, почему важен пользовательский заголовок Host: оба запроса достигают одного и того же IP-адреса узла и NodePort, а именно хост и путь определяют правило и выбранный бэкенд.

Итоги

Вы использовали подготовленный контроллер ingress-nginx, связали правила Ingress с готовыми сервисами и проверили маршрутизацию по хосту и пути через единую точку входа.