CoreDNS как авторитативный DNS в Kubernetes: Corefile и зоны
Published: 2026-06-16
CoreDNS — единственный DNS-сервер в кластере Kubernetes по умолчанию. Он авторитативен для зоны cluster.local: отвечает окончательно, без обращения к upstream. Всё, что не попадает в эту зону, уходит в форвардинг. Этот пост о том, как CoreDNS становится авторитативным, как добавить собственные зоны и что смотреть, когда DNS не отвечает.
Как CoreDNS попадает в кластер
При установке кластера (k0s, k3s, kubeadm) CoreDNS разворачивается как Deployment в kube-system. Два пода, один ConfigMap с Corefile, один Service с ClusterIP — этот IP прописывается в /etc/resolv.conf каждого пода.
bashkubectl get pods -A | grep coredns
# kube-system coredns-7f9fb5c85d-4btrq 1/1 Running 0 6d
# kube-system coredns-7f9fb5c85d-9kxrp 1/1 Running 0 6d
kubectl get svc -n kube-system kube-dns
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 6d
Внутри пода:
bashcat /etc/resolv.conf
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
ndots:5 означает: если в имени меньше пяти точек, к нему будут добавлены суффиксы из search по очереди. Поэтому redis резолвится в redis.default.svc.cluster.local — CoreDNS отвечает авторитативно.
Corefile: структура плагинов
Вся конфигурация CoreDNS — один файл Corefile, который лежит в ConfigMap:
bashkubectl get configmap coredns -n kube-system -o yaml
Стандартный Corefile из k0s/kubeadm:
corefile.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
Каждый блок zone { plugins } — это сервер. Порядок плагинов внутри блока задан при компиляции CoreDNS, а не порядком строк в файле. Строки в Corefile определяют только конфигурацию плагинов, не порядок их вызова.
Плагин kubernetes — ядро авторитативности
Плагин kubernetes отвечает за зону cluster.local. Он читает объекты из API Kubernetes через informer-кэш и возвращает ответы без обращения к upstream.
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
| Параметр | Что делает |
|---|---|
pods insecure |
Создаёт A-записи для подов вида 1-2-3-4.namespace.pod.cluster.local без верификации |
pods verified |
То же, но проверяет, что IP действительно принадлежит поду — защита от DNS rebinding |
fallthrough in-addr.arpa ip6.arpa |
PTR-запросы, которые плагин не обработал, передаются следующему плагину |
ttl 30 |
TTL ответа в секундах |
Что резолвит этот плагин авторитативно:
# Service
redis.default.svc.cluster.local → ClusterIP
redis.default.svc.cluster.local (SRV) → порт + ClusterIP
# Headless Service
redis.default.svc.cluster.local → IP всех endpointов
# Pod (pods insecure)
10-244-1-5.default.pod.cluster.local → 10.244.1.5
# ExternalName Service
ext.default.svc.cluster.local → CNAME → external.host
Добавить собственную авторитативную зону
Вариант 1: плагин file (статический zone file)
Подходит для небольших зон, которые меняются редко. Зонный файл кладётся в ConfigMap и монтируется в под CoreDNS.
ConfigMap с zone file:
yamlapiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom-zones
namespace: kube-system
data:
internal.example.com.db: |
$ORIGIN internal.example.com.
$TTL 300
@ IN SOA ns1.internal.example.com. admin.internal.example.com. (
2026061601 ; serial
3600 ; refresh
900 ; retry
604800 ; expire
300 ) ; minimum TTL
@ IN NS ns1.internal.example.com.
ns1 IN A 10.96.0.10
api IN A 10.244.1.100
db IN A 10.244.2.50
* IN A 10.244.3.1
Patch CoreDNS ConfigMap — добавить блок зоны:
yamlapiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
internal.example.com:53 {
file /etc/coredns/zones/internal.example.com.db
log
errors
}
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
Монтировать зонный файл в Deployment CoreDNS:
bashkubectl edit deployment coredns -n kube-system
Добавить в spec.template.spec:
yamlvolumes:
- name: custom-zones
configMap:
name: coredns-custom-zones
containers:
- name: coredns
volumeMounts:
- name: custom-zones
mountPath: /etc/coredns/zones
readOnly: true
Плагин reload перечитывает Corefile автоматически при изменении ConfigMap — перезапуск не нужен. Зонные файлы он не перечитывает: при обновлении zone file нужен kubectl rollout restart deployment/coredns -n kube-system.
Вариант 2: stub zone — форвардинг конкретной зоны на внешний сервер
Если зона обслуживается внешним DNS (например, внутренний AD/корпоративный DNS), настраивается stub:
corefile.:53 {
errors
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
forward corp.example.com 192.168.1.53 192.168.1.54 {
policy round_robin
health_check 5s
}
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
reload
}
Запросы *.corp.example.com уйдут на 192.168.1.53/192.168.1.54. Остальное — на системный resolver. Порядок блоков forward важен: CoreDNS выбирает первый подходящий.
Проверка работы
bash# Запустить отладочный под
kubectl run dnstest --image=busybox:1.36 --rm -it --restart=Never -- sh
# Внутри пода:
nslookup redis.default.svc.cluster.local
nslookup api.internal.example.com
nslookup google.com
# Посмотреть, что реально отвечает CoreDNS
kubectl exec -n kube-system $(kubectl get pod -n kube-system -l k8s-app=kube-dns -o name | head -1) \
-- cat /etc/coredns/Corefile
# DNS-трейс из пода
nslookup -type=A -debug redis.default.svc.cluster.local 10.96.0.10
Метрики CoreDNS через Prometheus (если подключён):
promql# Запросы в секунду по зонам
rate(coredns_dns_requests_total[5m])
# Ошибки SERVFAIL
rate(coredns_dns_responses_total{rcode="SERVFAIL"}[5m])
# Латентность p99
histogram_quantile(0.99, rate(coredns_dns_request_duration_seconds_bucket[5m]))
Что может пойти не так
CoreDNS отвечает NXDOMAIN для существующего сервиса
bash# Проверить, что сервис существует
kubectl get svc redis -n default
# Проверить endpoint
kubectl get endpoints redis -n default
# Если endpoints пустые — проблема не в DNS, а в selector/pod labels
kubectl describe endpoints redis -n default
CoreDNS возвращает A-запись только если у Service есть хотя бы один Ready endpoint (для headless) или ClusterIP (для обычного). Service без подов с ClusterIP резолвится нормально — трафик просто некуда доставлять.
Loop detected — CoreDNS падает в CrashLoopBackOff
bashkubectl logs -n kube-system -l k8s-app=kube-dns | grep -i loop
# [ERROR] plugin/loop: Loop (127.0.0.1:53 -> :53) detected
Плагин loop обнаружил, что /etc/resolv.conf на ноде указывает на 127.0.0.1 (systemd-resolved или dnsmasq). CoreDNS форвардит запрос себе же.
Исправление — явно указать upstream в forward вместо /etc/resolv.conf:
corefileforward . 8.8.8.8 8.8.4.4 {
max_concurrent 1000
}
Или настроить на ноде /etc/resolv.conf так, чтобы он не указывал на localhost.
Slow DNS — все запросы занимают 5 секунд
Причина: ndots:5 вызывает перебор всех search-суффиксов для внешних имён. Запрос google.com превращается в 6 запросов:
google.com.default.svc.cluster.local → NXDOMAIN
google.com.svc.cluster.local → NXDOMAIN
google.com.cluster.local → NXDOMAIN
google.com. → ответ
Решение — явно использовать FQDN с точкой на конце для внешних имён в приложении, или снизить ndots в Pod spec:
yamlspec:
dnsConfig:
options:
- name: ndots
value: "2"
Зонный файл не перечитался после обновления ConfigMap
Плагин reload следит только за Corefile, не за зонными файлами. После изменения ConfigMap с zone file:
bashkubectl rollout restart deployment/coredns -n kube-system
kubectl rollout status deployment/coredns -n kube-system
Итого
- CoreDNS авторитативен для
cluster.localчерез плагинkubernetes— он отвечает из informer-кэша API, без upstream - Собственные зоны добавляются через плагин
file(статический zone file в ConfigMap) илиforward(stub для внешнего DNS) - Плагин
reloadперечитывает Corefile автоматически; zone file — только после рестарта подов - Для диагностики:
kubectl run dnstest,nslookup -debug, метрики:9153 - ndots:5 — источник медленного DNS для внешних имён; лечится
dnsConfig.optionsв Pod spec