← Все статьи

Три инцидента, которые были одним: IP базы, зашитый в конфиг

500 на логине, бэкенды в CrashLoopBackOff и зависший деплой выглядели как три проблемы. Все три оказались одним захардкоженным IP базы в конфиге pgbouncer.

Сообщение было стандартное: «стенд не работает». Это был нагрузочный стенд в Gambchamp, и снаружи у него было сразу три разные проблемы.

  • Логин в админку отдавал 500.
  • Несколько подов бэкендов висели в CrashLoopBackOff с сотнями рестартов.
  • Деплой застрял на полпути: новые поды не проходили readiness, старые продолжали обслуживать трафик.

Здесь очень хочется завести три задачи. Это были бы три неверных расследования.

Первая неверная догадка

Логи приложения были забиты вот этим:

asyncpg.exceptions.ConnectionDoesNotExistError: connection was closed in the middle of operation

Похоже на баг в коде или нестабильную сеть внутри кластера. Но все бэкенды ходили в PostgreSQL через общий pgbouncer, а его лог говорил точнее:

pooler error: server login has been failing, cached error: connect failed (server_login_retry)

connect failed легко принять за ошибку аутентификации. Это не она. pgbouncer не мог даже открыть TCP-соединение с сервером базы. Ошибка в приложении была лишь эхом этого.

Доказательство изнутри кластера

Две read-only команды из пода pgbouncer закрыли вопрос: резолв стабильного имени мастера у провайдера и попытка открыть порт 5432 на адресе из нашего манифеста. Имя резолвилось в новый адрес. Старый адрес больше не слушал.

Провайдер пересоздал ноду managed PostgreSQL, и она получила новый IP. В GitOps-репозитории именно этот стенд направлял pgbouncer и postgres exporter на базу по IP. Все остальные среды использовали FQDN мастера, который провайдер переводит на текущий мастер при пересоздании и failover.

История делала картину ещё хуже: в репе нашёлся старый коммит «point exporter at correct DB IP». Это уже случалось, и тогда починили тем же хрупким способом.

Исправление

  1. Заменили IP на FQDN мастера в конфиге pgbouncer и в exporter, отрендерили чарт локально, смержили.
  2. Argo CD засинкал Secret, потому что self-heal был включён.
  3. Вручную перезапустили pgbouncer. Этот шаг легко забыть: pgbouncer.ini смонтирован файлом, pgbouncer его не перечитывает, а аннотации reloader на этом Deployment не было. С exporter то же самое.

После рестарта новый под писал нормальные login attempt и ни одного connect failed. Бэкенды вышли из крашлупа сами, а зависший rollout докатился без вмешательства.

Почему это выглядело как три проблемы

Startup- и liveness-пробы бэкендов обращались к базе. Когда база стала недоступна, Kubernetes видел сломанные поды и перезапускал их, а новый ReplicaSet никак не мог стать готовым. Одна пропавшая зависимость дала три совершенно непохожих симптома.

Что вынести

  • connect failed (server_login_retry) в pgbouncer означает мёртвый endpoint, а не неверный пароль. Сначала смотри на адрес, потом на креды.
  • Крашлуп, зависший деплой и 500 на логине могут быть одной причиной уровнем ниже. Сначала ищи общую зависимость.
  • Подключай клиентов к стабильному FQDN managed-базы, а не к IP ноды. IP в манифесте — это мина, а не настройка.
  • Если конфиг смонтирован файлом и процесс за ним не следит, синк GitOps — это только половина изменения. Либо добавь reloader, либо сделай рестарт частью процедуры.