← Все статьи

Петля логина в WordPress, которая случалась только иногда

wp-admin выкидывал обратно на страницу логина с reauth=1, но не всегда. Причина — случайные auth-соли, сгенерированные в каждом поде за балансировщиком без sticky-сессий.

В Optimacros маркетинговые сайты — это флот из девяти WordPress в Kubernetes, у каждого от двух до пяти реплик под HPA. Жалоба звучала как проблема с паролем: «Пароль верный, один раз зашёл, а потом начинается петля».

После basic auth открытие /wp-admin/ возвращало пользователя на wp-login.php?...&reauth=1. Иногда вход ненадолго держался; следующий запрос снова выкидывал. Затронуты были все сайты.

Слово, которое всё выдало: иногда

Первый подозреваемый в любой петле WordPress за прокси — TLS: WordPress считает запрос обычным HTTP и бесконечно редиректит. Но сайты уже переводили X-Forwarded-Proto в $_SERVER['HTTPS'] и выставляли FORCE_SSL_ADMIN. А главное, ошибка TLS зацикливает каждый раз. Петля, которая случается через раз, указывает на то, что различается между репликами.

Причина: у каждого пода свои ключи

WordPress подписывает cookie аутентификации восемью константами-солями из wp-config.php: AUTH_KEY, SECURE_AUTH_KEY и компания. В этом деплое они не были заданы нигде — ни в окружении, ни в дополнительном конфиге. Entrypoint официального образа на этот случай генерирует случайные соли в каждом поде.

У Service стояло sessionAffinity: None, и балансировщик раскидывал запросы по репликам. Cookie, подписанную подом A, под B отвергал, и WordPress просил войти заново. «Один раз зашёл» значило лишь, что два запроса подряд попали в один под.

Проверка, которая врёт

Первая попытка доказать это дала ложноотрицательный результат. Я сравнил хэши строк define() в wp-config.php по подам. Но в этих строках лежит случайный fallback конкретного пода, а не значение, которое WordPress реально использует. Надёжная проверка — эффективное значение: printenv WORDPRESS_AUTH_KEY в каждом поде и сравнение хэшей. Они различались, в этом и суть.

Исправление через Git

Argo CD работал с self-heal, так что исправление через kubectl откатилось бы за минуты. Всё шло через репозиторий, по схеме, которая в кластере уже была для секретов:

  1. Один раз сгенерировать восемь солей на каждый сайт и положить их в Vault.
  2. Добавить шаблон VaultStaticSecret в общий чарт лендингов, чтобы Vault Secrets Operator создавал Kubernetes Secret на каждый сайт.
  3. Подать восемь переменных WORDPRESS_*_KEY и WORDPRESS_*_SALT из этого Secret. Образ читает их через getenv_docker(), так что все поды используют одинаковые значения.

Argo CD раскатил это на все девять сайтов с maxUnavailable: 0, без простоя. Смена солей один раз разлогинила всех админов — ожидаемо и с предупреждением.

Что вынести

  • «Иногда работает» — подпись состояния, которое различается между репликами. Ошибка конфигурации ломается каждый раз.
  • Любое stateless-приложение больше чем в одну реплику за балансировщиком без sticky нуждается в общих ключах подписи и сессий. Sticky-сессии лишь прячут проблему.
  • Проверяй эффективное значение внутри процесса, а не строку в конфиге, которая может оказаться fallback’ом.
  • Когда в Argo CD включён self-heal, исправлению место в Git, а не в kubectl.