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. Прочитайте ответ целиком, прежде чем решить, что ответа не было.- Диагностируйте с контрольной точкой. Сравнение с заведомо исправным резолвером отделяет «врёт мой инструмент» от «сломан сервер».
- Снимайте дамп объекта перед ручной правкой платформенного компонента и проверяйте, что спецификация пережила выкат, прежде чем ему доверять.