← Все статьи

kubectl patch --type merge стёр CoreDNS, а все проверки остались зелёными

Однострочное обновление образа заменило всю спецификацию контейнера. CoreDNS потерял конфиг, тихо откатился на плагин whoami и отвечал на всё NOERROR без ответа.

В моей домашней лаборатории DNS всего кластера умер для новых запросов, а все сигналы здоровья остались зелёными. Поды падали с bad address на любое имя, но подключение по IP работало. Поды CoreDNS — 1/1 Running, Ready, ноль рестартов. Argo CD Synced, Postgres здоров, все публичные эндпоинты отдают 200. Логи CoreDNS выглядели оживлёнными и здоровыми: ровный поток NOERROR. А dig говорил status: NOERROR при ANSWER: 0.

Всё, что уже было подключено, продолжало работать. Падали только свежие запросы, поэтому проблему вообще не сразу удалось увидеть.

Изменение, которое всё вызвало

Я обновил образ CoreDNS тем, что выглядело безобидной однострочкой:

kubectl -n kube-system patch deploy coredns --type merge \
  -p '{"spec":{"template":{"spec":{"containers":[{"name":"coredns","image":"…"}]}}}}'

--type merge — это JSON Merge Patch (RFC 7386), а merge patch заменяет массивы целиком. Он не сливает элементы списка по имени — так делает strategic merge patch. В итоге весь массив containers заменился моим объектом из одного элемента, а всё, что я не повторил, удалилось: аргумент -conf /etc/coredns/Corefile, монтирование томов, порты, обе пробы и ресурсы.

Почему это выглядело здоровым

Без -conf CoreDNS не читает смонтированный Corefile. Он стартует на встроенном дефолте — одном серверном блоке с плагином whoami.

whoami отвечает на каждый запрос NOERROR, кладёт собственный адрес клиента в секцию ADDITIONAL, добавляет SRV-запись с портом клиента и оставляет ANSWER пустым. Резолверы видели пустой ответ и падали; dig +short ничего не печатал, что читается как «нет ответа»; логи выглядели как обычный трафик NOERROR. А поды были Ready, потому что readiness-пробу удалил тот же патч — контейнер без пробы всегда готов.

Как я это нашёл

Время ушло на правдоподобные ложные следы: в eBPF-карте сервисов Cilium после выката ненадолго был один из двух бэкендов CoreDNS, эндпоинты после рестарта агента висели в состоянии restoring, и я даже переключал настройку DNS-прокси Cilium. Ничто из этого не было причиной.

Сработал сырой DNS-запрос с ноды, с контрольной точкой: тот же запрос к заведомо исправному резолверу вернул один ответ, а CoreDNS — ноль ответов и две дополнительные записи. Разбор байтов показал A-запись с моим собственным адресом и SRV-запись с TTL 0 — отпечаток whoami. Быстрая проверка это подтвердила: настоящий CoreDNS отдаёт ещё /ready на 8181 и метрики на 9153, а этот слушал только порт 53.

Исправление

Я восстановил Deployment из дампа, снятого до изменения, — работа на минуты. Восстанавливать спецификацию контейнера по памяти было бы совсем другим делом. Позже в тот же день я повторил обновление через kubectl set image, и новая версия оказалась совершенно беспроблемной.

Выводы

  • --type merge для всего, где есть список, разрушителен. Контейнеры, тома, env, порты, tolerations — всё это массивы. Используйте kubectl set image, strategic merge patch (kubectl patch по умолчанию) или применяйте полный манифест.
  • Ready значит ровно столько, сколько проба за ним. Если компонент «Ready, но работает неправильно», проверьте, что проба вообще существует.
  • dig +short прячет ответы, где есть только ADDITIONAL. Прочитайте ответ целиком, прежде чем решить, что ответа не было.
  • Диагностируйте с контрольной точкой. Сравнение с заведомо исправным резолвером отделяет «врёт мой инструмент» от «сломан сервер».
  • Снимайте дамп объекта перед ручной правкой платформенного компонента и проверяйте, что спецификация пережила выкат, прежде чем ему доверять.