Debian unattended-upgrades 不执行:如何启用和排查
Debian 默认关闭 unattended-upgrades。本文解释为何安装后没有任何动作,教您读取 Origins-Pattern、检查 apt-daily 定时器,并验证自动更新是否实际运行。
为何全新 Debian 安装中的 unattended-upgrades 不执行任何操作
在 Debian 上,即使已经安装 unattended-upgrades,它也可能一次升级都不执行,因为安装软件包和启用软件包是两个独立步骤。软件包在配置自身之前会询问一个 debconf 问题,Debian 安装程序会为该问题保存答案 false。Ubuntu 对同一个问题给出的答案相反,因此同一个软件包在 Ubuntu 上看似正常,在 Debian 上却像是出现故障。
系统不会明确提示这一点。启动时没有错误,登录时没有警告,也没有可供查看的日志文件,因为负责写入日志的代码从未被调用。启用它只需执行一条命令。本指南其余部分将介绍启用后仍可能阻止它运行的四个因素:它可以从哪些软件源执行升级、systemd 定时器实际何时触发、如何确认某次运行失败,以及系统是否允许自动重启。
打印修改前系统的实际配置
dpkg -l unattended-upgrades | tail -n 1
sudo apt install -y debconf-utils
sudo debconf-show unattended-upgrades
cat /etc/apt/apt.conf.d/20auto-upgrades
apt-config dump APT::Periodicdebconf-show会打印 unattended-upgrades/enable_auto_updates 中保存的值。该行开头的 * 表示该值是由某项配置设置的,而不是使用软件包默认值。在 Debian 安装程序构建的计算机上,这项配置来自安装程序。
cat 可能会打印 cat: /etc/apt/apt.conf.d/20auto-upgrades: No such file or directory。这一点本身就能解释为什么没有任何动作:没有该文件,就没有周期性任务,因此不会安排任何操作。
真正重要的是 apt-config dump。APT 会按文件名顺序读取 /etc/apt/apt.conf.d/ 中的所有文件并合并配置,因此在 99local 中设置的普通值会覆盖 20auto-upgrades 中的相同值。读取单个文件只能告诉您该文件中的配置。apt-config dump 才能告诉您 APT 实际会执行什么。
以下两个键决定是否会执行任何操作:
APT::Periodic::Update-Package-Lists刷新软件包列表,这正是apt update手动执行的任务。APT::Periodic::Unattended-Upgrade执行升级本身。
它们的值不是 true 或 false,而是以天为单位的时间间隔。"1" 表示“如果过去 1 天内未执行,则执行此操作”,"7" 表示每周执行,"0" 表示从不执行。APT::Periodic::Unattended-Upgrade "0"; 是有效配置,但会永久不执行任何操作,而且不会报告错误。如果您的转储对该键显示 0,或者完全没有该键,那么原因已经找到了。
启用方式:使用 dpkg-reconfigure,或手动写入配置项
sudo apt update
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades这里不能省略 --priority=low。该问题的优先级较低,因此在默认优先级下,dpkg-reconfigure 不显示任何内容、不进行任何更改,并以状态码 0 退出,看起来与命令成功执行完全一样。请在对话框中选择“是”。随后,软件包的 postinst 脚本会根据您的回答写入 /etc/apt/apt.conf.d/20auto-upgrades。
如果您通过脚本构建计算机,则没有可供回答的对话框,因此请先设置该回答:
echo 'unattended-upgrades unattended-upgrades/enable_auto_updates boolean true' \
| sudo debconf-set-selections
sudo dpkg-reconfigure -f noninteractive unattended-upgrades
apt-config dump APT::Periodic您也可以直接写入这两个配置项:
printf 'APT::Periodic::Update-Package-Lists "1";\nAPT::Periodic::Unattended-Upgrade "1";\n' \
| sudo tee /etc/apt/apt.conf.d/20auto-upgrades
apt-config dump APT::Periodic这样会立即生效,但也会留下隐患。debconf 中的回答仍然是旧值,因此下次执行 dpkg-reconfigure 或重新安装软件包时,系统会根据 debconf 重新写入该文件,并在完全不输出任何信息的情况下覆盖您的修改。请同时设置这两项,或者设置 debconf 并让 postinst 负责管理该文件。
这正是它与该系列另一端的唯一实质区别。另一端的安装程序会自动启用同一个软件包,因此 Ubuntu unattended-upgrades 的配置 从一台已经能够自动打补丁的计算机开始,后续主要进行参数调整。以下内容适用于两者。
Debian 实际会为您安装哪些更新?
启用计时器并不等于同意安装所有更新。每个软件包源都包含发行版元数据:来源、标签、套件、代号和站点。unattended-upgrades 会查看每个可升级软件包的候选版本,读取该版本所属源的元数据;只有该源匹配 Unattended-Upgrade::Origins-Pattern 中的条目时,才会安装更新。没有匹配项就不会升级,这是设计如此,并且不会提示。
先输出您的匹配模式,再输出与其进行匹配的元数据:
apt-config dump Unattended-Upgrade::Origins-Pattern
grep -H -E '^(Origin|Label|Suite|Codename|Components):' /var/lib/apt/lists/*Release
apt-cache policygrep -H 会在每行保留文件名,因此您可以看到每组元数据属于哪个源。字段值就是匹配模式右侧的值。模式中的 ${distro_codename} 会在运行时替换为您当前使用的发行版代号,因此同一个文件可以跨发行版升级继续使用。
请将您自己的模式与旁边的元数据一起查看,因为“我只会获得安全修复,还是也会获得小版本更新”的答案就写在那里,没有其他地方:
- 指定
label=Debian-Security的模式会匹配安全归档。这是 Debian 发布安全公告的地方。 - 指定
-updates套件的模式会匹配 stable-updates,其中包含 Debian 在两次小版本发布之间提供的更新,例如时区数据。 - 指定普通发行版套件的模式会获取已发布的小版本更新。这会带来更多变更,也需要您进行更多测试。
- 您自行添加的软件仓库在为其编写匹配模式之前,完全不会匹配任何内容。
最后一点经常让人意外。第三方软件仓库有自己的来源和标签,因此 unattended-upgrades 会看到候选版本,也会看到该源不匹配任何内容,然后跳过它。为第三方软件仓库添加匹配模式需要谨慎决定,因为供应商仓库可能会在同一个套件中发布新的主版本。这样一来,您就等于同意让该软件在夜间自动进行主版本升级。
unattended-upgrades 也不会将您从一个 Debian 发行版迁移到另一个发行版。它只会在当前发行版内升级软件包。从一个 stable 发行版升级到下一个发行版,仍然需要由您自行安排并手动执行。
修改列表时,请记住 APT 会追加列表内容。在另一个文件中添加第二个 Origins-Pattern 块时,内容会追加到软件包提供的列表,而不是替换它。如果您的意图是替换列表,请先清空原列表:
#clear Unattended-Upgrade::Origins-Pattern;
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};请将本地修改放入一个排序位置晚于软件包提供文件的新文件中,例如 /etc/apt/apt.conf.d/52unattended-upgrades-local。50unattended-upgrades 是一个 conffile,因此编辑它会导致软件包的每次后续升级暂停,并询问如何处理您的版本。单独使用新文件则不会产生冲突。
在这里还值得输出另外两个键。Unattended-Upgrade::Allowed-Origins 是同一功能的旧格式,以 origin:archive 对的形式编写,目前仍会读取。因此,从教程复制的配置经常同时包含两种列表,却无法明确判断实际匹配的是哪一个。Unattended-Upgrade::Package-Blacklist 保存与软件包名称匹配的正则表达式;其中过于宽泛的表达式会阻止远超您预期范围的软件包升级。下面的试运行会输出实际生效的配置。
为什么没有在预期时间运行?
这里由两个 systemd timer 负责,而且它们执行的任务不同。apt-daily.timer 启动 apt-daily.service,后者刷新软件包列表并下载软件包。apt-daily-upgrade.timer 启动 apt-daily-upgrade.service,后者才会调用 unattended-upgrade。两者都会以不同参数运行 /usr/lib/apt/apt.systemd.daily。如果第二个 timer 被禁用或屏蔽,两个定期任务都可能显示为 1,但实际上永远不会安装任何内容。
systemctl list-timers 'apt-daily*' --all
systemctl is-enabled apt-daily.timer apt-daily-upgrade.timer
systemctl cat apt-daily-upgrade.timerlist-timers 会为每个单元提供 NEXT、LEFT、LAST 和 PASSED。没有 NEXT 的 timer 不会运行。is-enabled 输出 masked,表示有人强制将其关闭;无论您在 apt.conf.d 中写入什么,都不会改变这一点。
现在查看 systemctl cat 输出的 [Timer] 部分。OnCalendar 是 timer 最早可以触发的时间。RandomizedDelaySec 会在此时间之后增加随机等待,避免一组 Debian 机器在同一秒访问相同的镜像站。这就是为什么 NEXT 列显示的时间与 OnCalendar 不一致,也解释了为什么昨天的任务在不同分钟运行。这是设计如此。Persistent=true 表示机器在计划时间处于关机状态时,会在下次启动后不久运行任务,而不是跳过当天。
要调整时间窗口,请覆盖该单元,而不是直接编辑它:
sudo systemctl edit apt-daily-upgrade.timer[Timer]
OnCalendar=
OnCalendar=03:30
RandomizedDelaySec=30m空的 OnCalendar= 行是必需的,因为列表型单元设置会累积:省略这一行后,原有的软件包计划仍会保留,同时新增第二个计划。systemctl edit 会自动为您重新加载 systemd,因此请使用 systemctl list-timers 'apt-daily*' 确认结果,并查看新的 NEXT。
测试这些内容时,无需等待 timer:
sudo systemctl start apt-daily-upgrade.service
journalctl -u apt-daily-upgrade.service --since -1h有些人在 /etc/cron.daily 中查找时会对一个问题感到困惑:APT 仍会为不使用 systemd 的系统提供 /etc/cron.daily/apt-compat。使用 cat 读取它。在使用 systemd 的系统上,它会提前退出,因此不会重复执行任务。
验证其是否有效:unattended-upgrade --dry-run --debug
sudo unattended-upgrade --dry-run --debug二进制文件名是单数,软件包名称是复数。在此处输入 unattended-upgrades 会得到 command not found,很多人会据此误以为软件包缺失。
这条命令几乎可以回答所有“为什么跳过了那个软件包”的问题,因为它会输出自身的判断依据。输出开头附近会列出根据您的匹配模式计算出的源:
Allowed origins are: ...随后每个候选软件包占一行,并包含它将要安装的版本对应的源记录:
Checking: curl ([<Origin component:'main' archive:'stable' origin:'Debian' label:'Debian' site:'deb.debian.org' isTrusted:True>])然后是它将处理的软件包列表;如果系统没有需要处理的软件包,则会显示:
No packages found that can be upgraded unattended and no pending auto-removals将前两部分结合起来,诊断结果就很明确。找到您预期会升级的软件包对应的 Checking: 行。逐字段将它的源记录与上方列出的允许源进行比较。只要有一个字段不匹配,通常是 label 或 archive,这就是它被跳过的全部原因。
--dry-run 只在内存中标记软件包,不会安装任何内容,因此您可以随时运行它。它仍会追加写入 /var/log/unattended-upgrades/unattended-upgrades.log。
如果源记录匹配,但软件包仍被暂缓升级,请依次检查以下项目:
apt-mark showhold会列出您或某个工具设置了 pin 的软件包。unattended-upgrades 不会处理处于保留状态的软件包。- 此次升级可能需要删除或添加其他软件包。除非相关键允许,否则 unattended-upgrades 会避免此操作。请与
sudo apt-get -s upgrade的结果进行比较;它会在不应用这些安全规则的情况下显示相同的判断。 - 中断运行后,dpkg 处于半配置状态。使用
sudo dpkg --configure -a修复,然后重新检查。 /var没有可用空间,因此不会下载或解包任何内容。使用df -h /var检查。/boot中充满旧内核,导致下一次内核升级失败。检查df -h /boot,并输出apt-config dump Unattended-Upgrade::Remove-Unused-Kernel-Packages,确认是否已启用清理功能。
在计时器运行期间手动执行 apt,会得到 Could not get lock /var/lib/dpkg/lock-frontend。这条消息表示 unattended-upgrades 正在正常工作。等待它完成。
如何确定一次运行何时失败?
先查看日志,因为无论是否配置其他功能,日志都会生成:
sudo tail -n 40 /var/log/unattended-upgrades/unattended-upgrades.log
sudo tail -n 40 /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
journalctl -u apt-daily-upgrade.service --since -7d第一个文件是决策日志:记录检查了什么、选择了什么、安装了什么。第二个文件包含原始的 dpkg 输出;软件包的 postinst 脚本失败时,通常可以在这里看到相关信息。如果升级在机器关机过程中运行,还会生成单独的关机日志。
邮件是常用的报告方式:
Unattended-Upgrade::Mail "you@example.com";
Unattended-Upgrade::MailReport "on-change";MailReport 接受 always、on-change 和 only-on-error。only-on-error 听起来是更规范的选择,但对于没有人登录的服务器,通常反而不合适,因为一台完全停止升级的机器也不会再发送错误报告。这样一来,正常运行的服务器和已经停止工作的服务器都会保持沉默。on-change 会在每次安装内容时向您发送邮件,因此邮件也能证明定时器仍在运行。
只有机器能够发送邮件时,邮件才能离开机器。unattended-upgrades 会将消息交给本地邮件系统,因此需要安装 MTA(邮件传输代理),例如 postfix,或者安装兼容 sendmail 的中继客户端,例如 msmtp。使用 command -v sendmail 和 command -v mail 检查。两者都不存在时,报告不会发送到任何地方,升级仍会成功,整个故障也就无法被发现。还要注意,新 VPS IP 地址没有发送信誉,因此直接向公共邮箱发送的邮件经常会被归入垃圾邮件。通过您已经使用的邮件服务商进行中继,比自行运行邮件服务器更可靠。
如果您不想运行邮件系统,可以监控现有监控工具所记录的日志时间戳:
stat -c '%y %n' /var/log/unattended-upgrades/unattended-upgrades.log如果时间戳一周没有变化,说明定时器已经停止运行,无论配置文件中写的是什么。应将这项检查与常规 Linux 服务器维护检查放在一起。
系统是否应自动重启?
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";将 Automatic-Reboot 设置为 true 后,unattended-upgrades 会在无需确认的情况下重启系统,但只有在运行结束后存在 /var/run/reboot-required 文件时才会重启。该标记文件不是由 unattended-upgrades 创建的。必须由其他软件包创建。在 Debian 上,通常由 needrestart 创建。不要假设该文件一定存在。下次升级内核后检查:
ls -l /var/run/reboot-required /var/run/reboot-required.pkgs
uname -r
dpkg -l 'linux-image-*' | grep '^ii'如果该文件在您的计算机上始终不出现,Automatic-Reboot "true" 就永远不会触发。您可能会连续数月以为系统会因内核更新而重启,但实际仍在运行旧内核。针对最新安装的 linux-image 软件包执行 uname -r,即可确认这一点。
Automatic-Reboot-Time 会将重启安排在指定时间,而不是立即执行。将 Automatic-Reboot-WithUsers 设置为 false 后,只要仍有用户登录,系统就会跳过重启。对于始终有会话处于打开状态的服务器,这意味着系统根本不会重启。
需要决定的是系统重启后如何恢复运行,而不是是否执行重启。用户手动启动的服务不会自动恢复。启动时需要输入密码短语的加密卷不会挂载。相互依赖的两台计算机可能会以错误的顺序恢复。对于可接受在 02:00 中断一分钟的无状态 Web 服务器,可以启用自动重启。对于需要人工介入的系统,应保持该功能关闭,并针对标记文件配置告警,由人工选择重启时机。介于两者之间的是 needrestart。它会重启仍在使用已升级库的服务,因此除内核外的情况都能覆盖。其默认模式会在重启服务前请求确认,因此在无人值守运行中依赖它之前,请先阅读 /etc/needrestart/needrestart.conf。
测试版和不稳定版的工作方式相同吗?
上文介绍的是 Debian stable。其安全修复来自带有独立标签的单独归档。这种结构使“仅安装安全更新”成为可以表达的设置。其他套件的构建方式不同,因此从 stable 服务器复制的配置往往无法完全达到其作者预期的效果。在持续更新的套件上启用自动升级,还意味着可能无人值守地进行主版本升级。这需要作出不同的接受决定。如果您正在权衡这一选择,请参阅在服务器上运行 Debian stable、testing 或 unstable,了解各套件的定位。在 Red Hat 体系中,相同任务使用不同的工具和术语;Rocky Linux 和 AlmaLinux 上的 dnf-automatic通过自身的 timer 和配置文件完成这项工作。
自动升级可以缩短修复发布与安装之间的时间差。但它不会告诉您哪些内容仍然存在暴露风险,因此应配合检查服务器上的已知 CVE。CVE 是 common vulnerabilities and exposures 的缩写,是用于跟踪修复的公开标识符。
五分钟检查
apt-config dump APT::Periodic会打印出两个非零值的键。systemctl list-timers 'apt-daily*'会为两个计时器打印NEXT时间。sudo unattended-upgrade --dry-run --debug会打印允许的源,其中包括与您的发行版代号对应的安全归档。sudo systemctl start apt-daily-upgrade.service执行完成,并且/var/log/unattended-upgrades/unattended-upgrades.log的时间戳发生变化。- 一周后,同一个日志会列出它安装的软件包。
前四项通过,说明机器已完成配置。第五项通过,说明配置已正常工作。
FAQ
为什么 Debian 会安装 unattended-upgrades,却将其保持为关闭状态?
该软件包会提出一个 debconf 问题,即 unattended-upgrades/enable_auto_updates,并根据答案写入 /etc/apt/apt.conf.d/20auto-upgrades。Debian 安装程序会为该问题保存 false,因此当软件包作为任务的一部分或依赖项安装时,它会被配置为不执行任何操作。运行 sudo debconf-show unattended-upgrades 查看已保存的答案,然后运行 sudo dpkg-reconfigure --priority=low unattended-upgrades 修改答案。低优先级设置很重要,因为在默认优先级下,该命令会直接退出,根本不会显示此问题。
如何在不等待计时器的情况下测试 unattended-upgrades?
运行 sudo unattended-upgrade --dry-run --debug。该命令会输出它将接受的软件源;每个可升级软件包占一行 Checking:,并附带该软件包的软件源记录;同时输出它将安装的软件包列表,但不会实际安装任何内容。若要执行实际升级流程,请运行 sudo systemctl start apt-daily-upgrade.service,然后结合 /var/log/unattended-upgrades/unattended-upgrades.log 阅读 journalctl -u apt-daily-upgrade.service --since -1h。
unattended-upgrades 是否也会安装常规更新,而不只是安全修复?
只有匹配某个模式时才会。软件包的来源匹配 Unattended-Upgrade::Origins-Pattern 中的某个条目时,才会安装该软件包的升级版本。安全归档、stable-updates 套件以及您自行添加的任何软件仓库都是独立条目。在您自己的计算机上输出 apt-config dump Unattended-Upgrade::Origins-Pattern,然后将其与 grep -H -E '^(Origin|Label|Suite|Codename):' /var/lib/apt/lists/*Release 进行比较。它也不会将系统升级到其他 Debian 发行版。
为什么升级不会在计时器设定的时间运行?
apt-daily-upgrade.timer 会在 OnCalendar 的基础上设置 RandomizedDelaySec,因此 systemd 会在该时间窗口内随机选择一个时刻运行,而不是在日历指定的时间触发。这样可以将负载分散到所有指向同一组镜像站的 Debian 计算机上。systemctl list-timers 'apt-daily*' 会显示它实际选择的时刻。若要调整该时间窗口,请运行 sudo systemctl edit apt-daily-upgrade.timer,并为其提供一行空的 OnCalendar=,后接您自己的值。
是否应为安全更新启用自动重启?
只有在可以安全接受计划外重启的环境中才应启用。每次运行后,只要存在 /var/run/reboot-required,Unattended-Upgrade::Automatic-Reboot "true" 就会在不确认的情况下重启;该标记文件由其他软件包(通常是 needrestart)写入,而不是由 unattended-upgrades 本身写入。内核升级后,请确认该文件确实会出现在您的计算机上,再信任此设置。对于启动时需要输入密码的计算机,或运行着由人员手动启动的服务的计算机,应将该设置保持为 false,并监控该标记文件,在出现标记时发出告警,由人员选择重启时机。