Задача звучала на пять минут: проверить, нужна ли перезагрузка серверу клиента. Посмотреть /var/run/reboot-required, ответить «да» или «нет». Разбирались полдня, потому что почти каждое утверждение про needrestart, которое выглядит очевидным, при проверке оказалось неверным.
Дальше - что этот инструмент делает на самом деле. Всё проверено на живой машине: Ubuntu 22.04, работающее ядро 5.15.0-187, установленное 5.15.0-190, панель управления и полтора десятка чужих сервисов рядом с нашим сайтом.
Он не перезагружает систему и не умеет этого
Главное заблуждение: раз он говорит про ядро, значит может и ребутнуть. Не может. Проверил тремя способами, потому что на боевом сервере верить памяти не хочется.
Список опций: единственный режим действий это -r, и все три его значения касаются только сервисов - l (list), i (interactive), a (automatic). Опции «перезагрузить хост» там нет. Кстати, у needrestart нет -h: справка вылезает на любой неизвестный флаг.
Man-страница: слово reboot не встречается ни разу, ядро упомянуто один раз, и то как -k check for obsolete kernel, то есть только проверка.
Код: grep по /usr/share/perl5/NeedRestart нашёл два вхождения, и оба про контейнеры, а не про хост.
machined.pm:90 machinectl reboot <машина>
LXC.pm:115 lxc-stop --reboot --name <контейнер>
То есть роль needrestart по отношению к ядру чисто уведомительная. Он сам об этом честно пишет в своей плашке: «Restarting the system to load the new kernel will not be handled automatically».
Внутри две независимые проверки
Это второе, что стоит понять сразу: ядро и сервисы проверяются отдельно и никак друг на друга не влияют. Их можно запустить по одной.
$ sudo needrestart -k -b # только ядро
NEEDRESTART-KCUR: 5.15.0-187-generic
NEEDRESTART-KEXP: 5.15.0-190-generic
NEEDRESTART-KSTA: 3
$ sudo needrestart -l -b # только библиотеки
NEEDRESTART-SVC: exim4.service
NEEDRESTART-SVC: NetworkManager.service
NEEDRESTART-SVC: php8.1-fpm.service
...
KCUR - работающее ядро, KEXP - ожидаемое, KSTA - вердикт: 1 всё в порядке, 2 ABI совместим, 3 нужен ребут. Флаг -b (batch) обязателен, если команда идёт из скрипта или по ssh без TTY: без него программа уходит в интерактивный диалог и повисает в ожидании ввода.
Следствие, которое обычно и вызывает вопросы: если перезапустить демоны, исчезнут только строки NEEDRESTART-SVC. Блок про ядро останется, и плашка «Pending kernel upgrade» будет появляться при каждом запуске, пока новое ядро не загрузится по-настоящему.
Без sudo он молча уходит
Запуск от обычного пользователя выглядит как поломка:
$ needrestart
Scanning processes...
$
Ни списка, ни ядра, ни диалога. Причина простая: проверка библиотек читает чужие процессы, проверка ядра лезет в системное состояние, и без root оба возвращают пусто. Сканировать свои собственные процессы он, конечно, может, но перезапускать там нечего.
Интерактивный режим опаснее, чем кажется
Первый экран без флагов - это whiptail, текстовый диалог с рамкой из символов. Никакого графического режима, просто программа ждёт нажатия клавиши. Экран «Pending kernel upgrade» безобиден, это визуальная форма того же KSTA: 3.
А вот следующий экран - список сервисов с чекбоксами, и отмечены они по умолчанию. Подтверждение там сразу перезапустит на живой машине пул PHP-FPM, почтовик и NetworkManager. Ядро при этом всё равно не поменяется. Так что для просмотра надёжнее -b, а если уж зашли в диалог - Cancel или Esc.
Режим -p отвечает на всё одной строкой
Самый практичный вариант, который мы в итоге и оставили. Формат тут не собственный, а стандартный для Nagios-плагинов: СТАТУС - текст | perfdata.
$ sudo needrestart -p
CRIT - Kernel: 5.15.0-187-generic!=5.15.0-190-generic (!!), Services: 6 (!),
Containers: none, Sessions: none|Kernel=2;0;;0;2 Services=6;;0;0 ...
код возврата: 2
Пометки в скобках - это уровни: без пометки OK, (!) warning, (!!) critical. Общий статус берётся по худшему подпункту, а не по типу проверки. Хорошо видно на моей рабочей машине, где ядро не проверялось:
CRIT - Services: 5 (!), Containers: none, Sessions: 1 (!!)
Сервисы сами по себе дали бы WARN, но одна залипшая сессия на устаревших бинарниках вытянула ответ до CRIT. Коды возврата тоже готовые: 0 чисто, 1 нужен рестарт сервисов, 2 нужен ребут. Одной командой получаешь и вердикт для человека, и число для монитора.
Ручная альтернатива через uname -r плюс dpkg | sort -V выглядит проще, но она хрупкая: спотыкается на разных флейворах (-generic против -virtual), на HWE-ядрах и на случае, когда самое свежее установленное ядро не то, которое выберет GRUB. needrestart вычисляет ожидаемое ядро сам.
apt зовёт его сам, но только чтобы показать
Отдельная находка: needrestart уже встроен в apt через /etc/apt/apt.conf.d/99needrestart, то есть запускается после каждой операции с пакетами, включая ночные unattended-upgrades. В логе ночного прогона это выглядит так, будто он всё починил:
Services to be restarted:
systemctl restart exim4.service
systemctl restart php8.1-fpm.service
Service restarts being deferred:
systemctl restart NetworkManager.service
Ключевое слово - «to be restarted», а не «Restarting». Он печатает команды, которые следовало бы выполнить, и выходит. Причина в конфиге: строка $nrconf{restart} в /etc/needrestart/needrestart.conf закомментирована, активного режима нет. Проверяется это не по логу, а по systemctl show -p ActiveEnterTimestamp: время старта юнитов не сдвинулось.
Отдельная категория deferred - это NetworkManager, который он сознательно не перезапускает. Разумно: на машине, до которой добираешься только по ssh, рестарт сетевого демона это классический способ потерять доступ.
reboot-required отвечает на другой вопрос
Привычный файл /var/run/reboot-required и его сосед .pkgs - это не индикатор, а журнал причин: какие пакеты в ходе установки попросили перезагрузку. Как показатель актуальности он ненадёжен по трём причинам. Туда пишет не только ядро (libc6, dbus, systemd, udev тоже добавляют себя). Он не очищается рестартом сервисов. И он только дописывается, поэтому пакет, попросивший ребут неделю назад, остаётся в списке.
Лежит он в /run, а это tmpfs, то есть память. Удалить руками технически можно, но бессмысленно: сам исчезнет при перезагрузке и вернётся при следующем обновлении ядра. needrestart этот файл не трогает вообще, он считает своё независимо.
Что закрывается без перезагрузки
Ровно половина списка - сервисы на удалённых библиотеках. У нас это оказались libcurl, libpng16 и libpq, которые php-fpm держал открытыми после обновления пакетов. Такое лечится обычным systemctl restart, но с оговорками: пул PHP отдаёт 502 в момент рестарта, поэтому лучше в тихий час, а сетевой демон стоит оставить перезагрузке.
Ядро не закрывается ничем, кроме ребута. Когда он наконец случился, вывод стал такой:
$ sudo needrestart -p
OK - Kernel: 7.0.0-30-generic, Microcode: CURRENT, Services: none,
Containers: none, Sessions: none|Kernel=0;0;;0;2 Microcode=0;0;;0;1 ...
Строка Microcode появляется не везде: она есть в needrestart 3.11 из свежей Ubuntu и отсутствует в 3.5 из 22.04. Полезно помнить, если парсите вывод скриптом.
Шпаргалка
sudo needrestart -p # вердикт и код возврата, годится и человеку, и монитору
sudo needrestart -k -b # только ядро, машинный формат
sudo needrestart -l -b # только сервисы на старых библиотеках
sudo needrestart -r l # список без диалога и без действий
И главное правило, ради которого всё это писалось: needrestart отвечает на вопрос «что устарело», а не «что сделать». Решение про перезагрузку и окно для неё остаётся человеку, у которого есть права и ответственность за эту машину.