Новинка Виртуальный VDS сервер в Нидерландах от 390р
Выберите продукт

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступность upstream-серверов и применить обратимые исправления без одновременной остановки всех DNS-реплик.
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Состояние CrashLoopBackOff у CoreDNS и ответы SERVFAIL обычно относятся к разным уровням проблемы. В первом случае контейнер CoreDNS завершается, а Kubernetes многократно пытается его перезапустить. Во втором процесс может оставаться работоспособным, но не получать корректный ответ от вышестоящего DNS-сервера. Отдельный распространённый сценарий — петля пересылки: запрос после нескольких перенаправлений возвращается в тот же CoreDNS.

Ниже приведена последовательная диагностика для кластера, в котором есть доступ к ConfigMap CoreDNS и настройкам kubelet. Не изменяйте Corefile до сбора исходных данных: неудачная правка может одновременно вывести из строя все DNS-реплики. В managed-кластере часть действий, особенно настройка kubelet, может быть доступна только оператору платформы. Для самостоятельного развёртывания Kubernetes нужны управляемые администратором узлы: это могут быть физические серверы, облачные инстансы, другие виртуальные машины или VPS-серверы.

Как возникает петля пересылки DNS-запросов

CoreDNS обслуживает внутреннюю зону Kubernetes, часто cluster.local, а запросы к внешним именам обычно передаёт upstream-серверам через плагин forward. В Corefile может использоваться такая директива:

forward . /etc/resolv.conf

Она означает, что адреса upstream нужно брать из файла /etc/resolv.conf внутри контейнера CoreDNS. Содержимое этого файла формируется Kubernetes с учётом DNS-политики Pod и параметра resolvConf в конфигурации kubelet.

Петля возникает, если указанный в resolv.conf сервер прямо или косвенно пересылает запрос обратно в CoreDNS. Типичный пример — адрес 127.0.0.53, используемый локальным stub-резолвером systemd-resolved. Внутри обычного контейнера loopback-адрес относится к самому контейнеру, а не к узлу. Аналогично опасны 127.0.0.1, ::1, ClusterIP службы кластерного DNS и адрес локального DNS-кэша, который сам отправляет внешние запросы обратно в CoreDNS.

Плагин loop обнаруживает простую статическую петлю пересылки при запуске и завершает процесс. Kubernetes после этого запускает контейнер снова, поэтому пользователь видит CrashLoopBackOff. Отсутствие сообщения от loop не доказывает отсутствие любой возможной петли: плагин проверяет ограниченный сценарий при запуске, поэтому необходимо самостоятельно проследить весь маршрут пересылки.

Если проблема проявляется не в CoreDNS, а в DNS внутри контейнеров, дополнительно изучите, как systemd-resolved и Docker влияют на /etc/resolv.conf. Не переносите выводы о DNS узла на сеть Pod без проверки сетевой модели кластера.

Шаг 1. Зафиксировать состояние CoreDNS

Сначала получите список реплик, их узлы, IP-адреса и количество перезапусков. Метка k8s-app=kube-dns распространена, но не обязательна:

kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide

Если команда не нашла Pod, определите фактические имена Deployment, ConfigMap, Service и метки:

kubectl -n kube-system get deployments
kubectl -n kube-system get configmaps
kubectl -n kube-system get services
kubectl -n kube-system get pods --show-labels

Далее в командах используются распространённые имена coredns для Deployment и ConfigMap, kube-dns для Service и namespace kube-system. Перед выполнением замените их фактическими значениями своего кластера. Имя DNS Service нельзя считать универсальным.

Для каждой проблемной реплики проверьте события и причину завершения контейнера:

kubectl -n kube-system describe pod <имя-pod-coredns>

Особенно важны разделы Last State, Restart Count и Events. Состояние CrashLoopBackOff само по себе не является диагнозом: оно показывает только то, что контейнер неоднократно завершается.

Получите текущий и предыдущий журнал. Флаг --previous часто критичен, поскольку текущий контейнер мог только что запуститься и ещё не успеть вывести причину ошибки:

kubectl -n kube-system logs <имя-pod-coredns>
kubectl -n kube-system logs <имя-pod-coredns> --previous

Если в Pod несколько контейнеров, укажите контейнер через -c. Сообщение плагина loop о найденной петле подтверждает направление диагностики. Ошибки разбора Corefile, невозможность открыть порт или сбой другого плагина требуют отдельного исправления и не должны автоматически трактоваться как DNS-петля.

Шаг 2. Сохранить конфигурацию и определить домен кластера

Перед изменениями сделайте локальную копию ConfigMap и Deployment. В резервной копии ConfigMap находится Corefile, а манифест Deployment поможет восстановить аргументы, образ, DNS-политику, тома и проверки контейнера.

kubectl -n kube-system get configmap coredns -o yaml > coredns-configmap-backup.yaml
kubectl -n kube-system get deployment coredns -o yaml > coredns-deployment-backup.yaml
kubectl -n kube-system get configmap coredns -o jsonpath='{.data.Corefile}' > Corefile.backup

Если объекты называются иначе, подставьте найденные на предыдущем шаге имена. Просмотрите Corefile без редактирования и найдите директивы kubernetes, forward, loop, reload, health, ready и prometheus:

kubectl -n kube-system get configmap coredns -o jsonpath='{.data.Corefile}'

Аргумент после директивы kubernetes обычно показывает домен кластера. Например, строка:

kubernetes cluster.local in-addr.arpa ip6.arpa

означает, что полное имя стандартной службы API будет выглядеть как kubernetes.default.svc.cluster.local. Если в Corefile указан другой домен, во всех последующих тестах используйте его. Далее запись <домен-кластера> означает фактическое значение из Corefile, а не обязательно cluster.local.

Уточните имя и ClusterIP DNS Service:

kubectl -n kube-system get services
kubectl -n kube-system get service <имя-DNS-Service> -o jsonpath='{.spec.clusterIP}'

Не заменяйте весь Corefile шаблоном из другого кластера: блок kubernetes, домен кластера, обратные зоны и набор скомпилированных плагинов могут различаться.

Шаг 3. Проверить, куда указывает resolv.conf

Если хотя бы одна реплика остаётся запущенной достаточно долго, выведите файл непосредственно из контейнера:

kubectl -n kube-system exec <имя-pod-coredns> -- cat /etc/resolv.conf

При постоянном падении контейнера команда может не успеть выполниться. Тогда изучите DNS-политику Deployment и узел, на котором запускается CoreDNS:

kubectl -n kube-system get deployment coredns -o jsonpath='{.spec.template.spec.dnsPolicy}'
kubectl -n kube-system get pod <имя-pod-coredns> -o jsonpath='{.spec.nodeName}'

При политике Default Pod получает DNS-настройки узла в форме, подготовленной kubelet. Точный источник зависит от параметров kubelet и операционной системы. Нельзя считать, что контейнер всегда получает буквальную копию узлового /etc/resolv.conf.

Если для CoreDNS указана ClusterFirst, внимательно проверьте итоговый контейнерный resolv.conf: пересылка CoreDNS на кластерный DNS Service может замкнуть запрос на самого себя. Не меняйте DNS-политику автоматически — сначала сравните её с документацией и способом установки конкретного кластера.

На выбранном узле проверьте обычный файл и его реальную цель:

cat /etc/resolv.conf
readlink -f /etc/resolv.conf

Если используется systemd-resolved, дополнительно изучите его состояние и файл с фактическими upstream-серверами:

resolvectl status
cat /run/systemd/resolve/resolv.conf

Путь /run/systemd/resolve/resolv.conf распространён, но не универсален. Использовать его в настройке kubelet можно только после проверки, что файл существует, содержит реальные адреса доступных DNS-серверов и не указывает обратно на кластерный DNS.

Проверьте все строки nameserver. Подозрительными являются:

  • 127.0.0.53 — локальный stub systemd-resolved;
  • 127.0.0.1, ::1 и другие loopback-адреса;
  • ClusterIP фактической службы кластерного DNS;
  • IP локального DNS-агента, который пересылает запросы обратно в CoreDNS;
  • недоступный IPv6-адрес при отсутствии корректной IPv6-маршрутизации;
  • адрес, доступный с узла, но заблокированный для сети Pod.

Сравните найденные адреса с ClusterIP проверенной DNS Service:

kubectl -n kube-system get service <имя-DNS-Service> -o jsonpath='{.spec.clusterIP}'

Шаг 4. Проследить цепочку до upstream DNS

Наличие адреса в resolv.conf ещё не подтверждает петлю. Нужно определить, куда сервер отправляет запрос дальше. Составьте цепочку:

CoreDNS → адрес из forward → следующий DNS → конечный рекурсивный сервер

Для каждого промежуточного DNS проверьте конфигурацию пересылки. Если используется NodeLocal DNSCache или другой агент на узлах, отдельно выясните, куда он направляет обычные внешние запросы. Схема, в которой CoreDNS пересылает запрос локальному кэшу, а тот возвращает их в CoreDNS, остаётся петлёй, даже если адреса компонентов различаются.

Особое внимание уделите различию между адресом внутри сетевого пространства Pod и адресом узла. Loopback контейнера, loopback узла и ClusterIP — разные точки назначения. Также проверьте NetworkPolicy: она может разрешать запрос с узла, но запрещать исходящий UDP- или TCP-трафик от Pod.

Шаг 5. Проверить upstream отдельно от кластерного DNS

Для диагностики нужен контейнер с dig или аналогичной утилитой. Используйте одобренный в вашей инфраструктуре образ, а не случайный публичный образ:

kubectl -n kube-system run dns-debug --image=<одобренный-образ-с-dig> --restart=Never --command -- sleep 3600

Получите ClusterIP фактической DNS Service и сохраните его в переменную текущего сеанса оболочки:

DNS_SERVICE_IP=$(kubectl -n kube-system get service <имя-DNS-Service> -o jsonpath='{.spec.clusterIP}')

Сначала запросите внутреннее имя через CoreDNS, затем внешнее. Замените <домен-кластера> значением из директивы kubernetes в Corefile:

kubectl -n kube-system exec dns-debug -- dig @${DNS_SERVICE_IP} kubernetes.default.svc.<домен-кластера> A +time=2 +tries=1
kubectl -n kube-system exec dns-debug -- dig @${DNS_SERVICE_IP} example.com A +time=2 +tries=1

Если внутреннее имя разрешается, а внешнее возвращает SERVFAIL или истекает по тайм-ауту, блок kubernetes и маршрут к службе CoreDNS, вероятнее всего, работают. Продолжайте проверку на участке от CoreDNS до upstream.

Обратитесь непосредственно к каждому upstream-серверу, указанному в Corefile или resolv.conf:

kubectl -n kube-system exec dns-debug -- dig @<upstream-ip> example.com A +time=2 +tries=1
kubectl -n kube-system exec dns-debug -- dig @<upstream-ip> example.com A +tcp +time=2 +tries=1

Первая команда проверяет обычный запрос по UDP, вторая — по TCP. DNS использует оба транспорта на порту 53. Блокировка TCP может проявляться только для части запросов, например после усечённого UDP-ответа. При использовании IPv6 отдельно убедитесь, что у Pod есть маршрут к IPv6-адресу upstream и правила безопасности разрешают нужный трафик.

Результат теста из произвольного отладочного Pod не всегда совпадает с результатом CoreDNS: NetworkPolicy может выбирать Pod по меткам или namespace. Если политики ограничивают egress, запускайте диагностику с осознанно выбранными метками либо проверяйте соединение непосредственно из работающей реплики CoreDNS. После диагностики удалите временный Pod:

kubectl -n kube-system delete pod dns-debug

Почему исправный health endpoint не исключает SERVFAIL

Нужно различать три механизма. Плагин health сообщает, что процесс CoreDNS способен обслуживать свой HTTP endpoint. Плагин ready отражает готовность участвующих плагинов. Встроенные проверки плагина forward оценивают состояние отдельных upstream DNS-серверов после сетевых ошибок.

CoreDNS может успешно отвечать на liveness- и readiness-пробы, но возвращать клиентам SERVFAIL, если upstream недоступен, отклоняет рекурсивные запросы или не может разрешить нужную зону. Поэтому статус READY 1/1 не заменяет прямой DNS-запрос. Проверьте параметры HTTP-проб в Deployment и события Pod:

kubectl -n kube-system get deployment coredns -o yaml
kubectl -n kube-system describe pod <имя-pod-coredns>

Как проверить health checks плагина forward

Выведите Corefile и найдите полный блок forward, а не только строку с адресами:

kubectl -n kube-system get configmap coredns -o jsonpath='{.data.Corefile}'

Сверьте параметры health_check и max_fails. Если health_check не указан, используется встроенный интервал проверки. Значение max_fails определяет, после какого количества неудачных health checks upstream считается неработоспособным. При max_fails 0 проверки отключаются, а upstream не помечаются как недоступные.

Проверка forward запускается после сетевой ошибки и отправляет DNS-запрос выбранному upstream. Ответ с DNS-кодом, включая отрицательный код, может подтверждать сетевую доступность сервера. Поэтому health check проверяет возможность обмена DNS-сообщениями, но не гарантирует, что upstream корректно разрешает конкретную внешнюю зону.

Чтобы сверить загруженную конфигурацию, найдите путь тома в volumeMounts Deployment, затем прочитайте Corefile по фактическому пути. Не предполагайте, что он одинаков для всех сборок:

kubectl -n kube-system get deployment coredns -o yaml
kubectl -n kube-system exec <имя-pod-coredns> -- cat <путь-к-смонтированному-Corefile>

Проверьте журналы всех реплик. Наличие конкретных сообщений зависит от сборки, подключённых плагинов и настроек журналирования, поэтому отсутствие строк о health check не следует считать доказательством исправности upstream:

kubectl -n kube-system logs deployment/coredns --all-pods=true --since=15m | grep -Ei 'forward|health|timeout|servfail|refused|unreachable'

Если в Corefile включён плагин prometheus, найдите его адрес и порт. При прослушивании метрик на порту 9153 можно временно перенаправить порт одной реплики:

kubectl -n kube-system port-forward pod/<имя-pod-coredns> 9153:9153

В другом терминале найдите относящиеся к forward метрики:

curl -s http://127.0.0.1:9153/metrics | grep -E 'coredns_(proxy_healthcheck_failures_total|forward_healthcheck_broken_total|proxy_request_duration_seconds|forward_max_concurrent_rejects_total)'

Рост счётчика неудачных health checks для конкретного upstream указывает на проблемы обмена с ним. Счётчики нужно сравнивать во времени: ненулевое историческое значение само по себе не доказывает, что сбой продолжается. Имена отдельных метрик зависят от сборки и версии, поэтому сначала просмотрите полный вывод /metrics.

Если prometheus отсутствует, не добавляйте его во время аварийного восстановления только ради одной проверки. Используйте прямые запросы к upstream, журналы и уже существующую систему мониторинга.

Вариант исправления 1. Передать kubelet корректный resolv.conf

Этот вариант подходит, если Corefile должен сохранять forward . /etc/resolv.conf, а причиной стал ошибочный файл, передаваемый kubelet. В конфигурации kubelet параметр обычно называется resolvConf. Способ задания и расположение конфигурационного файла зависят от метода установки Kubernetes.

Сначала выясните фактический способ запуска kubelet. Не редактируйте предполагаемый путь вслепую: изменение неиспользуемого файла не даст результата, а одновременный перезапуск kubelet на всех узлах может нарушить работу кластера. Если API kubelet и права доступа позволяют, текущее значение можно попытаться получить через configz:

NODE=<имя-узла>
kubectl get --raw "/api/v1/nodes/${NODE}/proxy/configz"

В ответе найдите resolvConf. В защищённых кластерах endpoint может быть недоступен — тогда проверяйте параметры процесса и фактически используемый конфигурационный файл непосредственно на узле.

Сохранение исходной настройки и подготовка отката

Полноценного автоматического отката изменения resolvConf нет. До правки зафиксируйте фактический путь к конфигурации kubelet, способ запуска процесса и исходное значение параметра. Если kubelet читает YAML-файл, создайте его резервную копию с сохранением прав и владельца:

sudo cp -a <фактический-файл-конфигурации-kubelet> <фактический-файл-конфигурации-kubelet>.before-dns-fix

Также сохраните содержимое текущего и планируемого файлов resolv.conf:

sudo cat <текущее-значение-resolvConf> > kubelet-resolvconf-before.txt
sudo cat <новый-путь-resolvConf> > kubelet-resolvconf-candidate.txt

Если параметр передаётся через аргумент процесса, unit-файл, drop-in или систему управления узлами, сохраните именно этот источник. Простая копия произвольного YAML не поможет при откате, если kubelet его не читает.

Для узла с systemd-resolved корректным источником часто оказывается файл с реальными upstream-адресами, а не stub-файл с 127.0.0.53. Пример фрагмента KubeletConfiguration:

resolvConf: /run/systemd/resolve/resolv.conf

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

Безопасное применение по одному узлу

Выберите один узел для канареечной проверки. Учитывайте, что перезапуск kubelet влияет на управление Pod этого узла. При необходимости используйте принятую в организации процедуру обслуживания, включая запрет нового размещения нагрузок и эвакуацию тех Pod, которые допустимо перемещать.

Измените фактически используемое значение resolvConf, затем перезапустите kubelet способом, соответствующим установке кластера. Для узла с systemd это часто выполняется через службу, но название и способ управления нельзя считать универсальными:

sudo systemctl restart kubelet
sudo systemctl status kubelet --no-pager

Из управляющего окружения убедитесь, что тестовый узел вернулся в состояние Ready:

kubectl get node <имя-узла>
kubectl describe node <имя-узла>

Возврата узла в Ready недостаточно. До изменения остальных узлов нужно проверить, какой resolv.conf kubelet формирует для нового Pod. Создайте файл kubelet-resolvconf-canary.yaml и укажите изменённый узел и одобренный образ с утилитой dig:

apiVersion: v1
kind: Pod
metadata:
  name: kubelet-resolvconf-canary
  namespace: kube-system
spec:
  nodeName: <имя-изменённого-узла>
  dnsPolicy: Default
  restartPolicy: Never
  containers:
    - name: dns-debug
      image: <одобренный-образ-с-dig>
      command: ["sleep", "3600"]

Политика Default нужна, чтобы проверить DNS-конфигурацию узла, подготовленную kubelet, а не ClusterIP кластерного DNS. Примените манифест и дождитесь запуска:

kubectl apply -f kubelet-resolvconf-canary.yaml
kubectl -n kube-system wait --for=condition=Ready pod/kubelet-resolvconf-canary --timeout=120s
kubectl -n kube-system get pod kubelet-resolvconf-canary -o wide

Убедитесь, что Pod действительно размещён на изменённом узле, затем проверьте сформированный файл и разрешение внешнего имени:

kubectl -n kube-system exec kubelet-resolvconf-canary -- cat /etc/resolv.conf
kubectl -n kube-system exec kubelet-resolvconf-canary -- dig example.com A +time=2 +tries=1
kubectl -n kube-system exec kubelet-resolvconf-canary -- dig example.com A +tcp +time=2 +tries=1

В /etc/resolv.conf не должно быть ошибочного stub-, loopback- или кластерного адреса, замыкающего пересылку. Указанные nameserver должны быть достижимы из Pod по UDP и TCP. Если CoreDNS использует dnsPolicy: Default, такой Pod проверяет тот же принцип формирования DNS-настроек, который применяется к новой реплике CoreDNS.

После проверки удалите канареечный Pod:

kubectl -n kube-system delete pod kubelet-resolvconf-canary

Только после успешного возврата узла в Ready, проверки нового Pod, его /etc/resolv.conf и DNS-запросов можно поочерёдно продолжать изменение на остальных узлах. Не перезапускайте kubelet одновременно на всей группе.

Откат настройки kubelet

Если узел не возвращается в Ready, kubelet не запускается, новый Pod получает неправильный resolv.conf или не может обратиться к upstream, восстановите исходное значение в том же источнике. Для YAML-файла это можно сделать из созданной копии:

sudo cp -a <фактический-файл-конфигурации-kubelet>.before-dns-fix <фактический-файл-конфигурации-kubelet>

После восстановления перезапустите kubelet и снова проверьте состояние тестового узла:

sudo systemctl restart kubelet
sudo systemctl status kubelet --no-pager
kubectl get node <имя-узла>
kubectl describe node <имя-узла>

Создайте новый канареечный Pod повторно, если нужно подтвердить восстановление прежнего поведения. Не переходите к другим узлам, пока тестовый узел не восстановлен. Если локальный откат не возвращает kubelet в рабочее состояние, остановите массовое изменение и используйте процедуру восстановления, предусмотренную способом развёртывания кластера.

Файл /etc/resolv.conf уже существующих Pod обычно не перестраивается после изменения kubelet. После успешного изменения на нужных узлах пересоздайте реплики CoreDNS управляемым развёртыванием:

kubectl -n kube-system rollout restart deployment/coredns
kubectl -n kube-system rollout status deployment/coredns

Перед перезапуском проверьте число реплик, стратегию Deployment, доступные ресурсы и ограничения размещения:

kubectl -n kube-system get deployment coredns -o yaml

При одной реплике возможен перерыв в работе DNS. При стратегии RollingUpdate и доступной surge-ёмкости новая реплика может стать готовой до остановки старой. Результат зависит от maxSurge, maxUnavailable, ресурсов узлов, правил размещения и способности новой реплики пройти readiness-пробу. Не запускайте одновременно перезапуск CoreDNS и обслуживание kubelet на всех узлах.

Вариант исправления 2. Указать upstream непосредственно в Corefile

Если стабильные адреса рекурсивных DNS-серверов известны и доступны из сети Pod, зависимость CoreDNS от узлового resolv.conf можно убрать. В существующем Corefile замените только целевую директиву forward, сохранив блок kubernetes и остальные параметры кластера.

forward . 10.20.0.53 10.20.0.54 {
    policy sequential
    health_check 5s
    max_fails 2
}

Адреса в примере нельзя копировать как готовые. Укажите DNS-серверы своей инфраструктуры. Они должны разрешать необходимые внешние зоны, принимать запросы от сети Pod и не пересылать их обратно в кластерный CoreDNS.

Плагин forward поддерживает несколько upstream и проверки их состояния. Параметр health_check задаёт интервал повторных проверок нездорового upstream, а max_fails влияет на признание сервера неработоспособным. Политика sequential последовательно выбирает серверы; это удобно, когда один из них считается основным. Не добавляйте параметры без понимания их влияния и не задавайте слишком низкое значение max_concurrent: при достижении лимита новые запросы будут отклоняться.

Шаг 6. Подготовить и безопасно применить Corefile

Создайте отдельный рабочий Corefile и манифест из текущего объекта:

kubectl -n kube-system get configmap coredns -o jsonpath='{.data.Corefile}' > Corefile.new
kubectl -n kube-system get configmap coredns -o yaml > coredns-configmap-new.yaml

Откройте Corefile.new в текстовом редакторе и измените только нужный блок forward. Затем сравните его с сохранённой версией:

diff -u Corefile.backup Corefile.new

После проверки откройте coredns-configmap-new.yaml. В поле data.Corefile замените старый многострочный блок содержимым Corefile.new. Сохраните отступы YAML: строки Corefile должны находиться под конструкцией Corefile: | и иметь одинаковый дополнительный отступ.

Не удаляйте другие ключи из раздела data, если они присутствуют в исходной ConfigMap. Сохраните фактические имя, namespace, метки и необходимые аннотации. Из рабочего манифеста удалите серверные поля, которые не нужны для повторного применения:

  • metadata.creationTimestamp;
  • metadata.resourceVersion;
  • metadata.uid;
  • metadata.managedFields, если поле присутствует;
  • автоматические служебные аннотации, которые не должны переноситься в управляемый манифест.

Упрощённая структура выглядит так:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        kubernetes <домен-кластера> in-addr.arpa ip6.arpa
        forward . 10.20.0.53 10.20.0.54 {
            health_check 5s
            max_fails 2
        }
        cache 30
        loop
        reload
    }

Это пример структуры, а не готовый Corefile для копирования. Не заменяйте им фактические блоки, зоны и плагины своего кластера. Значение <домен-кластера> также нужно заменить фактическим доменом из сохранённого Corefile.

Проверьте YAML через клиентский и, если разрешено, серверный dry-run:

kubectl apply --dry-run=client -f coredns-configmap-new.yaml
kubectl apply --dry-run=server -f coredns-configmap-new.yaml

Dry-run проверяет объект Kubernetes, но не гарантирует, что CoreDNS сможет разобрать Corefile. Corefile чувствителен к структуре блоков, названиям плагинов и их наличию в конкретной сборке. Надёжнее дополнительно валидировать конфигурацию в тестовом окружении с тем же образом CoreDNS.

Перед применением ещё раз сравните поле Corefile из рабочего манифеста с отдельным файлом:

kubectl apply --dry-run=client -f coredns-configmap-new.yaml -o jsonpath='{.data.Corefile}' > Corefile.from-manifest
diff -u Corefile.new Corefile.from-manifest

Если различий нет, примените подготовленный манифест:

kubectl apply -f coredns-configmap-new.yaml

Если в Corefile включён плагин reload, CoreDNS периодически обнаруживает изменение файла и пытается применить новую конфигурацию без обязательного завершения процесса. При ошибке разбора работающая конфигурация сохраняется, а ошибка появляется в журнале. Однако изменение адресов или портов HTTP-обработчиков и метрик имеет дополнительные риски, поэтому не совмещайте его с исправлением DNS-петли.

Следите за Pod и журналами всех реплик:

kubectl -n kube-system get pods -l k8s-app=kube-dns -w
kubectl -n kube-system logs deployment/coredns --all-pods=true --since=10m

Если фактические метки или имя Deployment отличаются, подставьте их. Если reload отсутствует или ConfigMap не смонтирован как ожидается, понадобится контролируемый перезапуск Deployment:

kubectl -n kube-system get deployment coredns -o yaml
kubectl -n kube-system rollout restart deployment/coredns
kubectl -n kube-system rollout status deployment/coredns

Не удаляйте одновременно все Pod вручную. Deployment обычно восстановит их, но доступность DNS будет зависеть от скорости запуска, размещения и готовности новых реплик.

Шаг 7. Проверить результат

После применения исправления убедитесь, что счётчик перезапусков перестал расти:

kubectl -n kube-system get pods -l k8s-app=kube-dns

Просмотрите свежие журналы всех реплик и убедитесь, что сообщения о петле, ошибках загрузки Corefile и постоянных тайм-аутах upstream больше не появляются:

kubectl -n kube-system logs deployment/coredns --all-pods=true --since=10m

Создайте временный диагностический Pod и получите ClusterIP проверенной DNS Service:

kubectl -n kube-system run dns-debug --image=<одобренный-образ-с-dig> --restart=Never --command -- sleep 3600
DNS_SERVICE_IP=$(kubectl -n kube-system get service <имя-DNS-Service> -o jsonpath='{.spec.clusterIP}')

Выполните несколько независимых тестов:

kubectl -n kube-system exec dns-debug -- dig @${DNS_SERVICE_IP} kubernetes.default.svc.<домен-кластера> A +time=2 +tries=1
kubectl -n kube-system exec dns-debug -- dig @${DNS_SERVICE_IP} example.com A +time=2 +tries=1
kubectl -n kube-system exec dns-debug -- dig @${DNS_SERVICE_IP} example.com A +tcp +time=2 +tries=1

Замените <домен-кластера> фактическим значением из Corefile. Помимо этого проверьте имя одной из прикладных служб в её namespace, повторите внешние запросы несколько раз и при использовании IPv6 выполните запрос AAAA.

Также проверьте каждую реплику напрямую по её Pod IP. Так можно обнаружить ситуацию, когда служба распределяет запросы между исправным и неисправным экземпляром:

kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system exec dns-debug -- dig @<pod-ip-coredns> kubernetes.default.svc.<домен-кластера> A +time=2 +tries=1
kubectl -n kube-system exec dns-debug -- dig @<pod-ip-coredns> example.com A +time=2 +tries=1

Если включены метрики, повторно проверьте счётчики ошибок health checks и ответы по каждому upstream. Сравнивайте значения во времени. После завершения проверок удалите временный Pod:

kubectl -n kube-system delete pod dns-debug

Как интерпретировать типичные результаты

CoreDNS падает с сообщением loop

Проверьте аргумент forward, содержимое контейнерного /etc/resolv.conf и всю цепочку промежуточных DNS. Не удаляйте плагин loop ради прекращения перезапусков: это скроет защитную реакцию, но оставит петлю, и DNS-запросы продолжат циркулировать до тайм-аута.

Внутренние имена работают, внешние возвращают SERVFAIL

Вероятная область неисправности — forward, upstream, маршрут или сетевые ограничения. Проверьте прямой запрос к каждому upstream, право на рекурсию, UDP и TCP 53. Успешный ответ с узла не гарантирует доступ из сети Pod.

Все запросы к ClusterIP завершаются тайм-аутом

Проверьте, существуют ли готовые endpoints фактической DNS Service, совпадает ли selector службы с метками Pod и слушает ли CoreDNS ожидаемый порт:

kubectl -n kube-system get service <имя-DNS-Service> -o yaml
kubectl -n kube-system get endpoints <имя-DNS-Service> -o wide
kubectl -n kube-system get endpointslice -l kubernetes.io/service-name=<имя-DNS-Service>

Это уже не обязательно проблема upstream: запрос может вообще не доходить до CoreDNS.

Прямой запрос к upstream работает, а через CoreDNS — нет

Сравните источник тестового трафика, NetworkPolicy, фактически загруженную конфигурацию и журналы forward. Убедитесь, что проверяли именно тот адрес, который использует CoreDNS, и что ConfigMap успела обновиться во всех репликах.

Health checks forward постоянно завершаются ошибкой

Проверьте маршрут от Pod CoreDNS до каждого upstream, правила egress, UDP и TCP, а также исходящий адрес, который видит DNS-сервер. Если upstream отвечает обычным клиентским запросам, но не health check, сравните параметры проверки и политику доступа DNS-сервера. Не отключайте проверки через max_fails 0 только для сокрытия сетевой неисправности.

Ошибки появились только после изменения ConfigMap

Вероятна синтаксическая ошибка, неподдерживаемая директива или неверный адрес. Немедленно сравните Corefile с резервной копией и выполните откат, не продолжая серию случайных правок.

Откат Corefile

Если после изменения число готовых реплик уменьшается или DNS перестаёт отвечать, восстановите сохранённую ConfigMap:

kubectl apply -f coredns-configmap-backup.yaml

Экспортированная резервная копия может содержать серверные поля. Если API отклоняет её применение, создайте копию файла, удалите resourceVersion, uid, creationTimestamp и managedFields, после чего примените очищенный манифест.

При работающем reload валидная старая конфигурация должна быть перечитана автоматически. Проверьте журналы. Если автоматическое перечитывание не произошло и в кластере остаются работоспособные реплики, выполните контролируемый перезапуск:

kubectl -n kube-system rollout restart deployment/coredns
kubectl -n kube-system rollout status deployment/coredns

После отката повторите внутренние и внешние DNS-запросы. Восстановление статуса Running недостаточно: процесс может работать, но продолжать возвращать SERVFAIL.

Что не следует делать

  • Не удаляйте loop как «исправление» обнаруженной петли.
  • Не указывайте публичный или случайный DNS без проверки требований безопасности, маршрутизации и правил организации.
  • Не копируйте целиком Corefile из другого кластера.
  • Не считайте cluster.local обязательным доменом и не используйте его в тестах без проверки Corefile.
  • Не считайте kube-dns обязательным именем Service — сначала найдите фактический объект.
  • Не меняйте одновременно kubelet на всех узлах.
  • Не продолжайте массовое изменение resolvConf, проверив только состояние Ready. Сначала создайте новый Pod на канареечном узле и проверьте его /etc/resolv.conf.
  • Не правьте resolvConf без фиксации исходного значения и проверенного способа отката.
  • Не перезапускайте все реплики CoreDNS одновременным ручным удалением.
  • Не считайте успешную HTTP health-пробу доказательством исправной внешней DNS-резолюции.
  • Не отключайте health checks forward для маскировки недоступного upstream.
  • Не проверяйте только UDP: часть ответов требует корректной работы TCP.
  • Не делайте вывод об отсутствии петли только потому, что плагин loop не завершил процесс.

Особенности закрытого managed-кластера

В управляемом кластере доступ к узлам, параметрам kubelet или ConfigMap CoreDNS может быть ограничен. В таком случае самостоятельно исправить источник resolv.conf невозможно. Соберите имена и состояние Pod, предыдущие журналы CoreDNS, события, текущий Corefile, фактические имя и ClusterIP DNS Service, endpoints, список затронутых узлов, время возникновения проблемы и результаты запросов к внутренним и внешним именам.

Если доступен мониторинг, добавьте значения метрик ошибок forward с разбивкой по upstream и интервал, на котором они росли. Передайте данные оператору платформы через предусмотренный канал поддержки. Нельзя заранее обещать конкретное изменение kubelet, Corefile или сетевой инфраструктуры: доступные действия и границы ответственности определяются устройством managed-сервиса.

Краткий порядок восстановления

  1. Получить describe, текущие и предыдущие логи всех проблемных реплик.
  2. Определить фактические имена Deployment, ConfigMap и DNS Service.
  3. Сохранить ConfigMap CoreDNS, Corefile и манифест Deployment.
  4. Определить фактический домен кластера по директиве kubernetes в Corefile.
  5. Проверить директиву forward и содержимое /etc/resolv.conf контейнера.
  6. Сравнить nameserver с ClusterIP DNS, loopback-адресами и адресами локальных агентов.
  7. Проследить цепочку пересылки до конечного рекурсивного DNS.
  8. Проверить upstream из сети Pod по UDP и TCP.
  9. Сверить health_check, max_fails, журналы и доступные метрики forward.
  10. Перед правкой kubelet сохранить фактическую конфигурацию и исходное значение resolvConf.
  11. Изменить kubelet сначала на одном узле и дождаться его возврата в Ready.
  12. Создать новый Pod на изменённом узле, проверить его /etc/resolv.conf и DNS-запросы по UDP и TCP.
  13. Только после канареечной проверки продолжить изменение на остальных узлах.
  14. Исправить источник resolv.conf либо указать проверенные upstream в Corefile.
  15. Применить Corefile через reload или контролируемый rollout.
  16. Проверить внутренние и внешние имена через ClusterIP и каждую реплику, используя фактический домен кластера.
  17. При ухудшении восстановить резервную ConfigMap или исходную настройку kubelet.

Главный принцип диагностики — разделять работоспособность процесса CoreDNS, обслуживание зон Kubernetes, HTTP-пробы и пересылку внешних запросов. Такой подход позволяет отличить петлю от недоступного upstream, ошибки health checks и проблемы маршрута к службе DNS, а затем внести минимальное, проверяемое и обратимое изменение.

Поделиться статьей

Вам будет интересно

Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...
Nginx debug log на VDS: включаем отладку только для нужного IP OpenAI Статья написана AI (GPT 5)

Nginx debug log на VDS: включаем отладку только для нужного IP

Показываем, как на VDS включить подробный nginx debug log не для всего трафика, а только для вашего IP: проверить сборку Nginx, на ...
Netplan на Ubuntu VDS: статический IPv4/IPv6, default route, DNS и безопасное применение по SSH OpenAI Статья написана AI (GPT 5)

Netplan на Ubuntu VDS: статический IPv4/IPv6, default route, DNS и безопасное применение по SSH

Пошаговая инструкция по Netplan для Ubuntu VDS: как найти интерфейс, прописать static IP, IPv6, default route и DNS, проверить net ...