最近项目里来了一批银河麒麟v10的机器,网络环境完全隔离,任务是在上面部署一堆业务软件,所有deb包只能靠U盘往里搬。听起来像是小众需求,实际上做过的朋友都知道,内网部署、工控机房、敏感业务环境里,这种事情每天都在发生。银河麒麟和统信UOS这两个系统都属于Debian系,软件包格式和安装机制一脉相承,真上手之后你会发现:难点从来不是“怎么装一个deb”,而是怎么把依赖链、公钥、架构、版本这些坑一个个填平。这篇文章我按实战顺序整理了一套完整流程,从在联网机器上下载deb包,到离线机器上安装排错,再到批量交付自建本地软件源,每一步都有命令、有原理、有现场记录,适合正在做国产系统软件适配、内网软件分发,或者手里正好有一台离线UOS/麒麟机器想装点东西的朋友直接照着操作。
1. 离线安装不是冷门需求:内网环境才是常态
1.1 为什么会有大量断网机器
涉密环境、生产控制网络、金融业务内网,这些场景普遍切断了外网连接。数据安全要求摆在那里,运维的原则就是能不开通就不开通。还有一类是工控设备和专用终端,系统装完可能三五年不动,生产环境里出了问题,远程通道都没有,所有修复只能靠本地介质。在这种环境里,很多开发者习以为常的东西会全部失效——apt update连不上源、pip下载超时、npm仓库打不开,唯一可行的办法是离线把软件包带进去。
我做过的国产系统部署项目里,大概一半以上的目标机器都是这类纯内网状态。刚开始也天真过,想着能不能临时开放网络装完再断,实际执行起来发现根本行不通:安全审批流程太长,很多环境物理隔离就是物理隔离,没有任何讨价还价的余地。所以离线安装不是应急方案,而是一项需要好好设计的常规交付能力。
1.2 银河麒麟和UOS的Debian血统
银河麒麟V10和统信UOS在软件包管理上完全沿用了Debian的dpkg/apt体系。麒麟V10的版本基线大多对应Debian 10(Buster),统信UOS不同版本分别基于Deepin 20、Deepin 23的底座,往下追还是Debian那一套。这意味着在联网机器上能用的包管理逻辑,到了离线机器上依然成立,只要理解Debian的依赖规则和安装机制,两台机器之间的迁移就只是介质传递的问题。
但这里有个关键误区:镜像系统的血缘接近,不等于可以直接拿Ubuntu的软件包硬上。Debian、Ubuntu、Deepin、麒麟之间的核心库版本差异很大,跨源安装经常会把系统搞坏。我见过太多人图省事,直接下载Ubuntu仓库里的包回来装,结果glibc、libstdc++这类基础库被联动升级,装完之后系统直接起不来。离线环境下系统一旦损坏,修复成本比联网环境高出一个数量级。
1.3 离线安装的完整闭环
离线安装的整体链路并不复杂,但每一条链路分支都有隐藏的坑。大致分四步:第一,在联网机器上把目标软件及其全部依赖下载成deb文件;第二,通过U盘、移动硬盘或内部文件服务器传递到目标机器;第三,用dpkg或本地apt源按依赖顺序安装;第四,验证软件可运行,并把过程中踩过的依赖坑记录下来形成清单。前面两步决定“包齐不齐”,后面两步决定“装得上装不上”。
这篇文章的章节就是按这个链路展开的。下载阶段容易漏依赖、抓错版本,拷贝阶段要考虑文件系统和文件大小限制,安装阶段要面对公钥校验、依赖缺失、架构不符三个经典问题,最后如果机器数量多,还得上升到自建本地源的高度。
2. 动手前的三连问:架构、系统代号和包名定位
2.1 架构标识一对就少一半麻烦
在动手下载任何deb包之前,先搞清楚目标机器的CPU架构。命令很简单:
uname -m dpkg --print-architecture前者显示内核看到的硬件架构,后者是dpkg在软件包管理层面使用的架构标识。在x86机器上,通常输出x86_64和amd64;在飞腾、鲲鹏这类ARM平台上,输出aarch64和arm64;龙芯平台上能看到mips64el或loongarch64。很多人第一次接触时会犯迷糊——amd64是不是只能给AMD CPU用?不是,Intel和AMD的x86_64 CPU在Debian系里统一用amd64标识,ARM平台的arm64就是aarch64。
uname -m输出 | dpkg --print-architecture输出 | 常见平台 |
|---|---|---|
| x86_64 | amd64 | Intel/AMD 台式机、服务器 |
| aarch64 | arm64 | 飞腾、鲲鹏等ARM架构整机 |
| mips64el | mips64el | 龙芯3A/3B等旧平台 |
| loongarch64 | loongarch64 | 龙芯新平台 |
下载deb包时务必严格对应架构列,装错了会直接报wrong architecture,白忙一场。尤其在国产整机项目里,一栋楼里可能同时存在x86和ARM两种机器,离线包目录要分开建,混在一起后面排查会非常痛苦。
2.2 系统代号决定该参考哪条依赖基线
架构确认之后,还要确认系统版本和Debian基线的对应关系:
cat /etc/os-release cat /etc/debian_version我见过不少银河麒麟V10的机器,/etc/os-release里写着Kylin V10,/etc/debian_version里写着10.11,这说明它的软件包基线是Debian 10 Buster。统信UOS的家庭版早期对应Debian 10底座,Deepin 20.9附近也还是Debian 10,切到Deepin 23之后才逐渐往Debian 11靠拢。
搞清这个对应关系价值在于:去联网机器上下包时,你知道应该参考哪个Debian版本的依赖关系。比如目标机的libc6是2.28,这是Debian 10的标准版本,从Debian 11源里下的包很可能要求libc62.31以上,装上去依赖就对不上。后面排错章节我会详细讲这个问题的表现,这里先记住一句话:离线包的“版本基线”必须与目标系统一致,跨基线就是给自己挖坑。
2.3 包名定位是最容易被低估的环节
包名定位是第一道拦路虎。用户要装的是MySQL,网上搜出来一堆“银河麒麟MySQL离线安装包”,下载回来一看乱七八糟,还不如自己动手老老实实查。我的习惯是在联网机器上先查准确名称:
apt-cache search mysql-server apt-cache show mysql-server | grep -E '^(Package|Version|Depends|Architecture):'apt-cache show的输出里有完整的依赖列表,这一行信息在后续下载依赖时会反复用到。如果是单位内部开发的软件,直接找发包方要完整的deb包和依赖清单,比自己猜包名靠谱得多。
还要提醒一点:很多软件看起来是deb,实际根本不是。Node.js官方提供的是tar.xz二进制包,Python的openpyxl、playwright是走pip的包,Visual Studio那套是独立安装器。它们的离线方法完全是另一条路线,别一并混入deb体系。我见过有人拿着apt download nodejs下载出来的老掉牙版本,和官网新版对不上号,徒增工作量。
3. 联网机器上把deb打包带走:三种可靠姿势
3.1 apt download:单个包的精准搬运
在联网机器上下载deb,最直接的方式是apt download:
mkdir ~/offline-pkgs cd ~/offline-pkgs apt download mysql-serverapt会解析软件源信息,把对应deb文件下载到当前目录,而不是放到/var/cache/apt/archives。这个工具的特点是“不做依赖计算”,只负责把一个指定包从源里取出来,适合已经确定缺哪个包、需要精确补漏的场景。比如后期安装时报缺libaio1,就在联网机器上执行apt download libaio1,拷过去补上就行。
单独用的时候少,但它是所有离线下载动作里最基础的一块积木。它最大的好处是产物位置可控、文件名可预期,拷贝时不会漏。
3.2 apt-get install --download-only:一次带全依赖
这是离线部署的标配做法。在联网机器上执行:
sudo apt-get install --download-only -y mysql-server ls -lh /var/cache/apt/archives/执行时apt会做一次完整的依赖计算,把所有需要新安装或升级的包下载到/var/cache/apt/archives目录,但不实际安装。结果是主包和完整依赖链一次备齐。然后把目录里的deb全部拷走:
mkdir -p ~/offline-pkgs cp /var/cache/apt/archives/*.deb ~/offline-pkgs/这里藏着离线部署里最容易翻车的一个变量:download-only计算出来的依赖清单,和当前这台下载机器的已装软件情况强相关。如果联网机器上已经装了某个依赖库,apt就不会把它加入下载清单,而目标机器上可能根本没装这个库。等你到了离线环境安装时才发现少包,那叫一个难受。
所以最稳妥的做法是:找一台和目标机器系统版本、预装软件情况尽量一致的机器来下载。如果条件不允许,就把目标机器上dpkg -l的包清单拿过来,跟下载结果逐一比对。我把这个细节放在前面,是因为很多人的离线包装到一半缺依赖,回头查根因,基本都出在这个环节。
另外,download-only选中的依赖版本还受到软件源配置影响。如果联网机器配的是Ubuntu源,下载出来的候选包可能与Debian基线不一致。尽量使用与目标系统对应的源,这是离线包能不能用的分水岭。
3.3 deb-src 源码包:仓库里没有现成包时的兜底
有些软件在既定仓库里没有预编译包,或者官方只提供源码。这时需要下载源码包并在联网机器上完成构建:
deb-src http://deb.debian.org/debian buster main apt update apt source 包名源码包下载后会得到三个文件:.dsc描述文件、.tar.xz上游源码、.debian.tar.xz打包规则。然后展开并构建:
dpkg-source -x 包名.dsc cd 包名-*/ dpkg-buildpackage -us -uc -b构建会生成新的deb文件,拿到离线机器上安装即可。执行dpkg-buildpackage之前需要把build依赖装齐,而这些依赖本身往往也是一大批deb包,所以在联网阶段就要一并download-only出来备用。
这种方式一般只在找不到二进制包时才用。我遇到过内网要装一个小众音视频处理库,官方只发源码包,最后就是靠这条路在联网机器上编译出deb,一次性成功。它的代价是需要干净、完整的构建环境,如果目标机器上还要继续编译,那build依赖也要一起带过去,工程量瞬间变大。
三种下载方式的定位可以简单对比:
| 方式 | 核心命令 | 依赖处理 | 适合场景 |
|---|---|---|---|
| 单包下载 | apt download 包名 | 不带依赖 | 补缺、精确获取某个包 |
| 全家桶下载 | apt-get install --download-only | 自动计算并下载 | 完整应用离线搬运 |
| 源码构建 | apt source+dpkg-buildpackage | 手动准备build依赖 | 仓库无现成二进制包 |
4. 目标机器安装的核心战场:依赖关系和安装顺序
4.1 dpkg -i *.deb 一把梭的真相
把deb包拷贝到离线机器后,战斗才真正开始。很多人第一反应是执行:
sudo dpkg -i *.debshell会把*.deb按字典序展开成参数序列,dpkg再按这个顺序逐个处理。问题在于字典序和依赖顺序完全不是一回事。比如a包依赖z包,但a.deb的字典序排在z.deb前面,dpkg先装a,依赖不满足报错一片;继续装z,又提示前面的a还没配置好。运气好的话,最后用apt -f install能收尾;运气差的话,dpkg的数据库状态就乱了。
要理解这个现象,得先明白dpkg的定位:它是个很“笨”的工具,只负责解包、写进数据库、执行维护脚本,不检查依赖是否满足。真正管理依赖的是apt,但apt在离线环境没有可用软件源,于是出现了一个真空地带:dpkg有能力装,但不知道顺序;apt知道顺序,但没有源可用。离线安装策略的核心,就是想办法把这个真空地带补上。
4.2 两条靠谱的安装路径
第一条路径:按依赖顺序手动安装。下载阶段如果用的是download-only,可以在联网机器上把依赖关系导出一份带过去:
apt-cache depends mysql-server然后从依赖底层往上逐层执行dpkg -i。思路很简单,包少的时候很管用,包多了就考验耐心。实际项目里我通常先把这批包里的顶层包找出来,再一层层往下清依赖,相当于手动模拟一遍apt的解析过程。
第二条路径更省心:把离线目录伪装成一个最小本地源,让apt自己解析依赖。操作如下:
cd ~/offline-pkgs apt-ftparchive packages . > Packages sudo apt update sudo apt install -y mysql-serverapt-ftparchive会扫描当前目录的deb文件,生成Packages索引。apt update读取这个索引后,就能在本地目录内闭环解析依赖,把dpkg的笨重和apt的智能结合起来了。这是我在单机离线安装中最常用的方式,比手动按顺序敲命令省太多事。唯一需要注意的是:当前目录如果缺某个依赖包,apt会尝试从外部在线源补,所以安装前最好把/etc/apt/sources.list里的在线源临时注释掉,避免它去外网碰运气。如果apt-ftparchive不在系统里,先装apt-utils:
sudo apt install apt-utils4.3 一个完整场景:离线装MySQL 5.7.44
拿网上经常搜到的“银河麒麟V10 SP2服务器版安装MySQL 5.7.44”举例。这类任务在离线环境里非常典型。在联网机器上,如果默认源里已经没有5.7版本,需要先配置MySQL官方的APT源,然后再执行:
sudo apt-get install --download-only -y mysql-server-5.7下载结果通常包括mysql-server、mysql-client、mysql-common、libmysqlclient21,以及libaio1、perl、libmecab2这类“看起来和MySQL没关系”的底层依赖。很多人手动下包时最容易漏掉这些小依赖,而download-only在计算阶段已经自动补齐了,这就是我推荐它的核心原因。
包拷到离线机器后,我用本地源方式安装。装完验证:
sudo systemctl status mysql mysql -uroot -p能正常进入MySQL命令行,这个离线部署就算闭环了。整个过程中,凡是用dpkg -i硬装的,十有八九会卡在某个看似无关的小依赖上;凡是走本地源方式的,基本一次通过。
5. 离线安装报错现场:密钥、动态库和版本冲突排查全过程
5.1 NO_PUBKEY:公钥缺失的完整处理链路
离线环境里最典型的报错是apt update或安装过程中出现:
The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 1234567890ABCDEF根因是deb仓库在生成Release文件时用仓库私钥做过签名,系统里没有对应的公钥,apt默认不信任这个源,拒绝使用。在线环境下一条命令就能拉取公钥,离线环境下只能走“导出再导入”的路子。
在联网机器上导出公钥:
gpg --export 1234567890ABCDEF > pubkey.asc apt-key export 1234567890ABCDEF > pubkey.asc把pubkey.asc拷到离线机器后,执行:
sudo apt-key add pubkey.asc麒麟V10和UOS系统大多还兼容apt-key方式。如果系统较新、使用signed-by方式,就把导出的公钥文件放到/etc/apt/trusted.gpg.d/或对应源的keyring位置,并在sources.list里加上signed-by=/usr/share/keyrings/xxx.gpg参数。
这里有一个经验:公钥缺失往往在apt update阶段就暴露了,别等到安装时才发现。离线机器上第一次配置源之后,先跑一遍apt update,把密钥问题提前解决掉,比装到一半再回头处理痛快得多。
5.2 动态库缺失:ldd告诉你还差谁
另一个高频报错是软件装完后启动时报:
error while loading shared libraries: libxxx.so.1: cannot open shared object file这说明主包已经装上了,但它链接的动态库不在系统里。排查链路分三步:
第一步,用ldd查看可执行文件的依赖:
ldd /usr/bin/你的程序第二步,记下显示not found的库名,在联网机器上查这个库属于哪个包:
apt-file search libxxx.so.1输出结果类似libxxx1: /usr/lib/x86_64-linux-gnu/libxxx.so.1,意思是这个动态库由libxxx1这个包提供。第三步,把这个包下载下来带回去装上。
价格不贵,但动态库缺失最让人头疼的还不是安装本身,而是它往往意味着下载阶段漏了依赖。漏掉的原因通常是下载机器上恰好有旧版本库,download-only认为已经满足就不列进清单,目标机器却没这个库。所以对于直接通过dpkg -i安装的包,事前做一遍全量ldd检查很有必要。
5.3 版本冲突和架构报错
版本冲突的典型报错是:
The following packages have unmet dependencies: mysql-server: Depends: libc6 (>= 2.31) but 2.28-10 is installed这种基本是跨源下载造成的:目标系统里libc6版本低,你下载的包是从更高版本Debian源拿的,运行库要求高于目标系统基线。解决办法只有一个——换成与目标系统Debian基线一致的源重新下载。硬刚的话,升级glibc极有可能把整台系统搞挂,得不偿失。
架构报错更直观:
dpkg: error processing archive xxx_amd64.deb (--install): package architecture (amd64) does not match system (arm64)下载阶段没看架构标识,装上去必然报错。在飞腾、鲲鹏、龙芯这类平台上尤其常见,下包前用dpkg --print-architecture确认一遍,比事后折腾高效得多。批量部署时,我还会给离线包目录直接按架构命名,比如pkgs-amd64、pkgs-arm64,从源头上杜绝拿错包。
6. 单机交付升级到批量交付:自建本地软件源才是终局方案
6.1 为什么单机拷贝不可持续
单台机器用U盘来回拷贝还能忍,到了几十台、上百台机器,或者软件需要持续更新版本的阶段,“离线目录加U盘”的方案就撑不住了。每次版本迭代都要重新准备一轮离线包,挨台机器跑,光是拷贝和核对哈希就能耗掉大量时间。批量项目里,最终方案一定是自建本地软件源:内网机器通过apt install正常安装软件,唯一的区别是源地址指向自己的服务器。
6.2 用apt-ftparchive搭建简易本地源
先在一台内网服务器上建一个目录,把搜集到的deb包全部放进去:
cd /srv/apt-repo apt-ftparchive packages . > Packages apt-ftparchive release . > ReleasePackages文件记录了每个deb的名字、版本、架构、依赖、大小和校验信息,apt全靠它解析依赖;Release文件是这个仓库的元信息。之后把/srv/apt-repo通过nginx或httpd发布出去。
客户端配置:
sudo tee /etc/apt/sources.list.d/local.list <<'EOF' deb [trusted=yes] http://内网IP/apt-repo ./ EOF sudo apt update sudo apt install -y 你的软件两个细节必须说清楚。第一,源路径末尾的./不能丢,它表示Packages文件就在当前目录。第二,[trusted=yes]的作用是跳过GPG签名验证——自建仓库默认没有签名公钥,不加这个参数apt update会直接拒绝。如果安全要求必须签名,就用自己的GPG私钥给Release文件签名,再把公钥发给所有客户端导入。
6.3 用reprepro管理多版本和增量发布
简易本地源在软件少、更新频率低的场景够用。软件多了,需要分main、updates组件,还要管版本回滚,apt-ftparchive就吃力了。这时候推荐reprepro:
reprepro -b /srv/apt-repo --component main --architecture amd64 includedeb buster ./你的软件_1.0_amd64.debincludedeb会把deb导入仓库,自动更新Packages和Release索引,同时维护多个发行版和多个组件,发布、删除、导出都很规范。我在一个跨系统项目里同时维护了Debian 10底座的麒麟源和Debian 11底座的UOS源,客户机分别配不同路径,增量发布时一条命令就能完成。
6.4 内网批量环境的配置纪律
批量部署时,目标机器最好统一镜像、统一架构。一个arm64源和一个amd64源要分开维护,混用会立刻引发“wrong architecture”满天飞。另外,如果内网机器之前配置过外部在线源,记得注释掉或在/etc/apt/sources.list.d/里删除对应文件,只保留本地源,这样才能保证离线环境的纯粹性。批量机器的软件源配置建议通过脚本或配置管理工具统一下发,避免人工一台台改漏。
7. 实操沉淀下来的几条经验
7.1 下载机器的系统基线,是最容易翻车的变量
离线包不是“有就行”,而是“和你的目标环境同源”才行。我吃过一次亏:在一台Ubuntu 22.04上给麒麟V10准备离线包,download-only把一堆Ubuntu的新版本库带了出来,拷到麒麟机器后libc6冲突,整批白干。后来老老实实找了一台与目标系统Debian基线一致的机器做下载,一次成功。现在我的习惯是:接离线任务的第一件事,就是问清楚目标机的系统版本和预装环境,然后照着这个状态去准备下载环境。
7.2 介质和哈希:拷贝环节最容易被忽略
FAT32格式的U盘不支持单个超过4GB的文件,MySQL全家桶偶尔会有大包,内部大型软件更明显。要么换exFAT或NTFS,要么把离线目录拆成多个子目录分批拷。拷完之后第一件事是校验哈希:
sha256sum *.deb > sha256.txt到了目标机器上再比对一遍,能避免U盘拷贝过程中文件损坏带来的“灵异安装失败”。这个动作看起来多余,但确实救过我一次——一个十几GB的离线包目录,拷到一半U盘出问题,个别文件静默损坏,不校验的话装到一半才发现,排查起来非常费劲。另外提醒一下,拷贝完成后别急着拔盘,先看一遍df -h确认剩余空间,很多离线包体积大,目标机器磁盘太满也会导致安装失败。
7.3 认清软件打包形态,别被deb思维绑架
最后一条经验来自项目里反复出现的错位:Node.js装的是tar.xz,Python的包走pip,Visual Studio是独立安装器,Docker离线部署要导镜像。每个生态都有自己的离线方法论,不能拿着deb的锤子砸所有钉子。
处理这种事情,我的顺序是:先确认软件官方有没有离线安装包,没有的话再看它是基于什么运行时——如果是Python就准备pip download的wheel包目录,如果是Node就准备好npm pack或直接搬运node_modules,如果是C++生态就看静态链接版本,实在不行再考虑源码编译或容器镜像。定好路线再动手,比到目标机器上才发现装法不对要舒服得多。
7.4 最后补一个实用小技巧
批量部署前,把目标机器上的dpkg -l导出成清单,和准备带入的离线包目录做个对照。重点看两件事:目标机器哪些包已经存在、哪些包会触发升级。某些基础库一旦升级到更高版本,可能连带影响其它业务软件,这种事前发现比事后补救划算得多。我后来养成了习惯,每次做完一批机器,都会把这个对照清单存档,下次同类项目直接复用,省掉一大半试错成本。