Админ в отпуске, пароля от панели ни у кого: пять часов простоя
Пятница, 21:40, у интернет-магазина отваливается оплата. Разработчик открывает мониторинг, видит красный healthcheck на контейнере платёжного шлюза — и упирается в панель управления сервером, вход в которую защищён вторым фактором на телефоне единственного админа. Админ в самолёте, телефон выключен. Дальше — пять часов ручного разбора, звонков в поддержку хостинга и лихорадочного поиска обходных путей. Разберём, что на самом деле сломалось, какие версии отбросили по пути и какой процесс должен был этого не допустить.
Содержание
- Что сломалось: тихая авария в пятничный вечер
- Что видели в логах и на мониторинге
- Первые гипотезы — и почему они не сработали
- Настоящая причина: доступ к серверу существовал в одном экземпляре
- Пять часов вручную: как в итоге восстановили доступ
- Что изменили после: пароли, доступ и регламент на случай отпуска
Что сломалось: тихая авария в пятничный вечер
Формально ничего не «падало» одномоментно — деградация шла постепенно. За две недели до инцидента команда включила подробное логирование платёжного сервиса для отладки нового способа оплаты, но забыла настроить ротацию логов внутри контейнера. Логи копились в volume Docker, диск сервера медленно заполнялся. В пятницу вечером место закончилось: контейнер с платёжным шлюзом не смог писать во временные файлы, ушёл в crashloop, а nginx перед ним начал отдавать 502 на всех запросах к /checkout.
Первый сигнал — алерт от системы мониторинга о недоступности эндпоинта оплаты. Дежурный разработчик (не админ, а backend-инженер, который в проекте меньше года) открыл алерт, увидел 502 и полез разбираться. Штатный доступ у него был — SSH-ключ, добавленный в authorized_keys при найме. Но зайти по SSH не получилось: порт был закрыт на firewall для всех IP, кроме одного — домашнего адреса админа, который тот когда-то давно прописал как «временное» правило и забыл снять.
Панель управления сервером (FastPanel, доступ по 8888 порту) тоже была отгорожена тем же правилом firewall — единственным исключением был всё тот же IP. Разработчик физически не мог подключиться ни одним из двух штатных способов.
Что видели в логах и на мониторинге
Пока пытались достучаться до сервера, собрали, что было доступно снаружи:
- Мониторинг показывал рост времени ответа
/checkoutза последние шесть часов перед падением — с обычных 200–300 мс до нескольких секунд, и лишь потом обрыв соединений. - Метрика диска в системе мониторинга существовала, но алерт на неё не был настроен — только сам факт недоступности эндпоинта, без диагностики причины.
- В внешнем логе nginx (доступном через веб-интерфейс хостинг-провайдера, не через панель проекта) были видны сплошные
502 Bad Gatewayначиная с 21:32 — то есть проблема на уровне upstream, а не самого nginx. - Панель хостинг-провайдера (не путать с панелью управления сервером — это разные уровни: у провайдера свой личный кабинет с биллингом и базовым управлением виртуальной машиной) была доступна отдельно, через обычный логин-пароль без географической привязки. Именно она в итоге стала спасением, но не сразу.
Ключевой вывод на этом этапе: без входа на сам сервер невозможно было понять, что именно сломалось — рост времени ответа мог означать что угодно, от исчерпания места на диске до утечки памяти или проблемы у платёжного провайдера на их стороне.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПервые гипотезы — и почему они не сработали
Команда перебрала три версии, прежде чем дошла до реальной причины.
Версия 1: сломал деплой. Полчаса назад до падения был мелкий релиз — поправили текст на странице корзины. Откатили релиз через git revert и пересобрали пайплайн CI/CD. Ничего не изменилось: контейнер продолжал падать, потому что причина была не в коде, а в занятом диске, а откат кода не освобождает место.
Версия 2: атака или всплеск трафика. Посмотрели графики трафика на CDN и у провайдера — никакого аномального роста запросов не было, обычная пятничная нагрузка. DDoS отбросили сразу, как только увидели ровный график входящих соединений.
Версия 3: платёжный провайдер лежит на своей стороне. Проверили статус-страницу платёжного шлюза — всё зелёное, у них всё работало. Написали в саппорт платёжного провайдера, получили ответ через 40 минут: «с нашей стороны проблем нет, проверьте логи на своём сервере». Это была правильная подсказка, но воспользоваться ею всё ещё было нечем — доступа к серверу так и не было.
Отдельно попробовали резервный вариант: у второго разработчика в компании тоже был SSH-ключ, добавленный больше года назад, ещё до того, как ужесточили firewall. Оказалось, что этот ключ в принципе рабочий (не был отозван), но правило firewall блокировало соединение по IP раньше, чем доходило до проверки ключа — иметь верный ключ не помогало, если подключаешься не с того адреса.
Настоящая причина: доступ к серверу существовал в одном экземпляре
Когда все технические гипотезы кончились, стало ясно: проблема не в коде и не в трафике, а в организации доступа. Реконструкция показала цепочку решений, каждое из которых по отдельности выглядело разумным, а вместе создало единую точку отказа:
- Firewall для SSH и панели управления был настроен на белый список IP-адресов «для безопасности» — разумная мера сама по себе.
- В белый список попал только домашний IP админа, потому что именно он в тот момент настраивал сервер, а остальных «добавим потом».
- «Потом» так и не наступило — задача осела в беклоге на полгода.
- Пароль от панели FastPanel хранился в личном менеджере паролей админа, не в общем хранилище команды. Второй фактор аутентификации для панели был привязан к его личному телефону.
- У компании не было ни резервного администратора, ни задокументированной процедуры экстренного доступа («break glass»), ни правила «перед отпуском передать доступ или расширить список IP».
Отдельно всплыла деталь, которая усугубила ситуацию: SSH-ключ второго разработчика формально существовал в authorized_keys, но никто заранее не проверял, реально ли он работает через актуальный firewall — «доступ выдан» и «доступ действует» оказались разными утверждениями. Похожая логика разбирается в статье о том, как раздать доступ команде без выдачи root: доступ нужно не только выдавать, но и регулярно проверять на практике, а не полагаться на факт существования записи в конфиге.
Пять часов вручную: как в итоге восстановили доступ
Реальное восстановление шло по цепочке обходных путей, а не по продуманному плану — потому что продуманного плана не было.
Первым сработавшим шагом стал личный кабинет хостинг-провайдера — он не был завязан на IP-белый список сервера, только на обычный логин-пароль, который знал более широкий круг людей в компании (доступ к биллингу отдельно передавался бухгалтеру и техлиду). Через личный кабинет нашли функцию доступа к серверу через веб-консоль (VNC/serial-console), которая обходит SSH и сетевой firewall полностью — фактически это подключение «с экрана и клавиатуры», как если бы стоять рядом с сервером физически. Про такой способ подробно написано в материале про консольный доступ к серверу через KVM — именно он в итоге спас ситуацию, хотя команда о нём просто не вспомнила сразу, потому что никогда им не пользовалась.
Через веб-консоль зашли под локальным пользователем — благо, у второго разработчика был сохранён пароль от старой учётной записи, которую использовали ещё до перехода на SSH-ключи. Дальше действовали быстро:
df -h
# /var/lib/docker 98% использовано
du -sh /var/lib/docker/containers/*/*-json.log | sort -rh | head -5
# один лог-файл контейнера платёжного шлюза — больше 40 ГБ
truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.log
docker restart payment-gateway
После очистки лог-файла и рестарта контейнера сервис поднялся и /checkout снова начал отвечать 200. От первого алерта до восстановления прошло около пяти часов — большая часть времени ушла не на техническое исправление (оно заняло десять минут), а на поиск хоть какого-то рабочего пути внутрь сервера.
Параллельно настроили постоянную ротацию логов, чтобы инцидент не повторился по той же причине:
docker update --log-opt max-size=50m --log-opt max-file=3 payment-gateway
Что изменили после: пароли, доступ и регламент на случай отпуска
Разбор инцидента дал команде понятный список изменений — все они были сделаны в течение следующей недели.
Общее хранилище секретов вместо личных менеджеров паролей. Подняли self-hosted Vaultwarden на отдельном VPS и перенесли туда все пароли от панелей, баз данных и сервисов, доступ к которым нужен больше чем одному человеку. Пошаговая установка описана в статье как установить и настроить Vaultwarden на VPS. Личный менеджер паролей остался у каждого для личных нужд, но рабочие секреты команды теперь живут в общем хранилище с ролями доступа.
Второй фактор — не на одном телефоне. Для панели управления настроили TOTP с резервными кодами восстановления, которые хранятся в том же общем хранилище, а не только на одном устройстве. Разбор вариантов и настройка — в статье про двухфакторную аутентификацию для панелей управления.
Firewall расширили, но не открыли настежь. Вместо одного домашнего IP в белый список добавили статические адреса рабочих VPN-шлюзов, через которые подключается вся команда, плюс отдельное правило для аварийного доступа через IP-KVM провайдера (он и так работает поверх отдельного канала, не через основной firewall сервера).
Задокументировали процедуру экстренного доступа. Появился короткий чек-лист «нет доступа к серверу»: сначала личный кабинет провайдера → веб-консоль/KVM → локальный аварийный аккаунт с ограниченными правами. Отдельно описали процедуру сброса root-пароля средствами провайдера на случай, если и локальный доступ утерян — сам процесс расписан в материале про сброс root-пароля на VPS.
Правило перед отпуском. Теперь любой, кто уходит в отпуск дольше трёх дней и является единственным держателем доступа к чему-либо критичному, обязан либо передать доступ временно второму человеку, либо убедиться, что аварийный путь входа проверен и работает — не «должен работать», а реально протестирован за неделю до отъезда.
Ни один из этих пунктов не требует сложной инфраструктуры или дополнительных затрат — это в первую очередь вопрос дисциплины и явного распределения ответственности, а не покупки нового инструмента.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли было обойтись без веб-консоли (KVM), если бы её не было у провайдера?
Тогда единственным путём остался бы сброс root-пароля через панель управления сервером у хостинг-провайдера — эта функция есть почти у всех, кто продаёт VPS, но обычно требует подтверждения владения аккаунтом, что тоже занимает время. Поэтому KVM-доступ или его аналог стоит проверять заранее, а не в момент аварии.
Разве не проще было просто дозвониться до админа?
Проще, но ненадёжно как единственный план: человек может быть недоступен по десяткам причин — не только отпуск, но и болезнь, увольнение, потеря телефона. План восстановления доступа не должен зависеть от готовности конкретного человека ответить на звонок.
Стоит ли давать root-доступ сразу всей команде, чтобы избежать таких ситуаций?
Нет — это решает проблему единой точки отказа, но создаёт новую: больше поверхность для ошибки и утечки. Правильнее выдавать ограниченные права через sudo-группы конкретным людям под конкретные задачи, а полный доступ держать в аварийном контуре с логированием использования.
Как понять, что firewall-правило для панели или SSH настроено слишком узко?
Простой тест — раз в квартал попросить человека без «основного» доступа попробовать зайти официальным для него способом (ключ, VPN, IP) и зафиксировать, получилось ли. Если полагаться только на факт «доступ выдан в конфиге», риск обнаружить проблему только в момент реальной аварии.
Нужно ли хранить пароль от root в общем хранилище, если обычно заходят по SSH-ключам?
Да, как аварийный вариант — SSH-ключи могут быть недоступны (потерян приватный ключ, сломан агент), а пароль от root через консоль провайдера остаётся рабочим путём независимо от состояния SSH-инфраструктуры на самом сервере.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →