apt upgrade로 libc6나 openssl 보안 업데이트를 받았다고 해서 이미 떠 있는 데몬까지 패치되는 것은 아니다. 실행 중인 프로세스는 교체되기 전의 라이브러리를 메모리에 그대로 매핑하고 있어서, 재시작하기 전까지는 취약한 코드가 계속 돈다. /var/run/reboot-required 파일은 재부팅이 필요하다는 사실만 알려줄 뿐, 어떤 서비스가 옛 라이브러리를 쓰는지는 알려주지 않는다.
이 글에서는 Ubuntu에 기본 설치된 needrestart로 재시작이 필요한 서비스·사용자 세션·커널을 찾고, Ubuntu 24.04에서 바뀐 APT 훅의 자동 재시작 동작과 그것을 제어하는 설정, 모니터링 연동까지 정리한다. 모든 출력은 Ubuntu 24.04 LTS(needrestart 3.6-7ubuntu4.5) 테스트 서버에서 직접 실행한 결과다.
지금 옛 라이브러리로 돌고 있는 서비스
테스트 서버는 이틀 전 libc6 업데이트를 받은 뒤 재부팅하지 않은 상태였다. -b(batch)는 스크립트로 파싱하기 좋은 한 줄짜리 형식으로 결과를 낸다.
$ cat /var/run/reboot-required.pkgs
libc6
$ sudo needrestart -b
NEEDRESTART-VER: 3.6
NEEDRESTART-KCUR: 6.8.0-139-generic
NEEDRESTART-KEXP: 6.8.0-139-generic
NEEDRESTART-KSTA: 1
NEEDRESTART-SVC: dbus.service
NEEDRESTART-SVC: getty@tty1.service
NEEDRESTART-SVC: NetworkManager.service
NEEDRESTART-SVC: systemd-logind.service
NEEDRESTART-SVC: unattended-upgrades.service
NEEDRESTART-SVC: wpa_supplicant.service
6개 모두 needrestart의 자동 재시작 대상에서 기본 제외되는 서비스라, 업데이트 후 이틀째 옛 libc로 돌고 있었다. 제외 기준은 뒤의 override_rc에서 다룬다.
라이브러리를 교체해 재현하기
검증용 서비스를 하나 띄우고 zlib1g를 재설치해 라이브러리 파일을 바꿔본다. python3는 libz.so.1을 링크하므로 교체 대상이 된다.
[Unit]
Description=needrestart demo web server
[Service]
ExecStart=/usr/bin/python3 -m http.server 8088 --directory /srv/demo
DynamicUser=yes
[Install]
WantedBy=multi-user.target$ sudo systemctl daemon-reload && sudo systemctl start demo-web
$ PID=$(systemctl show -p MainPID --value demo-web)
$ grep libz /proc/$PID/maps | head -2
754c638e9000-754c638eb000 r--p 00000000 08:02 7896138 /usr/lib/x86_64-linux-gnu/libz.so.1.3
754c638eb000-754c638fd000 r-xp 00002000 08:02 7896138 /usr/lib/x86_64-linux-gnu/libz.so.1.3
$ sudo NEEDRESTART_SUSPEND=1 apt-get install -y --reinstall zlib1g
...
Unpacking zlib1g:amd64 (1:1.3.dfsg-3.1ubuntu2.2) over (1:1.3.dfsg-3.1ubuntu2.2) ...
Setting up zlib1g:amd64 (1:1.3.dfsg-3.1ubuntu2.2) ...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
packages have been installed but needrestart is suspended
$ grep libz /proc/$PID/maps | head -2
754c638e9000-754c638eb000 r--p 00000000 08:02 7896138 /usr/lib/x86_64-linux-gnu/libz.so.1.3 (deleted)
754c638eb000-754c638fd000 r-xp 00002000 08:02 7896138 /usr/lib/x86_64-linux-gnu/libz.so.1.3 (deleted)
dpkg는 파일을 덮어쓰지 않고 새 파일로 교체하므로, 프로세스는 디스크에서 사라진(deleted) 옛 inode를 계속 매핑한다. needrestart가 찾는 것이 바로 이 흔적이다(NEEDRESTART_SUSPEND=1은 APT 훅의 자동 실행을 잠시 막는 환경 변수).
needrestart로 확인하기
$ sudo needrestart -b
NEEDRESTART-VER: 3.6
NEEDRESTART-KCUR: 6.8.0-139-generic
NEEDRESTART-KEXP: 6.8.0-139-generic
NEEDRESTART-KSTA: 1
NEEDRESTART-SVC: cups-browsed.service
NEEDRESTART-SVC: cups.service
NEEDRESTART-SVC: dbus.service
NEEDRESTART-SVC: demo-web.service
NEEDRESTART-SVC: fwupd.service
NEEDRESTART-SVC: getty@tty1.service
NEEDRESTART-SVC: ModemManager.service
NEEDRESTART-SVC: NetworkManager.service
NEEDRESTART-SVC: nfs-mountd.service
NEEDRESTART-SVC: polkit.service
NEEDRESTART-SVC: rsyslog.service
NEEDRESTART-SVC: ssh.service
NEEDRESTART-SVC: systemd-logind.service
NEEDRESTART-SVC: udisks2.service
NEEDRESTART-SVC: unattended-upgrades.service
NEEDRESTART-SVC: upower.service
NEEDRESTART-SVC: wpa_supplicant.service
NEEDRESTART-SESS: noble @ session #517
NEEDRESTART-SESS: noble @ session #518
NEEDRESTART-SESS: noble @ user manager service
zlib을 쓰는 ssh, rsyslog, cups 등 11개가 새로 늘었고, 로그인 세션에서 띄워둔 python3와 사용자 systemd 인스턴스도 SESS로 잡혔다. 사람이 읽기 좋은 형태는 -r l(list only)이다.
$ sudo needrestart -r l
Running kernel seems to be up-to-date.
Services to be restarted:
systemctl restart cups-browsed.service
systemctl restart cups.service
systemctl restart demo-web.service
systemctl restart fwupd.service
systemctl restart nfs-mountd.service
systemctl restart polkit.service
systemctl restart rsyslog.service
systemctl restart ssh.service
systemctl restart udisks2.service
systemctl restart upower.service
Service restarts being deferred:
systemctl restart ModemManager.service
systemctl restart NetworkManager.service
/etc/needrestart/restart.d/dbus.service
systemctl restart getty@tty1.service
systemctl restart systemd-logind.service
systemctl restart unattended-upgrades.service
systemctl restart wpa_supplicant.service
No containers need to be restarted.
User sessions running outdated binaries:
noble @ session #517: python3[117688]
noble @ user manager service: systemd[117548]
No VM guests are running outdated hypervisor (qemu) binaries on this host.
| 옵션 | 동작 |
|---|---|
-b | NEEDRESTART-*: 형식의 batch 출력 (스크립트용) |
-r l / -r i / -r a | 목록만 / 서비스마다 물어보기 / 자동 재시작 |
-k, -l, -w | 커널만 / 라이브러리만 / CPU 마이크로코드만 검사 |
-p | Nagios 플러그인 형식 출력과 종료 코드 |
NEEDRESTART_SUSPEND=1 | APT 훅에서 needrestart 실행을 건너뜀 |
Ubuntu 24.04의 APT 훅은 자동으로 재시작한다
needrestart는 /etc/apt/apt.conf.d/99needrestart의 DPkg::Post-Invoke 훅으로 패키지 설치 직후 자동 실행된다. 이번에는 NEEDRESTART_SUSPEND 없이 libexpat1을 재설치했다.
$ systemctl show -p MainPID --value demo-web
117813
$ sudo apt-get install -y --reinstall libexpat1
...
Setting up libexpat1:amd64 (2.6.1-2ubuntu0.4) ...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
Running kernel seems to be up-to-date.
Restarting services...
systemctl restart avahi-daemon.service cups-browsed.service cups.service demo-web.service fwupd.service nfs-mountd.service polkit.service rsyslog.service ssh.service udisks2.service upower.service
Service restarts being deferred:
systemctl restart ModemManager.service
systemctl restart NetworkManager.service
/etc/needrestart/restart.d/dbus.service
systemctl restart getty@tty1.service
systemctl restart systemd-logind.service
systemctl restart unattended-upgrades.service
systemctl restart wpa_supplicant.service
No containers need to be restarted.
User sessions running outdated binaries:
noble @ session #517: python3[117688]
noble @ user manager service: systemd[117548]
No VM guests are running outdated hypervisor (qemu) binaries on this host.
$ systemctl show -p MainPID --value demo-web
118248
묻는 창 없이 ssh, rsyslog, demo-web 등 11개가 바로 재시작됐다(PID 117813 → 118248). /usr/share/doc/needrestart/README.Ubuntu에 따르면 24.04부터 기본 설정에서 Debconf 프롬프트 없이 자동 재시작하며, unattended-upgrades로 받은 보안 업데이트에도 똑같이 적용된다.
자동 재시작에서 빠지는 서비스 — override_rc
"deferred"로 남은 서비스는 /etc/needrestart/needrestart.conf의 override_rc에서 0으로 지정된 것들이다.
$nrconf{override_rc} = {
qr(^dbus) => 0,
...
qr(^network) => 0,
qr(^NetworkManager) => 0,
qr(^ModemManager) => 0,
qr(^wpa_supplicant) => 0,
...
qr(^getty@.+\.service) => 0,
qr(^user@\d+\.service) => 0,
...
qr(^docker) => 0,
...
qr(^unattended-upgrades\.service$) => 0,
...
# don't restart systemd-logind, see #798097
qr(^systemd-logind) => 0,네트워크나 세션을 끊을 수 있는 서비스라 자동으로는 건드리지 않는다. 안전한 것은 직접 재시작하면 목록에서 빠지고, dbus·systemd-logind처럼 재시작 자체가 위험한 것은 재부팅 일정으로 처리한다.
$ sudo systemctl restart unattended-upgrades.service
$ sudo needrestart -b | grep -c unattended-upgrades
0
커널 업데이트 감지
실행 중인 커널보다 새 커널 이미지가 설치돼 있으면 KSTA가 바뀐다. HWE 커널 이미지를 설치해 확인했다.
$ sudo NEEDRESTART_SUSPEND=1 apt-get install -y --no-install-recommends linux-image-7.0.0-31-generic
...
Setting up linux-modules-7.0.0-31-generic (7.0.0-31.31~24.04.1) ...
Setting up linux-image-7.0.0-31-generic (7.0.0-31.31~24.04.1) ...
packages have been installed but needrestart is suspended
$ uname -r
6.8.0-139-generic
$ sudo needrestart -k -b
NEEDRESTART-VER: 3.6
NEEDRESTART-KCUR: 6.8.0-139-generic
NEEDRESTART-KEXP: 7.0.0-31-generic
NEEDRESTART-KSTA: 3
$ sudo needrestart -k -r l
Pending kernel upgrade
----------------------
Newer kernel available
The currently running kernel version is 6.8.0-139-generic which is not the
expected kernel version 7.0.0-31-generic.
Restarting the system to load the new kernel will not be handled automatically,
so you should consider rebooting.
$ cat /var/run/reboot-required.pkgs
libc6
linux-image-7.0.0-31-generic
linux-base
| KSTA | 의미 (README.batch.md) |
|---|---|
| 0 | 알 수 없음 / 감지 실패 |
| 1 | 대기 중인 업그레이드 없음 |
| 2 | ABI 호환 업그레이드 대기 (같은 커널 ABI 번호의 새 빌드) |
| 3 | 버전 업그레이드 대기 |
커널은 needrestart가 절대 자동으로 처리하지 않으므로, 재부팅 없이 커널 보안 패치만 적용하려면 Livepatch를 함께 쓴다.
자동 재시작 끄기 — conf.d 로컬 설정
운영 서버에서 패키지 설치가 곧바로 ssh·DB 재시작으로 이어지는 것이 부담스러우면 목록 모드로 바꾼다. 본 설정 파일을 고치지 말고 conf.d에 덮어쓸 값만 둔다.
# /etc/needrestart/conf.d/50-local.conf
# APT 훅에서도 목록만 보여주고 재시작은 운영자가 직접 한다
$nrconf{restart} = 'l';
# demo-web은 목록에서도 "지연(deferred)"으로 분류
$nrconf{override_rc}{qr(^demo-web\.service$)} = 0;$ systemctl show -p MainPID --value demo-web
118248
$ sudo apt-get install -y --reinstall zlib1g
...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
Disabling Ubuntu mode, explicit restart mode configured
Services to be restarted:
systemctl restart cups-browsed.service
systemctl restart cups.service
systemctl restart fwupd.service
systemctl restart nfs-mountd.service
systemctl restart packagekit.service
systemctl restart polkit.service
systemctl restart rsyslog.service
systemctl restart ssh.service
systemctl restart udisks2.service
systemctl restart upower.service
Service restarts being deferred:
systemctl restart ModemManager.service
systemctl restart NetworkManager.service
/etc/needrestart/restart.d/dbus.service
systemctl restart demo-web.service
systemctl restart getty@tty1.service
systemctl restart systemd-logind.service
systemctl restart unattended-upgrades.service
systemctl restart wpa_supplicant.service
...
$ systemctl show -p MainPID --value demo-web
118248
Disabling Ubuntu mode와 함께 목록만 출력됐고, demo-web은 deferred로 옮겨져 PID가 그대로다. 점검 시간에 운영자가 needrestart -r a를 실행하면 명령행 옵션이 설정보다 우선해 목록의 서비스를 한 번에 재시작한다.
$ sudo needrestart -r a
Restarting services...
systemctl restart cups-browsed.service cups.service fwupd.service nfs-mountd.service packagekit.service polkit.service rsyslog.service ssh.service udisks2.service upower.service
Service restarts being deferred:
systemctl restart ModemManager.service
systemctl restart NetworkManager.service
/etc/needrestart/restart.d/dbus.service
systemctl restart demo-web.service
systemctl restart getty@tty1.service
systemctl restart systemd-logind.service
systemctl restart wpa_supplicant.service
...
모니터링에 연결하기
-p는 Nagios 플러그인 형식으로 요약과 종료 코드(0 OK / 1 WARN / 2 CRIT)를 낸다.
$ sudo needrestart -p; echo "exit=$?"
CRIT - Kernel: 6.8.0-139-generic, Services: 17 (!), Containers: none, Sessions: 3 (!!)|Kernel=0;0;;0;2 Services=17;;0;0 Containers=0;;0;0 Sessions=3;0;;0
Services:
- ModemManager.service
- NetworkManager.service
...
Sessions:
- noble @ session #517
- noble @ session #518
- noble @ user manager service
exit=2
Prometheus를 쓴다면 -b 출력을 node_exporter textfile collector 형식으로 바꿔 타이머로 주기 실행한다.
#!/usr/bin/env bash
# needrestart -b 결과를 node_exporter textfile collector 형식으로 내보낸다
set -euo pipefail
out=/var/lib/node_exporter/textfile_collector/needrestart.prom
mkdir -p "${out%/*}"
batch=$(needrestart -b 2>/dev/null)
ksta=$(awk -F': ' '/^NEEDRESTART-KSTA/ {print $2}' <<< "$batch")
svc=$(grep -c '^NEEDRESTART-SVC:' <<< "$batch" || true)
sess=$(grep -c '^NEEDRESTART-SESS:' <<< "$batch" || true)
reboot=0; [[ -e /run/reboot-required ]] && reboot=1
cat > "$out.tmp" <<PROM
# HELP needrestart_kernel_status 0=unknown 1=ok 2=ABI upgrade 3=version upgrade
needrestart_kernel_status ${ksta:-0}
needrestart_services_pending ${svc}
needrestart_sessions_pending ${sess}
reboot_required ${reboot}
PROM
mv "$out.tmp" "$out"
cat "$out"$ sudo /usr/local/sbin/needrestart-metrics.sh
# HELP needrestart_kernel_status 0=unknown 1=ok 2=ABI upgrade 3=version upgrade
needrestart_kernel_status 3
needrestart_services_pending 18
needrestart_sessions_pending 2
reboot_required 1
주의사항
| 항목 | 내용 |
|---|---|
| 24.04 기본값은 자동 재시작 | apt install 하나로 ssh·rsyslog 등이 재시작된다. 운영 중 끊기면 곤란한 서비스가 있으면 override_rc에 넣거나 $nrconf{restart} = 'l' |
| deferred 서비스 | dbus·systemd-logind·NetworkManager는 자동 재시작되지 않는다. libc6·libssl 업데이트 후에는 재부팅 일정을 잡는다 |
| 사용자 세션 | 로그인 세션의 프로세스와 user@.service는 재시작 대상이 아니다. 로그아웃 후 재접속해야 새 라이브러리를 쓴다 |
reboot-required는 자동으로 지워지지 않는다 | 커널을 설치했다가 제거해도 /run/reboot-required.pkgs에 항목이 남았다(/run이라 재부팅 시 사라짐). 커널 상태 판단은 needrestart -k -b의 KSTA로 한다 |
| interactive 모드 설정 시 | restart = 'i'인데 unattended-upgrades처럼 비대화형으로 실행되면 목록 모드로 떨어진다(설정 파일 주석) |
| 컨테이너 | docker, libvirt, lxcfs 계열은 기본 제외다. 컨테이너 안의 프로세스는 이미지 재빌드·재배포로 처리한다 |
| needrestart 자체의 취약점 | CVE-2024-48990~48992(로컬 권한 상승)는 Ubuntu 24.04에서 3.6-7ubuntu4.3에 수정됐다. dpkg -l needrestart로 버전을 확인 |
마무리
| 상황 | 명령 |
|---|---|
| 무엇을 재시작해야 하는지 보기 | sudo needrestart -r l |
| 스크립트·모니터링용 | sudo needrestart -b, sudo needrestart -p |
| 커널만 확인 | sudo needrestart -k -b → KSTA 1이 아니면 재부팅 필요 |
| 점검 시간에 일괄 재시작 | sudo needrestart -r a |
| 자동 재시작 끄기 | /etc/needrestart/conf.d/에 $nrconf{restart} = 'l'; |
| 특정 서비스만 제외 | $nrconf{override_rc}{qr(^이름)} = 0; |
보안 업데이트는 패키지 설치로 끝나지 않고, 실행 중인 프로세스가 새 라이브러리를 매핑해야 끝난다. 업데이트 후 needrestart -b의 SVC·SESS·KSTA가 비어 있거나 1인지 확인하는 것까지를 패치 절차에 넣어두면 된다.