Три инцидента, которые были одним: 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». Это уже случалось, и тогда починили тем же хрупким способом.
Исправление
- Заменили IP на FQDN мастера в конфиге pgbouncer и в exporter, отрендерили чарт локально, смержили.
- Argo CD засинкал Secret, потому что self-heal был включён.
- Вручную перезапустили 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, либо сделай рестарт частью процедуры.