На главную

Разбор2 мин чтения

Бэкап, который удалил систему

Сервер отвечал на ping и пускал по SSH, но не мог запустить ни одного нового процесса. Два часа ушло на версию об отказе диска. Причиной оказался мой собственный скрипт отправки бэкапов, выкаченный за четыре минуты до аварии.

Симптом

Уже запущенные процессы продолжали работать: SSH принимал подключения, веб-сервер отвечал, открытые сетевые соединения держались, ping шёл. Машина выглядела живой. Но любой запуск нового процесса падал с No such file or directory: bash, sudo, sed. SSH проходил аутентификацию и обрывался на запуске оболочки.

Неверная гипотеза

Картина похожа на пропавший том: файлы не читаются, но то, что уже в памяти, работает. Два часа я искал отказ хранилища. Путали и сопутствующие сигналы: панель хостера показывала устаревшее время последнего старта.

Совпадение по времени было прямо перед глазами: сервер умер через четыре минуты после выката.

Причина

Скрипт вытеснения старых копий из очереди отправки выбирал файлы командой ls -1t "$dir"/*.*. С включённым nullglob и пустым каталогом шаблон исчезает целиком, и ls остаётся без аргументов. Без аргументов он печатает текущий каталог, а у службы systemd текущий каталог это /.

Список уходил в rm -f как «самые старые копии». Самыми старыми записями в корне оказались символические ссылки /sbin, /lib и /lib64. Каталоги rm -f не трогает, но ссылок хватило: без загрузчика не запускается ни один бинарник.

Исправление

Сервер восстанавливали со спасательного образа: смонтировали корневой том, чтобы ядро доиграло незавершённый журнал ext4, и вернули удалённые ссылки на системные каталоги.

Защита от повторения

  • При внезапной аварии первым делом проверяется собственное последнее изменение, и только потом инфраструктура.
  • Шаблон, который может исчезнуть, не должен доходить до разрушительной команды.
  • Авария вскрыла дыру в копиях: у основного бота сохранялся только дамп базы, без конфигурации. Теперь конфигурация тоже уезжает на резервный стенд.

На главную