一台放在机柜深处的 PVE 7 节点前一天还好好的第二天例行巡检敲下apt-get update终端只回了一行冷冰冰的E: ... exit code 100剩下的信息全得自己挖。这个场景在 PVE 7 上出现频率相当高尤其集中在版本末期、仓库地址调整、密钥轮换、以及从旧版本升级上来的机器上。apt-get update failed exit code 100 这个报错本身不带任何定位信息它只是 apt 在“任何一个源没能完整拉下来”时给出的统一退出码真正的线索藏在它上面那几行输出里。这篇内容面向正在维护 PVE 7底层是 Debian 11节点的运维、虚拟化爱好者、以及把家里小主机当成实验环境的朋友从退出码的语义讲到源文件布局再落到一条条可以直接抄的命令把这个报错彻底拆开。看完全文你应该能在十分钟内判断出是仓库地址问题、密钥问题、还是网络链路问题并且知道改哪个文件、改完怎么验证。1. 先把 exit code 100 的含义弄清楚1.1 apt 的返回值不是随便给的很多人一看到 100 就以为是“权限不足”或者“磁盘满了”其实 apt 的退出码有固定语系0 表示一切正常1 表示有非致命的告警但流程走完了100 表示 apt 自己遇到了无法继续的错误提前退出了101 一般和 dpkg 相关而 200 多开头的通常是脚本钩子返回的异常。关键在于100 是 apt 层面的“我干不下去了”它不是某一条具体错误的专属代码。源返回 404、401、签名验证不通过、域名解析不出来、锁文件被占、磁盘写满最后都可能收敛成同一个 100。这就决定了排查思路不能盯着“100”本身看而要往上翻输出。我在现场处理这类问题的第一动作永远是让终端输出完整化而不是凭经验猜。因为 apt 的输出是有层次结构的Err:开头是某一条源抓取失败W:开头是警告E:开头才是最终致命错误而 100 是最后所有 E 行汇总之后的结果。1.2 为什么说它是最“泛”的错误码举个具体的对照。同样是 100输出可能是下面这几种完全不同的东西# 情况一企业源没有授权 Err:3 https://enterprise.proxmox.com/debian/pve bullseye InRelease 401 Unauthorized # 情况二旧版本仓库已经搬走 E: The repository http://镜像站/debian buster Release does not have a Release file. # 情况三签名密钥缺失 W: GPG error: ... The following signatures couldnt be verified because the public key is not available: NO_PUBKEY KEYID # 情况四名字解析失败 E: Failed to fetch ... Temporary failure resolving 域名 # 情况五锁被占 E: Could not get lock /var/lib/apt/lists/lock. It is held by process 1234这五种情况对应五套完全不同的处理动作前者要改源第二个要换版本代号第三个要补密钥第四个要查网络和域名解析第五个要清进程。所以“exit code 100 怎么修”这个问题没有统一答案只有“先分类、再对症”的流程。1.3 一分钟现场取证我习惯用一条组合命令把现场信息一次性捞全避免反复试错apt-get update 21 | tee /tmp/apt-update.log; echo 退出码: ${PIPESTATUS[0]} grep -E ^(Err|E|W): /tmp/apt-update.log第一行把标准错误也重定向进管道同时落盘一份日志第二行再单独把关键行筛出来。这样做的价值在于apt 有时候会刷出几十行进度条文字真正的错误行混在中间肉眼很难抓。落到文件里再 grep一眼就能看到有几条Err:、它们的域名分别是什么。紧接着看三个基础状态df -h /var /var/cache/apt/archives df -i / timedatectl status | head -5第一条看空间第二条看 inode小容量系统盘装了大量小文件时inode 会先于空间耗尽第三条看时间。这三个检查加起来不到五秒却能排掉相当一部分“看起来玄学”的故障——尤其是时间偏差这一项很多人根本想不到它会导致签名验证失败因为 apt 校验 Release 文件时会检查签名时间窗口系统时间跑到未来或者落后太多就会报Release file is not valid yet或者has expired。注意不要在没有看日志的情况下直接rm -rf /var/lib/apt/lists/*。这个动作确实能解决一部分缓存损坏问题但它会掩盖掉原始报错等缓存重建后如果还报错你又得重新制造一次现场。2. 按发生概率排序的六类根本原因2.1 仓库地址失效最常见的一类PVE 7 的源文件里通常有三到四个仓库Debian 主源、Debian 安全源、PVE 自身源装过 Ceph 的还会有 Ceph 源。任何一个地址失效整条 update 都会返回 100。地址失效的形态主要有两种第一种是版本代号过期。Debian 的稳定版仓库在生命周期结束后会从主站迁走如果你机器上还留着一份老旧的源文件指向已经不在主仓库路径下的代号就会得到does not have a Release file。从 PVE 6 升级上来的机器尤其容易中招因为升级过程不一定把sources.list.d/下的每一份文件都清理干净。第二种是商业仓库未授权。PVE 默认会带一个企业源配置文件如果没有有效授权访问它会返回 401。这个 401 不会导致整个仓库列表全挂但它足以让 apt 以 100 退出。这是 PVE 上最经典的“明明网络很好就是 update 不过”的场景。2.2 签名与密钥问题不报错但过不去Debian 的仓库用 GPG 签名保护apt 需要本地持有对应的公钥才能验证 Release 文件。密钥过期、密钥被替换、或者密钥环文件损坏都会让验证失败。典型输出是NO_PUBKEY后面跟一串十六进制指纹或者KEYEXPIRED。这类问题的迷惑性在于网络是通的文件也下载下来了但 apt 拒绝使用。有些人看到下载进度条走到 100% 就以为成功了忽略了后面的签名错误。我这里有个小经验——如果 update 的输出里有下载动作但没有 Reading package lists... Done 结尾八成是签名环节被拦了。密钥问题还分两种处理路径老式的apt-key方式把密钥加进全局信任环简单但已经在被淘汰新式做法是把密钥以独立 keyring 文件放在/etc/apt/keyrings/或/usr/share/keyrings/下再在源条目里用signed-by精确指定。PVE 7 时代两种都还能用但如果你打算长期维护建议往新式做法上靠。2.3 名称解析与链路问题看起来像源的问题Temporary failure resolving这个错误很直白就是域名没解析出来。原因可能是/etc/resolv.conf里的解析服务器地址不可达、本机网络配置改动后没生效、或者上游链路对大文件传输不友好。PVE 节点经常被配置成独立网桥模式管理员改了vmbr0的配置之后忘了检查宿主自身的出口就会出现“虚拟机都能上网宿主 apt 不通”的怪现象。还有一种更隐蔽的情况IPv6 半通。机器拿到了 IPv6 地址apt 默认会优先尝试 IPv6 连接但上游 IPv6 链路实际上是断的于是每条请求都要等到超时才回落。表现就是 update 特别慢跑了几分钟最后报一堆 fetch 失败。这时候只要强制走 IPv4 就能立刻恢复。2.4 锁冲突与缓存损坏最容易被误判Could not get lock /var/lib/apt/lists/lock这条错误经常出现在两种场景一是后台的自动更新任务正在跑比如 unattended-upgrades 或者定时任务你手动敲 update 撞上了二是上一次 apt 进程被中断CtrlC、SSH 断线、系统卡死锁文件残留下来了。缓存损坏则更微妙。apt 会把源索引缓存在/var/lib/apt/lists/下如果某次下载被强行中断可能留下半截文件后续 update 读到它就报解析错误。表现形式可能是Malformed entry、Unable to parse package file之类最终也是 100 退出。2.5 时间偏差与证书校验系统时间不准带来的连锁反应很多人低估了。除了前面提到的签名时间窗口问题如果源用的是 HTTPS还会涉及 TLS 证书有效期校验。时间跑到 2026 年去访问一个证书已经过期的源或者时间退回到 2020 年访问一个还没生效的证书都会直接握手失败。PVE 节点的时间同步一般交给系统自带的时间同步服务但如果节点长期离线、或者虚拟化环境里宿主的时间飘了客户机的时间就会跟着飘。我见过一台节点因为主板电池问题每次断电重启后时间都回到出厂值管理员修了三次源才想起来看时间。2.6 磁盘与 inode 耗尽这条在实验环境里出现概率不低。PVE 的系统盘很多人只分 8G 到 16G跑一段时间之后/var/cache/apt/archives里堆着几个 G 的 deb 包/var/log里堆着大量日志突然某天 update 就失败了。报错通常很直白You dont have enough free space in /var/cache/apt/archives/但如果你只看了最后的 100就容易忽略。inode 耗尽则更隐蔽df -h看着还有空间df -i一看已经 100%。这种情况常见于大量小文件堆积比如某些服务的临时文件目录。3. PVE 7 的源文件布局与正确写法3.1 源文件到底放在哪几个位置Debian 系的 apt 会按固定顺序读取源配置主文件/etc/apt/sources.list然后是目录/etc/apt/sources.list.d/下的所有.list文件以及.sources格式的新式文件Deb822 格式。PVE 的默认安装会在这两处都放东西路径典型内容说明/etc/apt/sources.listDebian 主源、更新源、安全源系统基础仓库/etc/apt/sources.list.d/pve-enterprise.listPVE 企业源默认启用未授权时返回 401/etc/apt/sources.list.d/ceph.listCeph 仓库装过 Ceph 组件才有/etc/apt/sources.list.d/pve-install-repo.list无订阅源部分安装方式会生成/etc/apt/sources.list.d/*.list第三方源手动添加的最容易残留过期条目排查的第一步永远是把目录完整列一遍而不是只看主文件ls -l /etc/apt/sources.list /etc/apt/sources.list.d/ grep -rhv ^\s*# /etc/apt/sources.list /etc/apt/sources.list.d/ | grep -v ^\s*$第二条命令把两份位置里所有未被注释的行汇总打印出来这样你就能一眼看全机器上实际生效的仓库清单。我遇过好几次“主文件已经改干净了但还是报错”最后发现是sources.list.d/里躺着一份早就忘了的文件。3.2 企业源与无订阅源的取舍逻辑这是 PVE 上最有争议也最容易出错的一块。默认情况下安装完 PVE 7 会启用企业源它提供经过测试的稳定更新但需要有效授权。没有授权的节点访问它会得到 401进而让 update 返回 100。两个选择摆在面前要么关闭企业源改用无订阅源要么保持企业源不动接受 401 报错。当然如果你确实有授权那就什么都不用改问题肯定在别处。从维护角度看个人实验环境和中小规模内部环境用无订阅源是完全合理的做法它的更新频率更高、覆盖的包更全代价是稳定性略逊于企业源。具体操作就是把企业源那一行注释掉# 编辑 /etc/apt/sources.list.d/pve-enterprise.list # 在行首加 # 注释掉 # deb https://enterprise.proxmox.com/debian/pve bullseye pve-enterprise然后在主文件或独立文件里加上无订阅源# 写入 /etc/apt/sources.list.d/pve-no-sub.list deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription这里有个细节值得说清楚不要图省事把两种源同时启用。同一个包在两个仓库里都有apt 会按优先级挑选虽然一般不会直接报错但版本混装带来的行为差异很难排查。要么企业源要么无订阅源二选一干净利落。注意bullseye这个代号必须和系统版本严格对应。PVE 7 基于 Debian 11代号就是 bullseye。写成 buster 或者 bookworm 都会导致仓库路径 404进而 100。3.3 Ceph 源的特殊处理如果机器上装过 Ceph 组件sources.list.d/里会多出一份 Ceph 仓库配置。它同样区分企业版和社区版而且社区版的地址路径里带着版本代号。这块的坑在于Ceph 的仓库地址结构是debian-ceph版本代号和你装的 Ceph 大版本必须匹配写错了同样是 404。处理原则跟 PVE 主源一致——如果不用企业版就把对应行注释掉换成社区版如果完全不用 Ceph那更简单直接注释掉整份文件即可。不用的仓库留着启用状态是给自己埋雷因为它每次 update 都要去访问一次一旦地址哪天变了你的例行维护就会突然失败。3.4 Debian 主源与安全源的规范写法Debian 部分最容易出问题的是安全源。它和主源的路径结构不一样少了dists/这一层对应关系具体写法是debian-security而不是debian。写错了就会 404。一份规范的 PVE 7 主源配置大致是这样# /etc/apt/sources.list deb http://你的镜像站/debian bullseye main contrib deb http://你的镜像站/debian bullseye-updates main contrib deb http://security.debian.org/debian-security bullseye-security main contrib三行的分工要讲明白第一行是主仓库包含绝大多数软件包第二行是稳定版的更新仓库收的是已经过验证的修正第三行是安全更新优先级最高。三者缺一不可少了更新源你会错过大量修正少了安全源则在安全层面留下缺口。另外contrib这个组件在一些精简配置里会被省掉但 PVE 的一些附加工具依赖它建议保留。如果你的镜像站不提供某个组件apt 会报Unable to find expected entry这也是 100 的常见来源之一。4. 一套可以直接照做的修复流程4.1 第一步备份永久的习惯改源之前先备份这条不用讨论。我一般直接把整个目录打包mkdir -p /root/apt-backup-$(date %F) cp -a /etc/apt/sources.list /root/apt-backup-$(date %F)/ cp -a /etc/apt/sources.list.d /root/apt-backup-$(date %F)/用cp -a而不是普通cp是为了保留权限和属主信息恢复的时候更省事。日期目录名是为了多次操作能留档万一某次改动引入了新问题可以逐次回退对比。4.2 第二步按错误类型分流处理拿到/tmp/apt-update.log之后按下面的分流表处理日志特征根本原因处理动作401 Unauthorized商业仓库无授权注释对应企业源文件换成社区源404 Not Found/does not have a Release file地址或版本代号过期核对代号与路径改正或删除该行NO_PUBKEY/KEYEXPIRED签名密钥缺失或过期导入对应公钥或改用signed-byTemporary failure resolving域名解析失败检查/etc/resolv.conf用getent hosts验证Could not get lock锁被占用找持锁进程确认无任务后清理not valid yet/has expired系统时间偏差校准时间同步服务No space left空间或 inode 耗尽清理缓存与日志或扩容表格只是索引真正动手时还要结合具体输出。比如同样是 404可能是代号写错也可能是镜像站本身不提供该组件两者处理方式不同——前者改配置后者换镜像站或去掉该组件。4.3 第三步修密钥的两种做法如果是密钥问题先拿到日志里那串NO_PUBKEY后面的指纹然后两种路径任选。老式做法直接用 apt-key 拉取apt-key adv --keyserver 密钥服务器地址 --recv-keys KEYID但它依赖密钥服务器可达在受限网络里经常拉不下来。我更推荐新式做法直接下载 keyring 文件放进系统目录# 下载密钥文件后转为 keyring 格式 gpg --dearmor 下载的密钥文件 /usr/share/keyrings/仓库名.gpg # 在源条目里显式指定 deb [signed-by/usr/share/keyrings/仓库名.gpg] http://仓库地址 bullseye main--dearmor这一步不能省因为 apt 需要的是二进制 keyring 格式而不是 ASCII armored 的文本。这一步做错的表现是 apt 报The following signatures couldnt be verified看起来跟没导入一样非常容易让人怀疑人生。提示signed-by的路径必须是绝对路径写相对路径 apt 会直接忽略然后回落到全局信任环等于没配。4.4 第四步清缓存与重建索引源文件改完之后不要直接 update先做一次彻底的缓存清理apt-get clean rm -rf /var/lib/apt/lists/* apt-get update三步各有作用clean清的是/var/cache/apt/archives里的 deb 包缓存能腾出空间删lists目录清的是索引缓存把可能损坏的半截文件全部干掉最后才是重新拉索引。顺序不能反先 update 再清缓存等于白做。执行完 update正常的结尾应该是这样的Reading package lists... Done看到这行才算真的通过。有些情况下 apt 会打印一堆W:警告但仍然返回 0那是可以接受的比如某个仓库的签名算法被标记为将来弃用属于提示性质。4.5 第五步验证与后续升级update 通过之后正式升级前建议先看一眼有哪些包会被动apt list --upgradable这一步的价值在于避免升级把内核或者关键组件一起带上去。PVE 节点的内核升级通常需要重启才生效如果你是在业务时段操作先看清楚升级清单再决定要不要立刻dist-upgrade。真正执行升级时PVE 上更推荐用apt dist-upgrade而不是apt upgrade因为前者能处理依赖关系变化带来的包增删。不过dist-upgrade的破坏力也更大操作前确认快照或者备份到位。虚拟机环境里我会先给节点做一份快照再动手物理机就老老实实先备份/etc。5. 常见问题速查与排查经验5.1 一张能省时间的速查表现象快速验证命令处理要点update 很慢最后超时time getent hosts 域名检查解析与 IPv6 链路必要时强制 IPv4报 401 但网络正常curl -I 企业源地址无授权就注释掉企业源报 404 且路径看着没错curl -I 完整仓库地址核对版本代号与组件名报 NO_PUBKEYapt-key list补密钥或改 signed-by报锁占用lsof /var/lib/apt/lists/lock确认无任务后删锁文件报时间不合法date -u对比实际时间重启时间同步服务报空间不足df -h与df -i清 archives 与日志这张表我贴在运维笔记的第一页因为它覆盖了九成以上的现场情况。真正需要深入排查的往往是复合型故障——比如既有个过期源又有时间偏差修好一个还剩一个这时候容易误以为方案无效。5.2 几个特别容易误判的地方第一个误判是把 401 当成网络问题。看到 fetch 失败就本能去 ping 域名结果 ping 得通就陷入混乱。实际上 401 是应用层的拒绝网络完全正常问题出在授权。判断方法很简单用curl -I请求那个地址如果返回 401 就说明链路没问题。第二个误判是以为注释掉源文件就生效了。文件名以.list结尾才会被读取如果你新建文件时写成了.list.bak或者.txtapt 根本不看它。我见过有人把企业源改名成pve-enterprise.list.disabled这是对的做法但也有人改成pve-enterprise.disabled后缀变了apt 依然会读——因为它只认.list和.sources其他后缀直接忽略但如果是.list被改成了别的名字那确实不读了。不过要注意反向的坑把文件内容注释掉是最保险的改扩展名在不同版本上行为有细微差别。第三个误判是在容器里排查宿主的问题。PVE 上经常有 LXC 容器容器的源配置和宿主是两套。你在宿主上改了半天容器里依然报错因为容器有自己独立的/etc/apt/sources.list。这个坑我踩过一次在一个跑着若干容器的节点上折腾了半小时最后发现报错的终端是容器的。5.3 我踩过的几个坑有一次在一台老节点上update 持续报 100日志里是 DNS 解析失败。我改了/etc/resolv.conf重启网络还是失败。最后发现是这台机器的网络配置被容器平台接管过resolv.conf被自动生成的版本覆盖了我手改的内容在下次网络重载时就丢了。解决办法是在网络配置层固定解析服务器而不是改那个会被覆盖的文件。还有一次是时间问题但表现得很邪门apt 报的是某条源Release file is not valid yet看起来像是源的问题。我换了三个镜像站都一样才回过头看时间。系统时间比实际快了将近一天timedatectl显示同步服务没在跑。启动同步服务之后立刻恢复正常。这个案例说明当所有源都报同一个“时间相关”错误时别怀疑源怀疑本机。再有一次是 inode 的问题。df -h显示根分区还有 2G 可用怎么都不像空间不足但 update 就是失败。df -i一看inode 使用率 100%。清理掉某个服务留下的海量临时小文件之后恢复正常。这件事之后我养成了在排查清单里加df -i的习惯很多“看起来资源充足却报错”的场景都跟它有关。最后分享一个日常维护的小习惯我会在每台节点的/root下留一份apt-check.sh里面就是前面那几条取证命令的组合出问题时先跑它把输出贴进日志文件然后才动手改配置。这样做的好处是改完之后能拿前后两份日志对比确认到底是哪一条错误消失了而不是凭感觉判断“应该好了”。改配置不记录、修好了不知道修好了什么是维护工作里最容易反复踩同一个坑的原因。