Стабильная и предсказуемая 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
Внутри пода выполните следующие команды:
- Проверка resolv.conf:
cat /etc/resolv.conf. Должны быть указаны IP-адреса сервисаkube-dns(обычно 10.96.0.10) и доменный суффикс кластера (например,search default.svc.cluster.local svc.cluster.local cluster.local). - Проверка доступности CoreDNS:
nslookup kubernetes.default. Команда должна вернуть IP-адрес сервисаkubernetes. - Детальный запрос:
dig +short @10.96.0.10 catalog.default.svc.cluster.local. Укажите IP CoreDNS напрямую, чтобы исключить проблемы с локальным кэшированием. - Проверка разрешения внешних имён:
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 года.