Основы маршрутизации в Kubernetes: Service, kube-proxy и CNI
Маршрутизация внутри кластера начинается с трёх компонентов: Service, kube-proxy и CNI-плагина. Service предоставляет стабильный IP-адрес и DNS-имя для группы подов. kube-proxy настраивает правила перенаправления трафика на этот IP. CNI-плагин обеспечивает связность сети между нодами. Без чёткого понимания этой цепочки диагностика проблем превращается в гадание.
Типы Service и когда их использовать
Kubernetes предлагает четыре основных типа Service. Выбор зависит от того, откуда приходит трафик и куда он должен попасть.
ClusterIP - сервис по умолчанию. Назначает внутренний IP, доступный только внутри кластера. Используйте для взаимодействия между компонентами одного приложения: бэкенд общается с базой данных, фронтенд - с API. Пример:
apiVersion: v1
kind: Service
metadata:
name: backend-api
spec:
type: ClusterIP
selector:
app: backend
ports:
- port: 80
targetPort: 8080
NodePort открывает порт на каждой ноде кластера в диапазоне 30000-32767. Трафик, пришедший на этот порт, перенаправляется в Service. Подходит для отладки и временного доступа. В продакшене NodePort создаёт лишнюю поверхность атаки и требует ручного управления портами. Если вам нужен внешний доступ, лучше использовать Ingress или LoadBalancer.
LoadBalancer интегрируется с облачным провайдером и создаёт внешний балансировщик нагрузки. Каждый такой Service получает собственный внешний IP. Это стандартный выбор для продакшена в AWS, GCP или Azure. В on-premise среде можно использовать MetalLB как реализацию LoadBalancer.
ExternalName не проксирует трафик, а создаёт CNAME-запись в DNS кластера. Применяется для доступа к внешним сервисам по DNS-имени без хардкодинга IP-адресов в конфигурациях подов.
kube-proxy: iptables vs IPVS - что выбрать?
kube-proxy - компонент, который материализует абстракцию Service в правила сетевого фильтра. Он работает в трёх режимах: userspace (устарел), iptables и IPVS.
Режим iptables включён по умолчанию в большинстве дистрибутивов. Для каждого Service kube-proxy создаёт цепочки правил в iptables. При росте числа сервисов количество правил растёт линейно - O(n). На кластерах с 1000+ сервисов это приводит к заметным задержкам при добавлении новых правил. Механизм балансировки в iptables - случайный выбор бэкенда, без учёта загрузки.
Режим IPVS использует Linux Virtual Server - подсистему ядра, заточенную под балансировку. Производительность не деградирует с ростом числа сервисов. IPVS поддерживает несколько алгоритмов балансировки: round-robin (rr), least connection (lc), source hash (sh) и другие. Для переключения на IPVS выполните:
# В конфигурации kube-proxy
mode: ipvs
ipvs:
scheduler: lc # least connection
Перед включением IPVS убедитесь, что на нодах загружены необходимые модули ядра: ip_vs, ip_vs_rr, ip_vs_wrr, ip_vs_sh. Проверить можно командой lsmod | grep ip_vs.
CNI-плагины и их влияние на маршрутизацию
CNI (Container Network Interface) - стандарт, по которому плагины настраивают сеть для подов. Выбор CNI определяет, как поды на разных нодах видят друг друга и какие сетевые политики доступны.
Flannel - простейший вариант. Создаёт оверлейную сеть через VXLAN или host-gw. Не поддерживает NetworkPolicy из коробки, требует отдельного плагина вроде Cilium для политик. Подходит для небольших кластеров и обучения.
Calico использует BGP для маршрутизации между нодами без оверлея - трафик идёт напрямую, без инкапсуляции. Это снижает latency и накладные расходы CPU. Calico поддерживает NetworkPolicy и собственные расширенные политики CalicoNetworkPolicy с возможностью фильтрации по FQDN.
Cilium построен на eBPF - технологии, которая выполняет код в ядре Linux без изменения самого ядра. Cilium обрабатывает сетевые пакеты на уровне системных вызовов, что даёт производительность, близкую к нативной. Поддерживает L7-политики: можно запретить HTTP-запросы с методом DELETE к конкретному URL. Для маршрутизации между сервисами Cilium предоставляет встроенную реализацию Service Mesh на eBPF - без сайдкар-контейнеров.
Если вы только начинаете работать с сетевыми политиками, обратите внимание на наше руководство по Service и Ingress - там разобраны типичные ошибки с селекторами и портами, которые приводят к 404.
Ingress и Gateway API: современная маршрутизация внешнего трафика
Service типа LoadBalancer решает задачу входа трафика, но не масштабируется: каждый сервис требует отдельного балансировщика, за который облачный провайдер выставляет счёт. Ingress и Gateway API решают эту проблему через единую точку входа с маршрутизацией на основе HTTP-хостов и путей.
Ingress: классический подход и его ограничения
Ingress - объект API, описывающий правила маршрутизации HTTP/HTTPS-трафика. Сами правила применяет Ingress Controller: nginx-ingress, Traefik, HAProxy и другие. Типичный манифест:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-v1
port:
number: 80
У этого подхода три фундаментальные проблемы. Первая - конфигурация через аннотации. Каждый контроллер имеет свой набор аннотаций, переход на другой контроллер требует переписывания манифестов. Вторая - отсутствие ролевой модели. Разработчик, создающий Ingress, может сломать маршрутизацию для всего кластера. Третья - слабая поддержка не-HTTP трафика и потоковой маршрутизации.
Подробный разбор Ingress-контроллеров с готовыми манифестами для Nginx и Traefik есть в статье по настройке маршрутизации HTTP/HTTPS трафика.
Gateway API: новый стандарт для Kubernetes 1.28+
Gateway API - преемник Ingress, вышедший в статус GA в Kubernetes 1.28. Он вводит разделение ответственности через три ресурса: GatewayClass (описывает реализацию контроллера), Gateway (экземпляр балансировщика) и HTTPRoute (правила маршрутизации).
Кластерный администратор создаёт GatewayClass и Gateway, определяя доступные порты, протоколы и TLS-сертификаты. Разработчик создаёт HTTPRoute, привязанный к конкретному Gateway, и описывает только свои маршруты. Он не может повлиять на чужие сервисы.
Установка Gateway API CRD:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
Пример HTTPRoute с взвешенной маршрутизацией для канареечного развёртывания:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
spec:
parentRefs:
- name: app-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: app-stable
port: 80
weight: 90
- name: app-canary
port: 80
weight: 10
Gateway API поддерживает HTTP-, TCP-, UDP- и gRPC-маршрутизацию. Для продакшена на Kubernetes 1.28+ это основной рекомендуемый способ управления входящим трафиком.
Отказоустойчивые пайплайны с операторами и Custom Resources
Service и Ingress покрывают статическую маршрутизацию. Когда логика маршрутизации зависит от состояния приложения или результатов обработки, нужны операторы - контроллеры, расширяющие Kubernetes через Custom Resource Definitions (CRD).
Проектирование Custom Resource Definition для пайплайна
Представьте пайплайн обработки данных: запрос приходит, проходит валидацию, обогащение и сохранение. Каждый шаг - отдельный микросервис. Если шаг падает, трафик нужно перенаправить на резервный экземпляр или поставить в очередь на повтор.
Описываем желаемое состояние через CRD:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: datapipelines.admin-wiki.ru
spec:
group: admin-wiki.ru
names:
kind: DataPipeline
plural: datapipelines
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
steps:
type: array
items:
type: object
properties:
name:
type: string
serviceName:
type: string
retryPolicy:
type: object
properties:
maxRetries:
type: integer
backoffSeconds:
type: integer
Экземпляр такого ресурса описывает конкретный пайплайн: последовательность шагов, имена сервисов для каждого шага и политику повторов при сбоях.
Реализация оператора: контроль маршрутизации и восстановление после сбоев
Оператор следит за ресурсами DataPipeline и управляет Service/Ingress в зависимости от состояния шагов. Используем фреймворк Kopf для Python:
import kopf
import kubernetes
import time
@kopf.on.create('admin-wiki.ru', 'v1', 'datapipelines')
def create_pipeline(spec, status, namespace, **kwargs):
api = kubernetes.client.CoreV1Api()
networking_api = kubernetes.client.NetworkingV1Api()
for i, step in enumerate(spec['steps']):
svc_name = step['serviceName']
# Проверяем, существует ли Service
try:
api.read_namespaced_service(svc_name, namespace)
status[f'step_{i}_ready'] = True
except kubernetes.client.exceptions.ApiException:
# Создаём Service с маршрутизацией на fallback
status[f'step_{i}_ready'] = False
# Логика exponential backoff
retries = 0
max_retries = step.get('retryPolicy', {}).get('maxRetries', 3)
backoff = step.get('retryPolicy', {}).get('backoffSeconds', 5)
while retries < max_retries:
time.sleep(backoff * (2 ** retries))
retries += 1
# Повторная проверка и восстановление
Оператор реализует exponential backoff: каждая повторная попытка ждёт вдвое дольше предыдущей. При длительном сбое оператор обновляет статус CR и может отправить алерт через metrics endpoint. Трафик автоматически переключается на резервный под или ставится в очередь.
Интеграция очередей сообщений: Kafka и RabbitMQ в Kubernetes
Прямая синхронная маршрутизация между сервисами создаёт жёсткие зависимости. Если сервис-получатель недоступен, запрос теряется. Очереди сообщений разрывают эту связку: отправитель публикует сообщение и продолжает работу, получатель забирает его, когда готов.
Выбор брокера: Kafka vs RabbitMQ для микросервисной архитектуры
| Критерий | Apache Kafka | RabbitMQ |
|---|---|---|
| Модель сообщений | Журнал (log), сообщения хранятся после чтения | Очередь, сообщение удаляется после подтверждения |
| Пропускная способность | Миллионы сообщений в секунду | Десятки тысяч сообщений в секунду |
| Гарантии доставки | At-least-once, exactly-once (с идемпотентным продюсером) | At-least-once, exactly-once (через publisher confirms) |
| Маршрутизация | По топикам и партициям | Гибкая: exchange (direct, topic, fanout, headers) |
| Сценарий | Потоковая обработка, событийная архитектура, аналитика | Сложная маршрутизация, RPC, задачи с приоритетами |
Kafka выбирают для высоконагруженных систем, где важна скорость и возможность перечитывать историю событий. RabbitMQ - когда нужна гибкая маршрутизация: сообщение может попасть в разные очереди в зависимости от заголовков или ключа маршрутизации.
Развертывание Kafka с Strimzi Operator
Strimzi - оператор для управления Kafka в Kubernetes. Он автоматизирует развёртывание, обновление и мониторинг кластера.
# Установка Strimzi
kubectl create namespace kafka
kubectl apply -f https://strimzi.io/install/latest?namespace=kafka -n kafka
# Создание Kafka-кластера с отказоустойчивостью
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: production-cluster
namespace: kafka
spec:
kafka:
replicas: 3
config:
min.insync.replicas: 2
default.replication.factor: 3
storage:
type: persistent-claim
size: 100Gi
zookeeper:
replicas: 3
storage:
type: persistent-claim
size: 50Gi
Параметр min.insync.replicas: 2 критичен для надёжности. При факторе репликации 3 и min.insync.replicas=2 система выдерживает потерю одной реплики без потери данных. Если доступна только одна реплика, продюсер с acks=all получит ошибку и не запишет сообщение - данные не будут потеряны.
Настройка продюсеров и консьюмеров для гарантированной доставки
Потеря сообщений происходит на стороне продюсера (не дождался подтверждения), брокера (упал до репликации) или консьюмера (зафиксировал смещение до обработки). Закрываем все три точки отказа.
Конфигурация продюсера на Python (confluent-kafka):
producer_config = {
'bootstrap.servers': 'production-cluster-kafka-bootstrap:9092',
'acks': 'all', # Ждём подтверждения от всех реплик
'enable.idempotence': True, # Исключаем дубликаты при повторах
'max.in.flight.requests.per.connection': 5,
'retries': 10,
'retry.backoff.ms': 100
}
На стороне консьюмера отключаем автоматическую фиксацию смещения и подтверждаем только после успешной обработки:
consumer_config = {
'bootstrap.servers': 'production-cluster-kafka-bootstrap:9092',
'group.id': 'processor-group',
'enable.auto.commit': False, # Ручное подтверждение
'auto.offset.reset': 'earliest'
}
# После обработки сообщения
consumer.commit(message)
Даже с идемпотентным продюсером возможны дубликаты при перебалансировке консьюмер-группы. Добавьте в обработчик проверку по уникальному ключу сообщения - например, через upsert в базу данных вместо insert.
Оптимизация потоков данных и типичные ошибки
Настроенная маршрутизация ломается в продакшене по предсказуемым причинам. Разберём три самые частые и способы их предотвратить.
NetworkPolicy: защита и сегментация трафика
По умолчанию в Kubernetes весь трафик между подами разрешён. Поды с чувствительными данными доступны из любого уголка кластера. NetworkPolicy закрывает эту брешь.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Эта политика разрешает входящий трафик к подам с меткой app: backend только от подов с меткой app: frontend на порт 8080. Всё остальное блокируется. Для тестирования политик используйте утилиту np-viewer или запускайте временный под с curl и проверяйте доступность целевого сервиса.
Настройка таймаутов и повторных попыток в Service Mesh
Цепочка сервисов A -> B -> C. Сервис C начинает тормозить. Без таймаутов A ждёт ответ от B, B ждёт ответ от C - потоки зависают, ресурсы исчерпываются, каскадный отказ накрывает весь кластер.
Service Mesh решает эту проблему на уровне инфраструктуры. Пример конфигурации Istio:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-b
spec:
hosts:
- service-b
http:
- route:
- destination:
host: service-b
timeout: 3s
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx,reset,connect-failure
Таймаут в 3 секунды на весь запрос и 1 секунда на каждую попытку. При трёх неудачных попытках возвращается ошибка, и поток освобождается. Circuit Breaker дополняет эту логику: если сервис C стабильно отвечает ошибками, Istio временно прекращает отправлять ему трафик и сразу возвращает 503.
Для углублённого изучения Service Mesh рекомендуем практическое руководство по Istio и Linkerd с готовыми конфигурациями для canary-деплоев и A/B-тестов.
Типичные ошибки при настройке маршрутизации и их решение
Неправильные селекторы в Service. Service направляет трафик на поды по меткам в spec.selector. Если метки в Service и Deployment не совпадают, трафик уходит в пустоту. Проверка: kubectl get endpoints <service-name> - список должен содержать IP подов.
Конфликт портов. Два Service пытаются занять один NodePort. Kubernetes назначает порты автоматически, но при ручном указании можно попасть в занятый диапазон. Проверка: kubectl get svc --all-namespaces | grep NodePort.
Отсутствие CNI. После установки кластера поды висят в статусе ContainerCreating или CrashLoopBackOff. Причина - не установлен CNI-плагин, pod network не настроена. Проверка: kubectl get pods -n kube-system - ищите поды Calico, Cilium или Flannel.
Неверные аннотации Ingress. Опечатка в аннотации nginx.ingress.kubernetes.io/rewrite-target ломает маршрутизацию без явных ошибок. Проверка: kubectl describe ingress <name> и логи Ingress Controller.
Игнорирование readiness/liveness проб. Поды, не готовые принимать трафик, остаются в эндпоинтах Service. Запросы падают с ошибкой. Решение: настройте readinessProbe для каждого контейнера.
Если вы проектируете маршрутизацию для микросервисной архитектуры, обратите внимание на руководство по продвинутой маршрутизации с Istio и Linkerd - там разобраны канареечные развёртывания, инжекция ошибок и настройка mTLS.
Практический пример: сквозной пайплайн с оператором и Kafka
Соберём изученные компоненты в работающую систему. Задача: внешний запрос на обработку данных проходит через Gateway API, попадает в оператор, который публикует задачу в Kafka. Воркеры читают из Kafka, обрабатывают данные и возвращают результат. При сбое воркера оператор перенаправляет задачу на другой экземпляр.
Архитектура решения
Поток данных выглядит так:
- Внешний клиент отправляет запрос на
api.example.com/process. - Gateway (NGINX Gateway Fabric) принимает запрос и маршрутизирует его на Service
pipeline-operator. - Оператор создаёт ресурс DataPipeline в Kubernetes и публикует сообщение в топик Kafka
processing-tasks. - Воркеры читают топик, выполняют обработку и пишут результат в топик
processing-results. - Оператор читает результаты, обновляет статус DataPipeline и возвращает ответ клиенту через callback URL.
- При сбое воркера оператор обнаруживает отсутствие результата по таймауту и публикует задачу повторно.
Развертывание и проверка работоспособности
Последовательность команд для запуска:
# 1. Установка Gateway API CRD и контроллера
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
kubectl apply -f nginx-gateway-fabric.yaml
# 2. Развёртывание Kafka через Strimzi
kubectl create namespace kafka
kubectl apply -f strimzi-operator.yaml -n kafka
kubectl apply -f kafka-cluster.yaml -n kafka
# 3. Создание CRD и оператора
kubectl apply -f datapipeline-crd.yaml
kubectl apply -f operator-deployment.yaml
# 4. Запуск воркеров
kubectl apply -f worker-deployment.yaml
# 5. Применение Gateway и HTTPRoute
kubectl apply -f gateway.yaml
kubectl apply -f httproute.yaml
Генерация тестовой нагрузки:
for i in {1..100}; do
curl -X POST https://api.example.com/process \
-H "Content-Type: application/json" \
-d '{"data": "test-'$i'"}'
done
Имитация сбоя воркера - удаляем под:
kubectl delete pod -l app=worker
Оператор фиксирует отсутствие результата в топике processing-results в течение таймаута 30 секунд. Статус DataPipeline переходит в degraded. Оператор публикует сообщение в processing-tasks повторно. Новый воркер подхватывает задачу, обрабатывает, результат появляется в топике результатов. Оператор обновляет статус на completed.
Для production-среды, где требуется высокая доступность, рассмотрите облачную инфраструктуру Timeweb Cloud с управляемым Kubernetes - это снимает задачу администрирования control plane и позволяет сфокусироваться на логике маршрутизации.
Вся логика восстановления инкапсулирована в операторе. Разработчикам не нужно писать код повторных попыток в каждом сервисе - достаточно описать шаги пайплайна в CRD. Система сама обрабатывает сбои и гарантирует, что каждое сообщение будет обработано.