DNS-маршрутизация для микросервисов в Kubernetes: настройка CoreDNS, Service Mesh и отказоустойчивости | AdminWiki
Timeweb Cloud — сервера, Kubernetes, S3, Terraform. Лучшие цены IaaS.
Попробовать

DNS-маршрутизация для микросервисов в Kubernetes: настройка CoreDNS, Service Mesh и отказоустойчивости

08 июня 2026 10 мин. чтения

Стабильная и предсказуемая DNS-маршрутизация - это основа взаимодействия микросервисов в кластере Kubernetes. В отличие от статичной сетевой настройки, DNS позволяет гибко управлять трафиком, реализовывать схемы балансировки и обеспечивать отказоустойчивость на уровне разрешения имён. В этом руководстве мы разберем практическую настройку CoreDNS, его интеграцию с решениями Service Mesh и создание отказоустойчивой инфраструктуры. Вы получите готовые конфигурации, которые можно применить в вашем кластере уже сегодня.

Проблемы с DNS - одна из частых причин сбоев в микросервисных архитектурах. Задержки резолвинга, некорректные ответы или полная недоступность DNS-сервера парализуют работу приложений. Мы сосредоточимся на решении этих проблем через настройку CoreDNS, политик маршрутизации в Istio/Linkerd и механизмов балансировки нагрузки.

Пошаговая настройка CoreDNS для маршрутизации сервисов

CoreDNS выступает стандартным DNS-сервером в Kubernetes, начиная с версии 1.13. Его модульная архитектура на основе плагинов позволяет гибко настраивать логику разрешения имён. Базовая конфигурация находится в ConfigMap coredns в пространстве имён kube-system.

Для реализации кастомных правил маршрутизации, например, перенаправления запросов к внутренним доменам на конкретные сервисы, используется плагин rewrite. Рассмотрим практический пример создания локальной зоны internal.company.local.

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns-custom
  namespace: kube-system
data:
  internal.override: |
    internal.company.local:53 {
        errors
        cache 30
        rewrite continue {
            name regex (.*)\.internal\.company\.local {1}.default.svc.cluster.local
            answer name (.*)\.default\.svc\.cluster\.local {1}.internal.company.local
        }
        forward . /etc/resolv.conf
    }

Этот фрагмент конфигурации создаёт отдельный сервер для зоны internal.company.local. Правило rewrite преобразует входящие запросы, например, api.internal.company.local, в стандартное Kubernetes-имя сервиса api.default.svc.cluster.local, и обратно преобразует ответ. Такой подход, известный как split-horizon DNS, позволяет использовать удобные имена для доступа к сервисам внутри кластера.

Для маршрутизации запросов к внешним доменам через определённые upstream-серверы используется директива forward. Например, чтобы все запросы к доменам зоны .corp отправлялись на корпоративный DNS-сервер 10.10.10.53, а остальные - на публичные резолверы Google, конфигурация будет выглядеть так:

.:53 {
    errors
    health
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods verified
        fallthrough in-addr.arpa ip6.arpa
    }
    forward . /etc/resolv.conf {
        max_concurrent 1000
    }
    prometheus :9153
    cache 30
    loop
    reload
    loadbalance
}

corp.override: |
    corp:53 {
        errors
        cache 30
        forward . 10.10.10.53
    }

Эти примеры покрывают базовые сценарии маршрутизации. Для более сложных случаев, таких как автоматическое управление DNS-записями извне кластера, может потребоваться инструмент вроде ExternalDNS. Принципы его работы и интеграции с GitOps-подходами подробно разобраны в нашем руководстве по автоматизации DNS через IaC и API.

Интеграция DNS с Service Mesh (Istio/Linkerd)

Service Mesh решения, такие как Istio и Linkerd, добавляют уровень интеллектуальной маршрутизации поверх базового DNS. Они перехватывают исходящие DNS-запросы от приложений через sidecar-прокси и могут динамически направлять трафик на разные версии сервиса, реализуя canary-развертывания, blue-green стратегии и инъекции ошибок.

Как Istio управляет DNS-запросами

В Istio sidecar-контейнер (istio-proxy) настраивается как локальный DNS-прокси для пода. Конфигурация задаётся через объект Sidecar. Когда приложение пытается разрешить имя сервиса, запрос сначала попадает в istio-proxy. Прокси, основываясь на правилах, определённых в VirtualService и DestinationRule, может подменить конечный адрес.

Рассмотрим пример VirtualService для canary-развертывания сервиса catalog. Правило направляет 90% трафика на стабильную версию v1 и 10% - на новую версию v2.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: catalog-vs
spec:
  hosts:
  - catalog.default.svc.cluster.local
  http:
  - route:
    - destination:
        host: catalog.default.svc.cluster.local
        subset: v1
      weight: 90
    - destination:
        host: catalog.default.svc.cluster.local
        subset: v2
      weight: 10
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: catalog-dr
spec:
  host: catalog.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

Для приложения DNS-имя catalog.default.svc.cluster.local остаётся неизменным. Вся логика маршрутизации скрыта на уровне сетевого уровня Istio. Для работы с внешними сервисами, не зарегистрированными в кластере, используется объект ServiceEntry, который явно добавляет их в service registry Istio, позволяя управлять трафиком к ним по тем же правилам.

DNS в Linkerd

Linkerd использует другой подход. Он не перехватывает DNS-запросы напрямую. Вместо этого его sidecar-прокси (linkerd-proxy) перехватывает исходящие TCP-соединения. Когда приложение устанавливает соединение, прокси разрешает целевое имя через стандартный механизм ОС (который указывает на CoreDNS), но затем применяет свои правила балансировки между найденными эндпоинтами. Для настройки продвинутой маршрутизации в Linkerd используются ресурсы ServiceProfile и TrafficSplit.

Выбор между Istio и Linkerd часто зависит от сложности задач. Если нужен детальный контроль над DNS-запросами и интеграция с внешними системами обнаружения сервисов, Istio предоставляет больше возможностей через ServiceEntry. Linkerd предлагает более простую модель, достаточную для многих сценариев маршрутизации внутри кластера. Базовые принципы работы Service и Ingress, которые лежат в основе любого Service Mesh, подробно разобраны в отдельном практическом руководстве по настройке внутреннего и внешнего доступа.

Обеспечение отказоустойчивости DNS-резолвинга

Отказоустойчивость DNS в Kubernetes - это многоуровневая задача. Она включает в себя устойчивую работу самих подов CoreDNS, настройку проверок здоровья и правильные политики повторных попыток на стороне клиентских приложений.

Развертывание и распределение CoreDNS

Стандартный Deployment CoreDNS должен быть настроен с учетом принципов распределенной работы. Критически важно использовать Pod Anti-Affinity, чтобы поды CoreDNS не размещались на одной узле, что минимизирует риск одновременного отказа.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      k8s-app: kube-dns
  template:
    metadata:
      labels:
        k8s-app: kube-dns
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - kube-dns
            topologyKey: kubernetes.io/hostname
      priorityClassName: system-cluster-critical
      serviceAccountName: coredns
      tolerations:
        - key: CriticalAddonsOnly
          operator: Exists
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
      - name: coredns
        image: coredns/coredns:1.11.1
        args: ["-conf", "/etc/coredns/Corefile"]
        volumeMounts:
        - name: config-volume
          mountPath: /etc/coredns
          readOnly: true
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9153
          name: metrics
          protocol: TCP
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          periodSeconds: 10
          failureThreshold: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: 8181
            scheme: HTTP
          initialDelaySeconds: 5
          periodSeconds: 2
        resources:
          limits:
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi

Конфигурация выше задаёт три реплики с обязательным anti-affinity, помечает поды как критически важные (priorityClassName: system-cluster-critical) и настраивает livenessProbe и readinessProbe. Проба на готовность (/ready) особенно важна, так как CoreDNS может быть временно не готов после перезагрузки конфигурации.

Мониторинг и автомасштабирование

Метрики CoreDNS доступны на порту 9153 по пути /metrics в формате Prometheus. Ключевые метрики для мониторинга:

  • coredns_dns_request_count_total - общее количество DNS-запросов.
  • coredns_dns_request_duration_seconds - гистограмма задержек обработки запросов.
  • coredns_dns_response_rcode_count_total - количество ответов по кодам (NOERROR, NXDOMAIN, SERVFAIL). Резкий рост SERVFAIL указывает на проблемы.
  • coredns_panic_count_total - счётчик паник, их наличие требует немедленного вмешательства.

На основе метрик CPU, памяти и количества запросов можно настроить Horizontal Pod Autoscaler (HPA) для CoreDNS:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: coredns-hpa
  namespace: kube-system
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: coredns
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: AverageValue
        averageValue: 100Mi

Балансировка нагрузки через DNS для внешнего трафика

DNS может выступать как инструмент грубой балансировки нагрузки для входящего трафика. Этот подход, часто называемый round-robin DNS, предполагает выдачу нескольких IP-адресов в ответ на один DNS-запрос. Клиентское приложение или локальный резолвер случайным образом выбирает один из адресов для соединения.

Настройка через ExternalDNS и Ingress

Для автоматического управления DNS-записями внешних конечных точек в Kubernetes используется ExternalDNS. Он отслеживает создание сервисов типа LoadBalancer или ресурсов Ingress и создаёт/обновляет соответствующие A или CNAME записи во внешнем DNS-провайдере (AWS Route53, Cloudflare, Azure DNS).

Пример манифеста для развертывания ExternalDNS для Cloudflare:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: k8s.gcr.io/external-dns/external-dns:v0.13.5
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=example.com
        - --provider=cloudflare
        - --cloudflare-proxied
        env:
        - name: CF_API_TOKEN
          valueFrom:
            secretKeyRef:
              name: cloudflare-api-token
              key: token

При создании Ingress-ресурса для хоста api.example.com ExternalDNS автоматически создаст в Cloudflare DNS-запись, указывающую на IP-адрес балансировщика нагрузки, предоставленный Ingress-контроллером. Если Ingress-контроллер развернут в нескольких экземплярах на разных узлах, можно создать сервис типа LoadBalancer с несколькими внешними IP (например, в облачном провайдере), и ExternalDNS добавит все эти IP в DNS-запись, реализуя балансировку на уровне DNS.

Балансировка внутри кластера с headless-сервисами

Для сценариев, когда приложению нужен прямой доступ к отдельным подам (например, для StatefulSet), используются headless-сервисы. Такой сервис создаётся с параметром clusterIP: None. DNS-запрос к имени headless-сервиса возвращает не один IP-адрес (ClusterIP), а список IP-адресов всех подов, соответствующих селектору сервиса.

apiVersion: v1
kind: Service
metadata:
  name: cassandra-headless
spec:
  clusterIP: None
  selector:
    app: cassandra
  ports:
  - port: 9042

Запрос dig cassandra-headless.default.svc.cluster.local вернёт A-записи для всех подов с меткой app: cassandra. Клиентская библиотека (например, для Cassandra или Redis) может использовать этот список для прямого подключения к экземплярам, реализуя свою логику балансировки и отказоустойчивости. Для гибридных сценариев, где требуется тонкая настройка маршрутизации на уровне доменных имён, может быть полезно наше руководство по селективной маршрутизации с V2RayTUN.

Создание изолированных DNS-зон для окружений

В мультитенантных кластерах или при разделении development, staging и production окружений часто требуется изолировать DNS-резолвинг. Например, чтобы сервис в namespace dev при запросе catalog.prod.svc.cluster.local получал заглушку или ошибку, а не доступ к реальному продакшен-сервису.

CoreDNS предоставляет для этого механизм stubDomains и upstream. Конфигурация задаётся в основном ConfigMap. Допустим, мы хотим, чтобы все запросы к зоне prod.svc.cluster.local из development-окружения отправлялись на специальный DNS-сервер, который возвращает тестовые адреса.

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods verified
            fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
    stub.override: |
      stubDomains: |
        {"prod.svc.cluster.local": ["10.100.200.10"]}

В этом примере запросы к *.prod.svc.cluster.local будут перенаправляться на DNS-сервер 10.100.200.10, а не обрабатываться плагином kubernetes. На этом сервере можно настроить любую логику: возвращать фиктивные IP, перенаправлять на версию сервиса в staging-неймспейсе или возвращать NXDOMAIN.

Более строгий метод изоляции - использование отдельных экземпляров CoreDNS для разных групп namespace. Это можно реализовать с помощью NodeSelector и отдельных Deployment, привязанных к узлам с определёнными метками, а также настроив соответствующие Service для DNS. Однако этот подход сложнее в поддержке.

Диагностика и отладка проблем DNS

Когда микросервисы не могут найти друг друга, первое, что нужно проверить - работу DNS. Вот чек-лист и набор команд для диагностики.

Базовые проверки изнутри пода

Создайте временный под с сетевыми утилитами для тестирования:

kubectl run -it --rm dns-debug --image=busybox:1.36 --restart=Never -- sh

Внутри пода выполните следующие команды:

  1. Проверка resolv.conf: cat /etc/resolv.conf. Должны быть указаны IP-адреса сервиса kube-dns (обычно 10.96.0.10) и доменный суффикс кластера (например, search default.svc.cluster.local svc.cluster.local cluster.local).
  2. Проверка доступности CoreDNS: nslookup kubernetes.default. Команда должна вернуть IP-адрес сервиса kubernetes.
  3. Детальный запрос: dig +short @10.96.0.10 catalog.default.svc.cluster.local. Укажите IP CoreDNS напрямую, чтобы исключить проблемы с локальным кэшированием.
  4. Проверка разрешения внешних имён: nslookup google.com. Это проверяет работу forward-зон и доступность upstream-резолверов.

Анализ логов и метрик CoreDNS

Если запросы не проходят, изучите логи CoreDNS:

kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

Ищите ошибки типа SERVFAIL, REFUSED или сообщения о панике (panic). Включение детального логирования можно настроить через плагин log в Corefile:

.:53 {
    log
    errors
    ...
}

Проверьте метрики CoreDNS на предмет аномалий:

kubectl port-forward -n kube-system svc/kube-dns 9153:9153

Затем откройте в браузере http://localhost:9153/metrics. Обратите внимание на высокие значения coredns_dns_response_rcode_count_total{rcode="SERVFAIL"} или растущий coredns_panic_count_total.

Типичные проблемы и решения

  • NXDOMAIN для существующего сервиса: Убедитесь, что у сервиса есть селектор, соответствующий меткам подов. Проверьте, что поды в состоянии Ready.
  • Большие задержки (более 100 мс): Возможно, исчерпан лимит concurrent-запросов в плагине forward. Увеличьте параметр max_concurrent. Проверьте нагрузку на upstream-резолверы.
  • Временные сбои (timeouts): Настройте в клиентских приложениях политики повторных попыток (retry) и экспоненциальной отсрочки (backoff) для DNS-запросов. Используйте библиотеки с кэшированием DNS на стороне клиента.
  • Проблемы с безопасностью DNS: Для защиты трафика между подами и CoreDNS можно рассмотреть развертывание решений для DNS-over-TLS (DoT). Подробные инструкции по безопасной DNS-маршрутизации, включая DoT и DNSSEC, собраны в нашем отдельном практическом руководстве.

Правильно настроенная DNS-инфраструктура становится невидимым, но критически важным слоем, обеспечивающим стабильность микросервисов. Комбинируя возможности CoreDNS, Service Mesh и механизмов балансировки, вы можете построить гибкую, отказоустойчивую и управляемую сетевую среду в Kubernetes. Для комплексного понимания сетевого стека в современных условиях рекомендую также ознакомиться с обзорным руководством по сетевым технологиям 2026 года.

Поделиться:
Сохранить гайд? В закладки браузера