
那台服务器放在内网机房里没有外网权限安装一个软件包却让我从下午折腾到天黑。我在外网机器上辛辛苦苦下好的 rpm 文件拷到离线节点上执行安装结果提示缺依赖装上依赖又告诉我依赖的依赖也缺再装上又说版本不对。这种体验就是典型的“依赖地狱”。后来我做运维的时间长了才慢慢摸出 Linux 离线环境下安装软件包的完整思路——它不只是一个“把安装包拷贝过去”的动作而是一整套依赖分析、方案选型和现场排错的方法。这篇文章就把我这套方法论完整写出来不管是刚入门 Linux 的开发者还是被生产环境折磨过的运维都应该能从中拿到可以直接用的操作方案。1. 动手之前先把这三件事摸清楚很多人在离线环境装包失败不是操作顺序错而是缺少前置侦察。我见过太多人拿到一个 .deb 就往机器上丢最后发现系统是 CentOS压根不认.deb也有人下载 x86_64 的包结果机器是 aarch64 架构。这些都是可以提前避免的。1.1 系统信息收集发行版、架构、内核版本上机第一步先看清楚“我在跟谁打交道”。至少执行下面这几条命令uname -a cat /etc/os-release dpkg --print-architecture # Debian/Ubuntu 系 rpm --showrc | grep ^arch # RedHat/CentOS 系为什么要看三样东西发行版决定了你用 deb 还是 rpm也决定了软件源的类型架构决定了你下载的包是 x86_64 还是 arm64选错的话包管理系统会直接拒绝安装内核版本则影响内核模块和部分需要编译驱动的软件。比如有些监控内核态的工具在 5.x 内核上能用在 4.x 内核上可能就需要重新编译模块。补充一个经验内网环境里最好统一记录“基线信息”包括系统版本、系统小版本号、内核、架构。因为同一个大版本的不同小版本包依赖也可能有差异。比如 Ubuntu 20.04.4 和 20.04.6源里的同一个包可能拉取到不同版本依赖关系也会变化。1.2 搞清楚依赖关系离线安装的“第一只拦路虎”Linux 包管理器最大的特点是会自动处理依赖但这依赖于在线源。一旦离线这个能力就废了一大半。所以在离线环境安装前我建议先在有网的机器上把依赖关系看明白。Debian/Ubuntu 系可以这样查apt-cache depends nginx apt-cache show nginxRedHat/CentOS 系可以用yum deplist nginx dnf repoquery --requires nginx如果你手上已经有一个下载好的 rpm也可以直接查它rpm -qpR nginx.rpm这里有个容易被忽略的点依赖不一定是一个包名还可能是一个“虚拟包”或者共享库比如libssl.so.3()(64bit)。这意味着你要去找提供这个 .so 文件的软件包而不是找一个同名包。查提供方用apt-file search libssl.so.3 # Debian/Ubuntu 需要安装 apt-file dnf provides libssl.so.3 # RedHat/CentOS yum provides libssl.so.3我习惯把依赖关系想象成一张有向图软件包是节点依赖是边。离线安装的目标就是把这整张图上的节点一次性搬过去而不是只搬最顶层那一个。这也是为什么“下载一个包拷过去”在绝大多数情况下行不通。1.3 选安装路线二进制包、源码编译、容器搬运怎么选根据场景不同离线安装有三条主要路线很多人一上来就纠结“哪种方法最好”其实没有最好的只有最合适的。方案适用场景优点缺点离线源/本地源批量安装依赖关系复杂包版本相对固定保留包管理器的解析能力安装和卸载规范需要准备源元数据前期工作量略大源码编译官方没有对应发行版的二进制包或需要自定义编译参数灵活可控可指定安装路径编译周期长依赖库常需要一并编译容器镜像/静态打包软件自带一堆依赖甚至需要特定运行时环境一搬全搬几乎不破坏宿主机环境依赖 Docker/容器运行时架构限制明显如果只是临时装一个工具源码编译最快如果是给几十台离线机器统一装某个软件栈本地源最省心如果是部署整套应用容器镜像是付出代价最小、后期恢复最容易的方案。2. 方案一搭建本地离线仓库把“装包”变成“更新源”这个方案的核心思想是在有网的机器上提前把所有依赖包下载好然后把它们组织成 Linux 包管理器能识别的本地源让 apt / yum 以为自己还能“在线”工作。2.1 只装一个包时用下载工具把依赖一次性拉齐如果你只是要在离线机器上装一个包比如 nginx那没必要建完整源直接下载目标包和全部依赖就好。Debian/Ubuntu 系的思路找一台与目标机器相同发行版和架构的有网机器直接执行安装让系统把依赖自动下载到本地缓存sudo apt-get install --download-only nginx ls /var/cache/apt/archives//var/cache/apt/archives目录下会保留所有下载好的 .deb 文件包括 nginx 本体和各级依赖。把这个目录里的所有文件拷到 U 盘再在离线机器上执行sudo apt-get install /path/to/debs/*.deb这里不建议用dpkg -i *.deb因为 dpkg 本身不做依赖解析如果包之间的顺序不对会中间失败。用apt-get install *.deb会让 apt 从当前目录里的这些 .deb 文件之间寻找依赖关系成功率明显高很多。RedHat/CentOS 系有一个专门的小工具yumdownloaderyumdownloader --resolve --destdir/tmp/nginx-rpms nginx--resolve参数会自动把依赖包也下载下来。如果系统没有这个命令先装yum-utils。到离线机器上后直接cd /tmp/nginx-rpms yum install ./*.rpm2.2 批量安装时用本地源目录接管包管理器的“在线”行为如果离线机器不止一台或者要装几十个软件包每次都手动敲安装命令太累。更好的办法是在离线机器上建一个“本地软件源”让包管理器像访问在线源一样访问本地目录。Debian/Ubuntu 的做法是准备一个包含 Packages 索引文件的目录。把之前收集到的所有 .deb 文件放到同一个目录下mkdir -p /opt/offline-repo cp *.deb /opt/offline-repo/ cd /opt/offline-repo apt-ftparchive packages . Packages apt-ftparchive release . Releaseapt-ftparchive这个命令来自apt-utils包如果离线机器上没装可以在有网机器生成好 Packages 文件后一起拷过去。然后在/etc/apt/sources.list.d/offline.list里写入deb [trustedyes] file:/opt/offline-repo ./接着更新并安装sudo apt update sudo apt install nginxRedHat/CentOS 系更简单用createrepo生成元数据mkdir -p /opt/offline-repo cp *.rpm /opt/offline-repo/ createrepo /opt/offline-repo然后写一个 repo 文件比如/etc/yum.repos.d/offline.repo[offline] nameOffline Repository baseurlfile:///opt/offline-repo enabled1 gpgcheck0执行yum clean all yum makecache yum install nginx2.3 进阶局域网内搭一个 HTTP 源省去U盘来回拷贝如果离线环境内部有网络只是不能连外网那就没必要每台机器都复制一遍仓库目录。直接找一台机器把仓库目录用 HTTP 服务共享出来其他机器把源地址指向它就行。最轻量的办法是 Python 自带模块cd /opt/offline-repo python3 -m http.server 8080其他机器上只需要把源地址改成deb [trustedyes] http://192.168.1.100:8080 ./rpm 侧就把baseurl写成baseurlhttp://192.168.1.100:8080想长期使用可以用 systemd 管理这个 Python 服务或者用 nginx 把目录映射出去。实际生产里我更推荐 nginx毕竟访问量大、并发高的时候Python 单线程 HTTP 服务容易把磁盘 IO 打满。2.4 本地源的两个隐藏坑GPG签名与源配置语法有人照着教程做好源运行apt update却报错或者提示包无法验证多半是两个原因。第一个是 GPG 签名。在线源之所以安全是因为仓库里的元数据都经过 GPG 签名。本地源的解决办法有两个一个是在源配置里写[trustedyes]显式告诉 apt 信任这个源另一个是自己生成一对 GPG 密钥给本地仓库签名后在客户端导入公钥。内网可信环境一般用前者就够了生产环境建议走签名流程防止仓库文件被篡改。第二个是源配置语法。deb file:/opt/offline-repo ./这一行最后面那个./不能丢它表示 Packages 文件就在该目录下。如果漏了apt 会去错误路径找索引执行apt update后什么都刷不出来。写完一定要再跑一次sudo apt update看到正常读取本地目录再往下装包。3. 方案二源码编译安装绕开包依赖的兜底手段本地源方案很好用但有时候你手里只有一个源码 tar 包官方根本没提供你所在发行版的二进制包或者你需要改编译参数那就得走源码编译。3.1 什么时候必须走源码编译什么时候别死磕我判断是否编译安装的标准很简单如果二进制包能解决就不要编译。编译一次从 configure 到 make install通常要几分钟到几十分钟而且后续卸载麻烦系统里会散落不少文件。必须编译的场景主要有三类官方只发布源码比如一些运维脚本、性能监控模块目标系统版本太老仓库里的包版本太旧需要新功能需要自定义特性比如编译 nginx 时加--with-http_ssl_module或某些第三方模块。如果系统里连 gcc 都没有编译环境本身的搭建就变成一个更麻烦的前置问题。所以在离线环境中我强烈建议先把基础编译链打包好作为“公共底座”。Debian/Ubuntu 上是build-essentialRedHat/CentOS 上是Development Tools组包# Debian/Ubuntu sudo apt-get install build-essential # RedHat/CentOS sudo yum groupinstall Development Tools3.2 编译前的“前置依赖”准备很多人以为源码编译就不需要依赖库了这是误解。源码编译只是把“二进制依赖”变成了“开发头文件依赖”。比如编译 nginx需要 PCRE、zlib、OpenSSL 的开发库编译很多 GUI 程序需要 GTK 相关头文件。在离线环境里这些开发库同样要从有网机器上下载。Debian/Ubuntu 上开发包通常带-dev后缀RedHat/CentOS 上通常是-devel。下载方法还是用前面那一套# Debian/Ubuntu 下载所有编译 nginx 需要的 dev 包 apt-get install --download-only libpcre3-dev zlib1g-dev libssl-dev把这些 dev 包拷过去用dpkg -i或者本地离线源装好再开始编译源码。3.3 一次标准编译安装的完整过程以nginx为例假设我已经把 nginx 源码包和依赖库源码包都准备好。标准流程是解压、configure、make、make install。tar xf nginx-1.25.3.tar.gz cd nginx-1.25.3/ ./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-pcre../pcre-8.45 \ --with-zlib../zlib-1.3 \ --with-openssl../openssl-3.0.11 make -j$(nproc) sudo make install说几个细节。-j$(nproc)是并行编译能充分利用 CPU。但在内存比较小的机器上并行编译可能直接把内存占满导致 OOM所以我一般先在测试机上看一下nproc和内存摸不准就老老实实make -j2。--prefix指定安装目录我习惯把每个软件装到独立目录比如/usr/local/nginx、/usr/local/python3.11。这样软件版本升级、卸载都直观。不要一上来就装到/usr不然以后你根本不知道哪些文件属于哪个项目。configure 阶段如果报缺少某个库一般会明确提示“checking for xxx... not found”。这时候别慌按提示把对应 -dev 包装好或者像上面示例里用--with-pcre../pcre-8.45直接指明源码目录让 nginx 在编译时把依赖库一起静态编进去。3.4 编译安装后的运行期三件事PATH、pkg-config、动态库源码装完不等于能用。编译过的软件运行时要找共享库而 Linux 默认的库搜索路径里通常没有/usr/local/xxx/lib。典型报错是error while loading shared libraries: libxxx.so.1: cannot open shared object file解决办法是把库路径加入动态链接配置echo /usr/local/nginx/lib /etc/ld.so.conf.d/offline-local.conf sudo ldconfigldconfig执行后可以这样验证ldd /usr/local/nginx/sbin/nginx再看 PATH。如果软件的可执行文件目录不在 PATH 里命令行直接敲会提示 command not found。我当时临时用的办法是export PATH/usr/local/nginx/sbin:$PATH长期使用建议写进/etc/profile.d/offline.sh这样所有用户登录后都能直接用。还有一个容易被忽略的是 pkg-config 路径CMake 和 autotools 在编译第三方软件时靠.pc文件找开发库export PKG_CONFIG_PATH/usr/local/lib/pkgconfig:/usr/local/nginx/lib/pkgconfig这两个环境变量编译安装跑不起来的场景里出现频率极高。4. 方案三容器镜像与静态打包把整个运行环境一起带走有时候最麻烦的不是软件包本身而是它那一堆运行时依赖、配置文件、环境变量。这种场景下容器镜像是性价比很高的离线搬运方式。4.1 Docker镜像离线迁移save/load 组合拳Docker 镜像的好处是它把运行环境完整封装了库、配置、可执行文件都在里面。在有网的机器上先准备好镜像docker pull nginx:1.24 docker save -o nginx-1.24.tar nginx:1.24把nginx-1.24.tar拷到离线机器后docker load -i nginx-1.24.tar docker run -d --name nginx -p 80:80 nginx:1.24这里有个必须注意的坑架构。你在 x86_64 机器上用docker pull拉下来并保存的镜像是 amd64 的如果离线机器是 arm64 架构直接 load 是能加载成功但运行时会报exec format error。正确做法是在有网机器上提前指定平台docker pull --platform linux/amd64 nginx:1.24然后确认离线机器的 Docker 环境支持这个平台或者干脆在目标架构的机器上拉镜像再 save。另外docker save和docker export很多人会搞混。save 保存的是一个镜像的分层和元数据load 之后还能保留镜像的 tag、CMD、ENV 等export 导出的是容器文件系统的扁平 tarimport 回来就成一个“裸镜像”历史信息全丢了。离线迁移一定要用docker savedocker load。4.2 没有Docker也能跑的便携思路静态编译与依赖目录如果目标机器连 Docker 都没有还有一个思路优先选择静态编译版本的程序。Go、Rust 生态下很多工具可以纯静态编译比如一些运维 agent、CLI 工具编译完只有一个二进制文件拷过去就能跑不依赖任何系统库。判断一个二进制是否是静态链接可以用file my_tool ldd my_tool输出里显示not a dynamic executable或没有找不到库的报错说明是静态编译的。对于开源项目尽量找 static release 版本下载。如果只有动态编译的二进制还有一个“土办法”从有网的机器上把程序依赖的 .so 文件和可执行文件一起打包。用ldd找出依赖库列表ldd my_tool手动把列出的路径下的 .so 拷到同一个目录比如/opt/my_tool/lib然后在离线机器上设置export LD_LIBRARY_PATH/opt/my_tool/lib:$LD_LIBRARY_PATH /opt/my_tool/my_tool这种方式不优雅也不能保证所有程序都适用但遇到内网临时救急时很实用。4.3 整系统迁移用 debootstrap 离线搬运一个最小根环境有些软件的依赖复杂到你已经不想去理了这种情况下可以干脆把整套最小系统搬过去离线机器上通过 chroot 跑。做法是在有网的机器上用一个叫debootstrap的工具刻一个最小 Debian/Ubuntu 根文件系统sudo debootstrap --archamd64 bookworm /opt/offline-root http://deb.debian.org/debian tar -C /opt/offline-root -czf offline-root.tar.gz .把 tar 包拷到离线机器上解压就可以 chroot 进去mkdir -p /opt/offline-root tar -xzf offline-root.tar.gz -C /opt/offline-root sudo chroot /opt/offline-root /bin/bash进入 chroot 环境后你面对的是一个独立的 Debian 系统想在里面装什么软件都不会影响宿主机。这个方案特别适合“跑一次性的脚本”“调试某个大软件”等场景。缺点是 chroot 里共享宿主机内核如果软件依赖特定内核模块那照样需要额外处理。5. 离线安装现场排错几个高频报错和处理思路离线安装的坑翻来覆去就那么几个但每次遇到都让人血压升高。这里把最高频的报错和排查思路写出来。5.1 “无法定位软件包”不一定是你包名写错这个报错的英文版本通常是Unable to locate package xxx或 yum 的No package xxx available。先说第一个可能原因源里没有这个包。比如在 Debian 默认源里找不到 nginx但你加了 nginx 官方源后在线就能装。离线环境下的 nginx 同理你得确认自己准备的那个本地源里确实有对应包。第二个原因更常见源配置加好了但忘了刷新索引。离线源不会自动更新索引必须手动执行sudo apt update # 或 sudo yum makecache第三个原因是包名和系统发行版的叫法不一致。比如 CentOS 7 里很多包在 EPEL 源里离线环境下需要提前下载 EPEL 的 rpm 包并导入。如果目标机器装了 EPEL但离线环境下没法访问执行yum install时照样报 no package。排查步骤也很简单先apt-cache search 关键字搜一下本地源里到底有没有没有就回到有网机器上下载有就检查源路径和索引。5.2 “软件包似乎无效”的常见根因Package is not valid或者软件包似乎无效在 deb/rpm 安装时都可能出现。我的排查顺序如下。第一步看文件类型。很多“下载失败”的假象就是这样wget 在线页面最后保存下来的是一个 HTML 文件但文件名带了.deb或.rpm后缀。file /path/to/xxx.deb如果输出不是Debian binary package或RPM package那这个文件不能用来安装。第二步看架构。在 amd64 机器上强制安装 i386 的 deb 包也会报无效或“与架构不符”。确认方法dpkg --print-architecture dpkg --print-foreign-architectures rpm -q --whatprovides kernel第三步看包是否损坏。可以用包自身的校验命令dpkg-deb -I xxx.deb rpm -Kiv xxx.rpm推荐在拷贝到离线机器之前先在有网机器上算好sha256sum到现场再对比一遍。别嫌麻烦USB 拷贝过程中文件损坏的概率比你想象中高。5.3 依赖冲突两个包要找同一个库的不同版本这是离线环境里最让人头大的问题。比如软件 A 需要 libssl1.1软件 B 需要 libssl3两个库不能同时由同一个包管理器管理或者系统自带的版本已经不满足新包要求。这种情况下不要一股脑用--force-depends强制硬装那会把系统搞到不可恢复。我的建议是用apt-cache policy libssl1.1或yum list available libssl*看看离线源里有哪些版本优先寻找同时满足多个依赖约束的版本如果实在冲突把其中一个依赖锁成旧版本另一个软件用容器或者源码编译 自定义动态库路径的方式运行。这里再次体现容器方案的优势容器里的依赖和宿主机的依赖互相隔离依赖冲突问题直接绕开。5.4 装完却跑不起来PATH和动态库问题软件明明装好了执行却提示command not found或者cannot open shared object file这两个都属于“安装成功但运行环境没配好”。command not found说明可执行文件不在 PATH 里。先确认二进制文件是否存在ls -l /usr/local/nginx/sbin/nginx存在就把它所在目录加进 PATH。cannot open shared object file说明动态库路径没配置好参考前面 3.4 节的做法写 ld.so.conf.d 配置文件再ldconfig。排查时用ldd看缺哪些库ldd /usr/local/nginx/sbin/nginx输出里标了not found的就是缺的库。找到是哪个包提供的用包管理器装回去或者手动设置LD_LIBRARY_PATH指向库所在目录。5.5 一套好用的离线安装后检查清单我在每个离线环境安装完软件后都会按这个顺序检查服务能正常启动systemctl status xxx或直接跑二进制看进程。端口正常监听ss -lntp。依赖库全部加载ldd没有任何not found。日志无致命报错查看/var/log/下对应应用日志。环境变量在重启后仍生效注销重新登录再执行一次命令。这条清单帮我挡掉过很多“当时能跑重启就挂”的尴尬。6. 长期维护离线软件源的个人习惯离线安装做多了以后我最大的体会是永远不要在一次安装前才开始准备包。我现在的做法是维护一个“离线软件仓库”目录里面按系统版本和架构分目录比如ubuntu-22.04-amd64/、centos-7-x86_64/。每次在线上环境确认过能装的包就顺手下载一份归档到仓库并写一个 README记录下载来源、依赖关系、安装命令。这个习惯前期看着麻烦但第二次、第三次遇到同样环境时效率提升是碾压级的。你可以把仓库做成共享目录团队里其他人需要装包直接去对应目录里找而不是又到外网重新下一遍。另外我长时间离线维护后还有一条实实在在的体会在离线环境里源码编译安装的软件一定要用独立--prefix目录并且给每个软件建立一个.env或 systemd unit 文件固定 PATH 和 LD_LIBRARY_PATH。否则三个月后再看系统连你自己都不记得那些散落文件是从哪来的。最后想说离线安装这件事考验的不是你会多少命令而是你愿不愿意在动手之前多想一步环境是什么、依赖有哪些、用什么方式搬运最稳妥。把这三个问题想清楚绝大多数离线安装问题都能在你遇到它们之前被解决掉。