Петля логина в 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 откатилось бы за минуты. Всё шло через репозиторий, по схеме, которая в кластере уже была для секретов:
- Один раз сгенерировать восемь солей на каждый сайт и положить их в Vault.
- Добавить шаблон
VaultStaticSecretв общий чарт лендингов, чтобы Vault Secrets Operator создавал Kubernetes Secret на каждый сайт. - Подать восемь переменных
WORDPRESS_*_KEYиWORDPRESS_*_SALTиз этого Secret. Образ читает их черезgetenv_docker(), так что все поды используют одинаковые значения.
Argo CD раскатил это на все девять сайтов с maxUnavailable: 0, без простоя. Смена солей один раз разлогинила всех админов — ожидаемо и с предупреждением.
Что вынести
- «Иногда работает» — подпись состояния, которое различается между репликами. Ошибка конфигурации ломается каждый раз.
- Любое stateless-приложение больше чем в одну реплику за балансировщиком без sticky нуждается в общих ключах подписи и сессий. Sticky-сессии лишь прячут проблему.
- Проверяй эффективное значение внутри процесса, а не строку в конфиге, которая может оказаться fallback’ом.
- Когда в Argo CD включён self-heal, исправлению место в Git, а не в
kubectl.