各家的 Ubuntu 源都开始限速、下线老版本之后我才意识到自建本地 Mirror 源不是大厂的专属需求。手头 30 台开发机共用一条出口带宽某天全员同时 apt update几秒钟内就把带宽打满接着就是各种超时和 Hash Sum mismatch。从那天起我花了一个周末把 Ubuntu 18.04 的本地镜像源彻底搭了起来包括内网发布、客户端接入、增量同步和故障排查。这篇博文把整个建设过程完整复盘一遍从规划、选型到踩坑一步不落。1. 先盘清楚自建 Mirror 源解决什么问题成本值不值1.1 内网加速和离线交付是两种最常见的诉求自建本地 Mirror 源本质就是把官方源比如 Ubuntu 的 archive 和 security 仓库在某个时间点的软件包和索引同步到内网服务器再由这台服务器向内网机器提供 apt 服务。常见的使用场景无非两种。第一种是内网加速。开发机、测试服务器、CI 构建节点数量多了以后每次 apt update 都要把几百 MB 的索引拉一遍安装新包又要从公网慢慢下载。有了本地源同一机房内走千兆内网apt update 从几十秒降到一两秒装个软件包的等待时间从吃一顿饭变成喝口水。第二种是离线交付比如物理隔离的机房、不能连接公网的生产网段或者需要定期给一批相同版本机器批量装包这时候直接拷一个镜像盘进去比逐台机器apt download再 scp 高效得多。这两种场景我都实际经历过。早期我图省事只在几台机器上配了 apt 缓存代理比如用 squid 做正向缓存后来发现缓存代理只能解决“重复下载”的问题解决不了“首次下载还是要走公网”的问题而且各种 PPA 和企业内部自定义源混在一起缓存命中率并不稳定。最终还是老老实实搭了完整镜像。1.2 成本账要提前算磁盘、带宽、维护时间动手之前建议先把成本账摆到桌面上避免搭完发现磁盘放不下或者带宽撑不住。先说磁盘。Ubuntu 18.04bionic如果只同步 amd64 架构的 main、restricted、universe、multiverse 四个组件加上 updates 和 security 两个仓库整体体积大约在 90 到 120 GB 之间。如果只保留常用组件main 加 updates 加 security大约 50 到 60 GB。这里说的是解压后的目录实际占用不是下载流量。如果你还想同步源码包 deb-src体积会再膨胀 30% 左右。再说网络。首次全量同步假设 80 GB出口带宽 100 Mbps 时理论峰值每小时约 12.5 MB/s换算下来全量下载也需要将近两个小时实际受限于官方源对单 IP 的限速和磁盘写入能力通常要放宽到四到六个小时。这个时间点如果放在业务高峰期显然不合适建议安排到凌晨或者非工作时段跑。项目建议值说明磁盘空间至少 150 GB含临时空间和索引余量实际镜像 90-120 GB预留 30% 余量内存2 GB 起步apt-mirror 的临时排序占内存不多但索引解析高峰需要 1 GB 以上CPU2 核即可瓶颈在网络和磁盘不在 CPU同步窗口首次选凌晨后续选非高峰降低与业务流量抢带宽的风险出口带宽单独为镜像机留 50 Mbps 以上否则首次全量同步会拖垮其他服务维护时间也要算进去。镜像不是搭完就能永久放着不管的上游每天都会有安全更新进来你至少需要每周或者每天定时跑一次增量同步再花几分钟看一眼日志。如果镜像机本身没有专职运维可以把同步任务交给 cron 或 systemd timer出错时通过日志和告警感知不需要每天人工干预。2. 方案选型apt-mirror、debmirror、rsync 到底该选哪个2.1 三个主流工具的工作机制和适用边界Ubuntu 系自建 apt 源最常见的工具有三个apt-mirror、debmirror以及直接用 rsync 从官方镜像站同步。它们的思路完全不同用错工具会走不少弯路。apt-mirror 的工作方式最简单通过配置文件列出要同步的仓库地址然后调用底层下载工具按目录结构把dists和pool下的文件拷贝到本地。它不需要理解 Debian 仓库内部的元数据关系所有索引和包都是原样搬下来。它最大的优势是配置门槛低一个 mirror.list 文件列十几行就能跑起来社区里大多数自建 Ubuntu 源教程用的都是它。缺点是每次运行都会重新扫描全部索引没有真正意义上精细的增量控制虽然有基于时间戳的跳过机制但同步过程中的日志量较大而且同步中断后需要妥善处理元数据文件。debmirror 是 Debian 社区的老牌工具它更懂仓库内部结构。可以用一个命令按架构过滤、按组件过滤甚至可以选择只同步索引不同步包文件。它还支持通过 rsync 协议从镜像站同步增量效率更高。缺点是命令行参数非常多第一次用会有点发怵而且 Ubuntu 仓库对 rsync 访问的支持并不像 Debian 那么普遍不是所有镜像站都开放 rsync 端口。rsync 直接同步是追求极致效率的选择适合你所在的网络到上游镜像站之间链路非常稳定、你也很熟悉镜像站目录结构的场景。用 rsync 可以做到真正的增量同步文件级别对比同步完还能通过--delete精确删除上游已移除的文件非常适合维护一个长期干净的镜像盘。代价是脚本和异常处理全靠自己写对自动化能力要求高得多。2.2 我对 18.04 做的选型结论和理由综合手头的服务器配置和维护精力我最终选择 apt-mirror 为主、rsync 为辅的混合方案常规同步用 apt-mirror遇到底层文件异常需要强制校准某个具体组件目录时用 rsync 做一次定向修复。理由说来也简单。18.04 已经进入生命周期的后期主仓库更新频率很低绝大多数新变更集中在 security 仓库单轮增量数据量很小apt-mirror 的时间戳跳过机制已经足够应付。而 debmirror 虽然看起来更专业但多出来的复杂参数在日常维护中带来的收益并不明显。至于纯 rsync 方案除非你每天要维护多个发行版、多架构的镜像否则脚本出问题的概率比工具本身还要高。选型还有一个容易被忽略的维度——团队其他人能否接手。apt-mirror 的 mirror.list 打开就是纯文本列表任何人看一眼都明白在同步什么debmirror 的命令行和过滤表达式则需要额外理解成本。运维工具的可维护性有时候比技术上的“最优”更重要。3. 基于 apt-mirror 的完整搭建流程从 mirror.list 到 Nginx 发布3.1 安装和 mirror.list 配置逐行拆解在 Ubuntu 18.04 上安装 apt-mirror 很简单sudo apt update sudo apt install -y apt-mirror安装后核心配置文件是/etc/apt/mirror.list。我需要同时处理 Ubuntu 的两个域名这是新手最容易忽略的地方普通更新bionic 和 bionic-updates在archive.ubuntu.com下而安全更新bionic-security在security.ubuntu.com下两个域名是两套完全独立的仓库树必须分开写。############# config ################## set base_path /var/spool/apt-mirror set mirror_path $base_path/mirror set skel_path $base_path/skel set var_path $base_path/var set postcleanup $var_path/postcleanup.sh set defaultarch amd64 set nthreads 8 set _tilde 0 ############# end config ############## deb http://archive.ubuntu.com/ubuntu bionic main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted universe multiverse deb http://security.ubuntu.com/ubuntu bionic-security main restricted universe multiverse clean http://archive.ubuntu.com/ubuntu clean http://security.ubuntu.com/ubuntu逐行说几个关键点。base_path是镜像文件的根目录所有同步内容都在这个目录下。mirror_path下面会自动生成archive.ubuntu.com/ubuntu和security.ubuntu.com/ubuntu两棵目录树。defaultarch设置为amd64如果你内网有 arm64 机器需要把对应源的 URL 改成deb [archarm64] http://ports.ubuntu.com/ubuntu-ports ...注意 arm64 不走 archive 域名而是ports.ubuntu.com这在 18.04 里特别容易踩坑。nthreads是并发下载线程数网络好、磁盘是 SSD 时可以调到 8 到 12机械硬盘建议保持 4。不要盲目调高并发太高会导致大量半成品文件堆积反而拖慢整体速度。postcleanup指向的脚本用于清理上游已删除的旧包如果不开镜像盘会随着时间推移积累大量残留包文件白白占磁盘。首次同步前可以默认开启没问题。deb-src源码仓库我默认没有开启镜像源体积能省 30%。如果你的团队确实需要编译调试并拉取源码包再单独加上不迟。对于 18.04 这种已经进入维护期的系统源码包的使用率非常低。3.2 首次全量同步的吞吐优化和中断恢复写完配置文件后先不要直接丢到后台跑我建议用交互方式启动方便直观观察输出sudo -u root apt-mirror /etc/apt/mirror.list注意运行时的用户权限。apt-mirror 默认会往/var/spool/apt-mirror写文件如果你希望用普通用户跑需要先chown整个目录。我个人在测试机上直接以 root 运行生产机上则新建了一个mirror-user账号。权限隔离的好处说出来都是泪一旦镜像机本身被入侵普通用户权限的同步进程能造成的破坏比 root 小得多。首次全量同步的输出是典型的 wget 风格每下载一个文件打印一行。看到满屏200 OK属于正常现象不要被刷屏吓到。如果看到大量404 Not Found先不要急着怀疑配置Ubuntu 仓库在pool目录探测文件时产生 404 是正常行为关键是看最终有没有“download complete”或者“finish”之类的收尾输出。同步过程中途断网或者被 kill 掉怎么办直接重新跑一次。apt-mirror 的 wget 带时间戳检测已经下载完成的文件会被跳过未完成的文件会重新拉取具备天然的断点续传能力。我实测下来中断后重新执行之前同步完的文件不会重复下载这是它虽然“看起来笨”但实际可用性很高的原因。如果出现Hash Sum mismatch之类的问题不要尝试通过手动删除单个文件来解决后面第 5 节会专门说排查链路。正确的做法是找到出问题的组件目录把对应的dists/bionic*/.../binary-amd64和pool下相关子目录删掉再重跑一遍让 apt-mirror 重新拉取元数据。3.3 用 Nginx 把镜像目录发布到内网镜像同步到本地之后剩下的事就是把它变成一个标准 HTTP 服务。我用 Nginx 实现配置非常简单但有一个层级关系必须理清楚。apt-mirror 同步完的目录结构是/var/spool/apt-mirror/mirror/archive.ubuntu.com/ubuntu/dists/bionic/... /var/spool/apt-mirror/mirror/security.ubuntu.com/ubuntu/dists/bionic-security/...所以 Nginx 的root必须指向.../archive.ubuntu.com/ubuntu这一层而不是mirror这一层否则客户端请求/dists/bionic/Release时会直接 404。这是我的第一个生产环境配错的点整整排查了一个小时才想明白。完整 Nginx 配置如下server { listen 80; server_name archive.ubuntu.com; root /var/spool/apt-mirror/mirror/archive.ubuntu.com/ubuntu; autoindex on; autoindex_exact_size off; autoindex_localtime on; add_header Cache-Control public, max-age86400; } server { listen 80; server_name security.ubuntu.com; root /var/spool/apt-mirror/mirror/security.ubuntu.com/ubuntu; autoindex on; autoindex_exact_size off; autoindex_localtime on; add_header Cache-Control public, max-age86400; }两个 server 块分别对应官方两个域名。autoindex on非常关键它不仅方便你在浏览器里检查目录还让 apt 客户端在必要时能列出目录内容。add_header Cache-Control是后来加的让内网客户端的元数据能被缓存进一步减轻镜像机压力。Nginx 我建议直接监听 80 端口因为客户端默认走 http。不要自作聪明上 https——内网源不是安全边界签名验证本来就在 apt 层做没有任何必要引入 TLS 证书的额外复杂度。4. 客户端接入私有源的三种姿势与安全边界4.1 零改造方案内网 DNS 把域名引到镜像机镜像搭好后最省事、最不容易出错的客户端接入方式不是改机器上的 sources.list而是在内网 DNS 上动手脚。把archive.ubuntu.com和security.ubuntu.com两个域名直接解析到镜像机的内网 IP客户端完全不需要动任何配置文件apt update 就会自动走到本地镜像。这个方案对客户端是零改造对所有机器一视同仁连新装系统的机器上线即生效。这一切成立的前提是镜像客户端走的是 http 协议并且你认为覆盖内网所有机器的 DNS 解析是可接受的。Ubuntu 18.04 默认官方源就是 http没有 https 的强制跳转所以劫持后签名验证天然通过。如果客户端曾经手动改成过https://archive.ubuntu.comDNS 劫持会导致证书不匹配因为镜像机并没有官方域名的有效证书这种情况需要先把客户端改回 http。DNS 层面的覆盖还有一个额外好处apt 的 GPG 签名验证不会因为 IP 变化而失败。因为 apt 验证的是 Release 文件本身的签名和文件内容的一致性跟请求的域名是哪个 IP 完全没有关系。很多人担心“改成内网 IP 会不会签名校验失败”答案是不会只要文件完整、密钥存在就验得过去。4.2 直接改 sources.list 的标准写法与目录层级对应关系如果内网没有 DNS 改造条件那就只能逐台改/etc/apt/sources.list。写法上注意路径要和 Nginx 的 root 对齐。sudo sed -i s|http://archive.ubuntu.com/ubuntu|http://10.0.0.8/|g /etc/apt/sources.list sudo sed -i s|http://security.ubuntu.com/ubuntu|http://10.0.0.8/|g /etc/apt/sources.list这里容易绕晕的地方要再说透一点。客户端配置里 URL 的路径和镜像机 Nginx 的root是拼在一起组成最终请求路径的。Nginx 的root已经指到.../archive.ubuntu.com/ubuntu所以客户端 URL 里只需要写http://10.0.0.8/就够了不需要再带/ubuntu这段。如果客户端写了http://10.0.0.8/ubuntu请求就变成.../ubuntu/dists/bionic/Release实际文件在.../ubuntu/dists/bionic/Release层级就又错位了。但要注意用同一个 IP 同时服务两个域名的仓库时客户端 sources.list 里两个仓库指向同一个 IP 是可以的因为请求路径已经包含了域名目录层级。比如deb http://10.0.0.8/ bionic-security main实际上请求的是http://10.0.0.8/dists/bionic-security/...因为 Nginx 默认 server 块会先接住这个请求你可以在第一个 server 块用try_files或直接用默认块覆盖两个目录。更稳妥的做法是我上面那样配两个 server_name让 Nginx 根据 Host 头区分仓库树。如果客户端用 IP 直接访问两条仓库的记录都会落到同一个 server 块这时需要确保这个 server 块的 root 能同时覆盖两棵树或者干脆把两个仓库目录合并处理。实践中最简单的方式是在 Nginx 中再加一个不带 server_name 的默认 server 块root 指到镜像根目录并在 location 里同时开放两条路径。但说实话能控制 DNS 的场景我建议永远走 4.1 的方案改 sources.list 方案是退路不是首选。4.3 为什么永远不要用 trustedyes网上不少教程为了绕过签名验证会在 sources.list 里写deb [trustedyes] http://...我强烈不建议这么干。apt 的仓库签名验证不是摆设。Release 文件由 Ubuntu 官方密钥签名客户端信任这些密钥才能确保下载的 Packages 索引和软件包没有被篡改。自建镜像源同步过来的文件和官方源完全一致天然带合法签名根本不需要额外配置。如果遇到签名验证失败要么是镜像同步不完整要么是本地 keyring 缺了对应公钥要么是仓库 URL 对应的文件根本没有正确同步。这些都应该去解决根因而不是直接关掉校验。关掉校验等于把内网所有机器变成不设防的软件安装通道。镜像机一旦被入侵攻击者往 pool 里塞一个同名版本的恶意包所有客户端 apt install 都会静默安装后果是灾难性的。这是我在做安全评审时最不能接受的一种“方便”。正确的签名处理方式是什么都不做。18.04 客户端的 ubuntu-keyring 默认包含当年发布用的存档密钥同步下来的文件只要完整apt update 就会自动验证通过。5. 18.04 源的老化问题、增量同步优化与故障排查5.1 bionic 进入 ESM 阶段后同步策略要跟着变Ubuntu 18.04 的标准支持周期到 2023 年 5 月结束之后就进入了扩展安全维护ESM阶段。实际同步时你会发现bionic主仓库和bionic-updates仓库的更新频率已经非常低很多组件几个月都不动一次真正每周还在变化的是bionic-security仓库里的安全补丁。这个阶段性的变化直接影响同步频率的设计。如果你还按造新版本的习惯每小时跑一次全量扫描浪费带宽不说日志也会大量堆叠无意义输出。我建议把普通仓库的同步频率降到每周一次security 仓库保持每天一次。既然是同一个 apt-mirror 任务你可以在 mirror.list 里只保留需要的仓库或者写两个配置文件配合两条 cron 任务分别控制。# 每天的凌晨 03:17 同步 security避开整点流量高峰 17 3 * * * /usr/bin/apt-mirror /etc/apt/mirror.security.list /var/log/apt-mirror/security.log 21 # 每周日凌晨 04:30 同步完整仓库 30 4 * * 0 /usr/bin/apt-mirror /etc/apt/mirror.list /var/log/apt-mirror/full.log 21镜像体的体积变化也可以佐证这个判断。主仓库长期不更新后apt-mirror 的输出会从“每轮几百个文件”变成“每轮几个或者零个文件”说明增量已经非常小。此时唯一需要保证的是 security 仓库的时效性因为内网机器如果长期拿不到安全补丁相当于把漏洞暴露窗口拉长了。5.2 增量同步的血泪经验不要无脑跑 cron日志要人看理论上 apt-mirror 每次运行会自动跳过未变化文件不需要做任何特殊优化。真正的问题从来不是下载效率而是同步过程的完整性和异常发现。我遇到过最典型的情况是某次同步因为磁盘写满中断了cron 第二天凌晨又自动跑了一次。apt-mirror 启动后发现自己“好像有文件”就跳过了一大批下载但实际上有些索引文件损坏了。结果就是内网所有客户端 apt update 都报 Hash Sum mismatch而我直到用户反馈才被动发现。从那一刻开始我养成了“每天看同步日志结尾”的习惯不需要逐行读只需要确认最后一屏没有 ERROR 和异常退出。另外apt-mirror 在执行过程中Nginx 仍然在服务上一次同步的文件。多数情况下没有问题因为文件下载是写到临时位置再改名不是边写边覆盖。但如果刚好有人在某个文件写完一半的时刻发起 apt update理论上有概率拿到不完整文件。规避办法是把同步安排在业务低峰期或者干脆做一个双目录切换镜像同步到备用目录同步完成后再把 Nginx 的 root 软链接切过去。我目前用的是低峰期方案双目录切换也试过效果稳定但是会增加一套脚本逻辑建议按团队维护能力取舍。5.3 高频故障排查链路从 Hash Sum mismatch 到目录 404最后分享一套完整的排查链路我遇到的所有客户端侧怪问题基本都能用这个流程收敛到根因。第一步客户端报Hash Sum mismatch的时候先不要急着重试客户端。直接登录镜像机看对应仓库的dists/bionic/.../binary-amd64/Packages.gz和 Release 文件是否完整用apt-mirror再跑一次观察日志是否以正常状态结束。如果日志里出现大量 write error 或 disk full说明是磁盘空间问题先清理临时文件再说。第二步客户端报 404 Not Found 的时候打开浏览器访问镜像机的对应路径确认是 Nginx root 层级配错还是对应组件没同步。层级配错的表现是浏览器访问/dists/能看到目录但访问/dists/bionic/Release404。没同步的表现是整个/dists/bionic目录都不存在。第三步客户端报“无法解析域名”或者连接超时检查内网 DNS 配置生效没有以及镜像机防火墙是否放行了 80 端口。这两个问题看着低级但在分层的运维体系里恰恰是最容易被前置环节挡住、又最难定位的。第四步如果新装的一批机器在同步后 apt update 反复提示“仓库没有 Release 文件”大概率是镜像里缺少bionic-updates或者bionic-security中的某一个仓库因为客户端默认会同时从三个仓库拉索引。检查 mirror.list 里三行 deb 是否齐全而不是去客户端上折腾。故障现象常见根因排查动作Hash Sum mismatch同步中断导致索引包不完整重跑 apt-mirror必要时删对应 dists 目录后重同步文件 404Nginx root 层级错误或组件未同步浏览器访问对应路径确认根目录和发行版目录域名无法解析/超时内网 DNS 未配或防火墙拦截检查 DNS 记录、端口连通性提示仓库无 Release 文件三个仓库中的某一个缺失核对配置文件里的三行 deb 条目把这条链路打印出来贴在工位旁边基本可以覆盖自建源 90% 的运维场景。最后补一句实在话。自建本地 Mirror 源这件事真正决定成败的不是第一天搭得多快而是后续三个月里你愿不愿意每天花三分钟看同步日志。把增量维护、安全校验和故障响应当成惯例动作这套基础设施就能稳稳当当地跑上很多年。