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