← Все статьи

Четыре проверки, которые врали, пока система была здорова

За один день миграций платформы четыре проверки сообщали об ошибке, хотя всё было в порядке. Когда один сигнал спорит со всеми остальными, первым подозревайте сам сигнал.

За один рабочий день миграций платформы в домашней лаборатории четыре разные проверки сообщали об ошибке или отсутствии объекта, хотя проверяемое всё это время было здорово. Каждая выглядела как настоящий дефект. Каждая стоила мне от нескольких минут до часа работы не в ту сторону.

Отдельные баги — мелочь. Закономерность — нет: когда система зелёная везде, кроме одного сигнала, сломан, скорее всего, именно этот сигнал.

1. Поле, которого нет в этой версии

Четыре минуты я ждал .status.valid у VaultStaticSecret. Эта версия оператора вместо этого отдаёт условия SecretSynced, Healthy и Ready. jsonpath к отсутствующему полю возвращает пустую строку, и её не отличить от «ещё не готово».

2. Селектор меток, который ничего не находил

Пять минут я опрашивал под плагина по app.kubernetes.io/name, хотя под был в Running с другими метками. Пустой список читается как «ещё не создан» и никогда не превращается в ошибку по таймауту.

3. Неоднозначное короткое имя

Когда установлен Longhorn, kubectl get backup разрешается в backups.longhorn.io, а не в нужный мне backups.postgresql.cnpg.io от CloudNativePG. Завершённый бэкап базы на каждом опросе выглядел без статуса, а delete падал с backups.longhorn.io ... not found.

4. Не та семантика apply

kubectl apply --dry-run=server отверг корректный манифест. Клиентский трёхсторонний merge не может знать, что отсутствующее в файле поле нужно удалить, если у живого объекта нет аннотации last-applied-configuration. А этот объект создал Argo CD через server-side apply. Проверка с --server-side --field-manager=argocd-controller приняла его сразу.

То же семейство, но в обратную сторону

Ещё две проверки на той же неделе врали в противоположном направлении:

  • curl -w '%{http_code}' возвращает 000 при любом сбое транспорта. Настоящий ответ был в коде выхода — 35, ошибка TLS, то есть соединение на самом деле установилось. Прочитав 000 как «нет связности», я ушёл разбираться в сеть, а не в SNI.
  • Неудачный патч удалил у контейнера readiness-пробу, и под навсегда стал Ready: контейнер без пробы готов «по определению». Это обошлось в двухчасовой отказ DNS.

Второе направление опаснее. Проверка, ложно сообщающая об ошибке, стоит времени. Проверка, ложно сообщающая об успехе, прячет аварию, и ничто не заставляет вернуться к ней ещё раз.

Привычка, которая решает все случаи

  • Когда всё остальное зелёное, первым подозревайте проверку. Если ноды, поды, контроллеры и трафик здоровы, а не согласна ровно одна проба, она и есть главный подозреваемый.
  • К каждой проверке — контроль. Положительный результат ничего не значит без заведомо хорошего сравнения, отрицательный — без заведомо плохого.
  • Ответ протокола важнее сводки обёртки. pg_stat_archiver вместо условия ContinuousArchiving, настоящий логин вместо «Secret существует».
  • Проверьте словарь самой пробы. Существует ли поле в этой версии, совпадает ли селектор, однозначно ли имя ресурса, тот ли это механизм apply, которым пользуется настоящий исполнитель?

Контроль тоже бывает разрушительным

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

Что взять с собой

  • Один красный сигнал среди множества зелёных скорее всего означает сломанный сигнал.
  • Пустой вывод — не успех. Проба должна различать «ещё нет» и «не могу ответить».
  • Используйте полные имена ресурсов, когда у двух CRD одинаковый kind.
  • Проверяйте тем же механизмом apply, что и настоящий контроллер.
  • Относитесь к зелёному с тем же подозрением, что и к красному.