← Все статьи

Argo CD показывал Synced, а API-сервер молча выкинул мой фикс

Месяцы без метрик, фикс уже в git и зелёный Synced. Поля не было в схеме CRD, API-сервер его отрезал, и никто не пожаловался.

У пет-проекта в моей домашней лаборатории месяцами не было ни одной метрики приложения. Postgres там на CloudNativePG, оператор создал PodMonitor, а в GitOps-репе уже лежал фикс для пропавшего скрейпа. Argo CD показывал Synced / Healthy ровно на том коммите, где был фикс. Тем временем отказ реплики остался незамеченным 23 часа — за этим просто никто не следил.

Сначала измерить слепое пятно

Прежде чем что-то трогать, я посчитал. У Prometheus было 46 активных таргетов и ни одного в этом namespace. Не «меньше, чем ожидалось», а ноль. Эта цифра превратила смутное ощущение в конкретный вопрос: почему PodMonitor, который явно существует, никто не скрейпит?

Два дефекта, каждый по отдельности выглядит безобидно

Несовпадение меток. kube-prometheus-stack с дефолтами чарта выбирает только мониторы со своей меткой release. У PodMonitor, который создаёт оператор CNPG, есть app.kubernetes.io/* и cnpg.io/cluster — и больше ничего. Объект существует, при осмотре выглядит как покрытие, а ни один Prometheus его не читает. Тот же дефолт молча отбросил и ServiceMonitor от cert-manager.

Поле, которого нет. Фикс в git пытался решить это со стороны CNPG — добавить метки через spec.monitoring.podMonitorMetadata.labels. Этого поля нет в схеме CRD моей версии CNPG. При структурных схемах API-сервер при apply отрезает неизвестные поля. Ни ошибки, ни предупреждения в Argo CD. Argo сравнил обрезанный живой объект с обрезанным желаемым состоянием, нашёл их равными и сказал Synced.

Ключевой шаг — спросить саму CRD, есть ли такое поле:

kubectl get crd clusters.postgresql.cnpg.io -o json \
  | jq -r '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.monitoring.properties | keys[]'

podMonitorMetadata в списке не было. Дело закрыто.

Чинить потребителя, а не каждого производителя

Вместо того чтобы гоняться за метками на каждом мониторе от операторов, я поменял Prometheus: выставил в чарте опции *SelectorNilUsesHelmValues в false, селекторы стали пустыми, и Prometheus подхватывает все ServiceMonitor и PodMonitor в кластере. Одна правка починила CNPG, cert-manager и каждый монитор, который создаст будущий чарт. Сразу после синка появился cnpg_collector_up для каждого пода Postgres.

ruleSelector я сознательно оставил строгим. Лишняя собранная метрика безвредна, лишний сработавший алерт — шум. Цена: каждый PrometheusRule обязан нести метку release, иначе он не загрузится никуда. Та же ловушка, слоем выше, поэтому я проверяю это при каждом добавлении правил.

Откат тривиален: убрать две строки, и Prometheus вернётся к строгому селектору без потери данных.

Альтернативой был spec.inheritedMetadata.labels у CNPG, но он вешает метки на все объекты кластера, включая поды Postgres, а смена меток подов — это rolling restart базы. Правка на стороне потребителя этого избежала.

Выводы

  • Зелёный Synced значит «то, что API-сервер сохранил, совпадает с git — после того, как сохранил». О полях, отрезанных по дороге, он ничего не говорит.
  • Если GitOps-изменение ни на что не повлияло, сначала спроси схему CRD, существует ли поле, и только потом отлаживай контроллер.
  • Существующий объект — ещё не покрытие. Считай активные таргеты, а не разглядывай мониторы.
  • Лучше один раз поправить селектор у потребителя, чем вечно перевешивать метки у каждого производителя.