ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Conda离线部署实战:conda-pack、离线频道与pkgs缓存迁移

Conda离线部署实战:conda-pack、离线频道与pkgs缓存迁移

内网机器上跑一段 Python 代码,最烦的不是代码本身,是conda create敲下去之后那一行Solving environment: failed。Conda 环境离线部署这件事,听起来像是把文件拷过去那么简单,真做过几轮就知道,卡人的从来不是拷贝,是依赖树、是平台 ABI、是那条被悄悄改写的 shebang。这篇东西写给谁:手里有一台或者一批不联外网的机器、需要稳定可复现地把 Python 环境搬过去的人;被"包拷过去了却起不来"折磨过几次、想搞清楚每种方案边界在哪的人。刚接手运维的同学,和写代码顺带管环境的算法同学,看完至少能判断自己手上那台机器该走哪条路,以及走错了会在哪一步翻车。

1. 先把问题拆开:无网络环境到底缺什么

1.1 离线部署的四个硬约束

很多人把离线部署理解成"把包文件复制过去",这个理解只对了一半。一个 conda 环境的正常运行,背后其实有四层东西在支撑,缺任何一层都会出问题,而每一层的解决难度完全不同。

第一层是包文件本体。也就是.conda或.tar.bz2格式的压缩包,里面是编译好的 Python 包、二进制扩展、头文件、元数据。这一层最容易搬,rsync或者scp就完事。

第二层是依赖解析所需的索引信息。conda 在安装时不是拿到一个包就装,它要去频道的repodata.json里读每个包的依赖声明、版本约束、构建号(build string),然后解出一棵满足所有约束的依赖树。离线环境里如果没有这份索引,或者索引是空的,conda install第一步就崩,报PackagesNotFoundError。

第三层是平台与 ABI 的匹配。conda 频道是按子目录组织的:linux-64、osx-arm64、win-64、noarch。你在一台linux-64上打的包,拿到另一台linux-64上未必能跑——glibc 版本、libstdc++ 版本、CPU 指令集(比如 AVX2 优化过的包在老的 CPU 上直接Illegal instruction)都是隐藏的雷。noarch是纯 Python 包,这一层最安全。

第四层是环境前缀的硬编码路径。这是最容易被忽略、也最坑的一层。Python 环境里有一堆文件写死了绝对路径:bin/pip这个脚本的第一行是#!/opt/conda/envs/ml/bin/python,bin/jupyter同理,conda-meta里的记录、.pth文件、脚本里的sys.prefix相关配置,全都是绝对路径。你把环境从/opt/conda/envs/ml挪到/data/envs/ml,这些路径就全错了,表现是command not found或者 Python 找不到自己的库。

搞清楚了这四层,就能理解为什么市面上会有好几种方案——每种方案本质上是在"用不同的方式处理这四层",取舍点在于操作复杂度、能否保留 conda 的依赖解析能力、以及迁移后的可维护性。

1.2 三种方案的定位与选型判据

下面要展开的三种方案,不是互斥的,也不存在哪个"最好"。它们各自对应一类真实的落地场景,我按"从粗放到精细"的顺序排。

方案核心手法依赖解析适用场景主要代价
conda-pack 整体打包把整个环境目录打成一个归档在联网机完成环境已定型、目标机只需运行无法再增减包,跨平台窗口窄
自建离线频道用conda index生成 repodata在离线机完成目标机需要灵活装包、多环境共用前期搭频道工作量大
pkgs 缓存直搬 + wheelhouse搬缓存目录和 pip 轮子离线机有限解析已有联网机做同构环境、以 pip 包为主依赖包元数据缺失时解析失败

判断自己该走哪条,问三个问题就够了。第一,目标机上还需要再装新包吗?如果需要,方案一直接出局,因为它打完包就冻结了,你没法在里面conda install新东西,甚至连路径上的硬编码都要靠conda-unpack修。第二,目标机器是几台?如果是一台,方案一最省事;如果是二十台,方案二一次性搭好频道,后面每台机器都能按需装,长期成本反而低。第三,你的依赖里 pip 包占比多少?如果项目重度依赖内网 Git 仓库或者私有 PyPI 上的包,那基本绕不开方案三里的 wheelhouse。

有个反直觉的经验:方案一(conda-pack)在生产环境里其实是三种方案中出问题最多的。原因不在工具本身,而在于它把环境的"运行时快照"和"可维护性"一起冻住了,一旦目标机环境和打包机有任何 glibc、驱动、CUDA 版本的细微差别,报错信息还特别不直观——你看到的是ImportError: libcudart.so.11.0: cannot open shared object file,但真实原因是驱动版本对不上。

2. 方案一:conda-pack 整体打包,省事但边界很硬

2.1 它到底做了什么:从原理看坑在哪

conda-pack的行为说起来简单:递归收集指定环境目录下的所有文件(排除pkgs缓存、__pycache__、*.pyc这类可再生内容),打成一个 tar.gz,另附一份conda-meta元数据清单。解压到目标机后,你再运行一个叫conda-unpack的脚本完成收尾。

关键在于conda-unpack这一步。它做的事是前缀重定位:扫描环境内所有文本文件,把打包时的前缀字符串(比如/opt/conda/envs/ml)替换成解压后的实际路径。同时它会顺手修掉那些以硬编码 shebang 开头的可执行脚本。所以不跑conda-unpack就直接用,基本必然翻车。

理解了这一点,就能明白为什么它跨平台能力几乎为零。包里装的是已经编译好的二进制,linux-64的.so文件拿到win-64上没有任何意义。noarch的纯 Python 包倒是可以跨,但一个真实环境里不可能只有 noarch 包。所以下面这条规矩必须记住:

conda-pack 只能在相同操作系统、相同架构、且目标机 glibc 版本不低于打包机的前提下使用。

glibc 这条尤其要注意,它是单向兼容的。高版本 glibc 编译出来的二进制,依赖GLIBC_2.28这类符号版本,拿到 glibc 2.17 的机器上跑,报错是version 'GLIBC_2.28' not found。反过来低版本打的包拿高版本跑,一般没事。实操里的正确做法是:永远在版本更老的那台机器上打包,或者在一台刻意压低 glibc 的容器里打包。

2.2 完整操作流程与参数详解

联网机上的打包动作,我一般这么写:

# 1. 建一个干净的打包专用环境,不要让 conda-pack 去污染业务环境 conda create -n packer python=3.10 -y conda activate packer conda install -c conda-forge conda-pack -y # 2. 打包目标环境(注意:不是当前激活的 packer 环境) conda pack -n ml-prod \ --ignore-editable-packages \ --exclude '*.pyc' \ --exclude 'tests' \ --exclude '*/tests/*' \ -o /data/artifacts/ml-prod-20240612.tar.gz \ --format tar.gz \ --compress-level 6 # 3. 生成校验和,这一步别省 sha256sum /data/artifacts/ml-prod-20240612.tar.gz > /data/artifacts/ml-prod-20240612.sha256

几个参数值得单独说。--ignore-editable-packages处理的是pip install -e .装的可编辑包,这种包的元数据指向源码目录,如果源码不在目标机上,打包时会直接报错退出,加上这个参数就跳过它们——但你要自己保证目标机上有对应的源码。--exclude支持 glob 通配,用来剔除测试数据、日志、__pycache__这类体积大又没用的东西,我一个带 torch 的环境靠这个能把包从 4.2G 压到 2.6G。--compress-level取值范围 0-9,默认 4,设到 6 是一个体积和耗时的平衡点,再往上收益递减得厉害。

目标机上的解压和收尾:

# 1. 目标目录结构先建好 mkdir -p /data/envs/ml-prod # 2. 解压 tar -xzf ml-prod-20240612.tar.gz -C /data/envs/ml-prod # 3. 激活并修复前缀(两步顺序不能反) source /data/envs/ml-prod/bin/activate conda-unpack # 4. 验证 python -c "import sys; print(sys.prefix)" which pip

conda-unpack这一步有个细节:它本身就在环境的bin/目录下,所以你得先激活环境才能直接敲命令,或者用绝对路径/data/envs/ml-prod/bin/conda-unpack也行。跑完之后它不会打印什么成功提示,静默通过,这时候你可以用grep -r "/opt/conda/envs/ml-prod" /data/envs/ml-prod/bin/ | head检查一下有没有残留的旧路径。

2.3 平台一致性与文件占用这两类现场翻车

Windows 上的情况和 Linux 差异不小,值得单拎出来说。Windows 环境打包时,绝对不要激活待打包的那个环境,因为 Windows 有文件锁机制,激活状态下python.exe、DLLs/*.pyd会被进程占用,conda pack会报PermissionError: [WinError 32] 另一个程序正在使用此文件。正确做法是在 base 或者其他环境里执行conda pack -n target-env。解压后的收尾脚本也不一样,是Scripts\conda-unpack.exe。

还有一个 Windows 特有的坑:路径长度限制。conda 环境的目录层级本来就深,site-packages下面再套几层包名,很容易超过 260 字符,tar解压时直接报path too long。解决办法是把环境解压到盘符根目录下的短路径,比如D:\e\ml,或者提前在目标机打开长路径支持。这个坑我在两台机器上撞过,第二次才开始习惯性地把环境放在短路径下。

再补充一个容易被忽视的操作禁忌:conda-unpack跑完之后如果你又在里面用 pip 装了新东西,pip 会重新写入新的 shebang,可能又变回错误的前缀。所以方案一的正确使用姿势是一次打包、一次收尾、之后只读不改。真要改,就在联网机上改完重新打包,别在离线机上手动打补丁——那会让"可复现"这三个字彻底失效。

3. 方案二:自建离线频道,把 conda 的解析能力保住

3.1 用 conda index 生成一份能被识别的 repodata

方案二的核心思路是:不去规避 conda 的依赖解析,而是把"解析所需的原料"——也就是频道目录和repodata.json——一起搬过去,让离线机上的 conda 像访问远程频道一样访问本地目录。这条路前期搭起来最费劲,但一旦搭好,长期收益最高。

目录结构必须严格按 conda 的频道约定来,子目录名就是平台标识,一个字母都不能错:

mkdir -p /srv/offline-channel/{linux-64,noarch,win-64}

把下载好的包文件按平台放进去。noarch的纯 Python 包装noarch/,带二进制扩展的装对应平台目录。这一步最容易出错的是把 noarch 包装进了 linux-64,表面上conda index也能跑通,但客户端解析时可能因为找不到匹配的 subdir 而报PackagesNotFoundError,排查起来相当费时间。

生成本地频道索引:

# 新版推荐用独立的 conda-index 包,功能更全,也支持 .conda 格式 conda install -c conda-forge conda-index -y python -m conda_index /srv/offline-channel # 检查生成结果,每个子目录下应该出现 repodata.json 及其压缩版本 ls -la /srv/offline-channel/linux-64/ | grep repodata

正常情况下会生成repodata.json、repodata.json.bz2、repodata.json.zst三个文件。conda 客户端会优先拉.zst,其次.bz2,最后才是明文 JSON。所以压缩文件损坏或者没同步,客户端也会解析失败,这个细节很多教程没写,但你一旦遇到"本地目录明明有包却装不上",先看这三个文件是不是齐全。

3.2 离线机上的 .condarc 与安装命令

客户端侧需要一份明确的频道配置。写进~/.condarc或者项目目录下的.condarc:

channels: - file:///srv/offline-channel - defaults channel_priority: strict offline: true show_channel_urls: true

channel_priority: strict这个配置要理解清楚:它保证同名包只从优先级最高的频道里取,不会出现"这个包装了 A 频道的 numpy,那个包拉了 B 频道的 numpy"这种混装。离线场景下我强烈建议开 strict,因为你的本地频道里包的版本是人为挑选的,混装很容易出现二进制不兼容。offline: true则让 conda 彻底不发起任何网络请求,把所有连接错误扼杀在源头,而不是等超时报错。

安装命令:

conda create -n py310 --offline \ -c file:///srv/offline-channel \ python=3.10 numpy=1.24 pandas=2.0 \ -y

如果报PackagesNotFoundError,用这条命令定位缺什么:

conda search --offline -c file:///srv/offline-channel "numpy" --info

还有一种简化的写法,前提是包都放在 conda 的conda-bld目录下(这个目录被隐式当作一个叫local的频道):conda install --use-local numpy。--use-local本质上就是把conda-bld当作频道使用,适合快速验证单个包,不适合长期维护。

3.3 依赖白名单怎么生成:用 explicit 清单反推包列表

这是方案二里最容易做错、也最有技巧的一步。你不能靠"我要 numpy 和 pandas,那就下这两个"这种思路,因为 numpy 自己要依赖blas、libgfortran、python_abi,pandas 又依赖pytz、dateutil、numpy,链式展开下来可能几十个包。手工找齐几乎不可能。

正确做法是在一台联网机上,用最小化的方式做一次"预演安装",把最终解析出来的完整包列表导出来:

# 联网机:只下载不安装,把包落到 pkgs 缓存 conda create -p /tmp/stage --download-only \ -c conda-forge \ python=3.10 numpy pandas scikit-learn -y # 导出精确清单,这个文件里是带完整 URL 的包列表 conda list --explicit -p /tmp/stage > /tmp/stage-explicit.txt # 文件长这样: # @EXPLICIT # https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.3-py310h...conda # https://conda.anaconda.org/conda-forge/noarch/pytz-2023.3-pyhd8ed1ab_0.conda

有了这份清单,把 URL 里的 subdir 段(linux-64或noarch)抽出来,就能把对应的包文件自动归类到频道目录里。我一般用一小段 shell 做这件事:

grep -v '^@' /tmp/stage-explicit.txt | while read -r url; do fname=$(basename "$url") subdir=$(echo "$url" | awk -F/ '{print $(NF-1)}') cp "$HOME/miniconda3/pkgs/$fname" "/srv/offline-channel/$subdir/" 2>/dev/null \ || echo "MISSING: $fname" done # 重新生成索引 python -m conda_index /srv/offline-channel

那段MISSING打印很关键,它告诉你哪个包没从缓存里找到,需要单独补下载。

3.4 频道优先级与包版本漂移的两处隐雷

第一处雷是默认频道的服务条款确认问题。较新版本的 conda 在使用默认频道时会要求确认服务条款,离线机上这个确认动作如果没提前做过,conda create会卡在一个交互提示或者直接失败。稳妥的做法是:离线机的.condarc里只保留file://本地频道,根本不去碰默认频道;或者提前在联网机上完成确认,把.condarc连同相关配置一起同步过去。如果你的依赖全部来自社区频道,那把默认频道整个删掉是最省心的。

第二处雷是版本漂移。离线频道里的包版本是静态的,但你的environment.yml里如果写的是numpy>=1.20这种宽约束,今天解析出来的和三个月后新同事照文档操作解析出来的,结果可能不一样——因为中间你往频道里补过新版本的包。企业级做法是用conda-lock把解析结果锁死,生成一份带完整 URL 和 sha256 的锁文件,离线机上照着锁文件装,保证每个人拿到的字节完全一致。

# 联网机 conda install -c conda-forge conda-lock -y conda-lock lock -f environment.yml -p linux-64 # 会生成 conda-lock.yml,内含每个包的精确 URL 与 hash # 离线机装的时候: conda-lock install --offline --prefix /data/envs/app conda-lock.yml

4. 方案三:pkgs 缓存直搬,加上 pip wheelhouse 混合打法

4.1 搬 pkgs 缓存前必须搞清楚的事

conda 的pkgs_dirs是一个很有意思的目录。它既是下载缓存,又是环境构建时的"源"——conda 创建环境时,不是重新解压包,而是用硬链接把 pkgs 缓存里的文件链接到环境目录。理解这一点非常关键,因为它直接决定了你搬缓存的方式。

先看当前配置:

conda config --show pkgs_dirs # 典型输出: # pkgs_dirs: # - /home/user/miniconda3/pkgs # - /home/user/.conda/pkgs

conda 会按顺序在这几个目录里找包,找到就用。所以离线机上的策略很简单:把联网机上完整的pkgs目录原样搬过去,然后让 pkgs_dirs 指过去。

但"原样"这两个字有讲究。因为 pkgs 目录里的解压文件夹和压缩包之间是硬链接关系,如果你用cp -r复制,硬链接关系会被打断,变成两份真实数据,体积翻倍不说,后续conda clean的行为也会异常。正确做法是保持硬链接地传输:

# 推荐:tar 管道,保留硬链接 tar -C /home/user/miniconda3 -cf - pkgs | \ ssh target-host "tar -C /home/user/miniconda3 -xf -" # 同机内移动用 cp -a cp -a /home/user/miniconda3/pkgs /mnt/usb/pkgs

传完之后,离线机上确认 pkgs_dirs 指向正确,然后直接按 explicit 清单建环境:

# 先确认 pkgs_dirs 配置生效 conda config --set pkgs_dirs /home/user/miniconda3/pkgs conda config --show pkgs_dirs # 用 explicit 清单离线建环境 conda create -n app --offline --file /path/to/explicit.txt -y # 验证:所有包都应该有来源,不应该出现 pip 标记 conda list -n app | head -30

这里有个必须反复提醒的禁忌:离线机上的conda clean一个选项都不要碰。conda clean --packages会清掉 pkgs 缓存里没有被任何环境引用的包,conda clean --all更狠,连索引缓存一起删。对离线机器来说,pkgs 缓存就是你的"安装源",删掉之后想再装任何东西都没戏了,只能重新从外网搬一次。我刚接手的时候不知道这条,一次conda clean -a把一台机器的缓存清干净了,后面补了半天。

4.2 pip wheelhouse:把轮子按目标平台下全

conda 解决不了一切,生物信息、深度学习这些领域的包,很多只在 PyPI 上有。这部分就得靠pip download交叉下载。核心命令:

mkdir -p /data/wheelhouse pip download -r requirements.txt \ -d /data/wheelhouse \ --platform manylinux2014_x86_64 \ --python-version 310 \ --implementation cp \ --abi cp310 \ --only-binary=:all:

这几个参数一个都不能少,而且它们之间有强依赖关系:--platform必须和--only-binary=:all:一起用,否则 pip 会直接报错When restricting platform and interpreter constraints, either --no-deps must be set, or --only-binary=:all: must be set。这条报错信息写得挺清楚,但第一次见的人多半会懵。

manylinux2014_x86_64对应 glibc 2.17 及以上,兼容性最广。如果你确定目标机很新,用manylinux_2_28_x86_64能拿到更新的轮子;目标机是老古董,就得往下找manylinux1_x86_64(glibc 2.5)。这个标签选错,装上去运行时报GLIBC_2.28 not found,和前面 conda-pack 那个坑是同一个根因。

--python-version 310和--abi cp310是配套的,前者是版本号(注意不带点),后者是 ABI 标签。cp310 表示 CPython 3.10,如果你的环境是 3.9 就改成39和cp39。

有些包没有提供 wheel,只有 sdist(源码包)。--only-binary=:all:会导致它们在下载阶段直接失败。处理方式是单独跑一遍不带--only-binary的下载,但要注意:sdist 在离线机安装时需要编译,意味着目标机上要有 gcc、g++、Python 头文件,还要有包依赖的系统库。如果目标机是纯净的最小化安装,这条路基本走不通,得提前在联网机上用pip wheel把源码包编译成 wheel 带过去:

# 在联网机上把 sdist 编译成 wheel pip wheel -r requirements.txt -w /data/wheelhouse \ --no-deps # 依赖已经单独下过了,这里只处理编译

离线机上的安装:

pip install \ --no-index \ --find-links=/data/wheelhouse \ -r requirements.txt

--no-index是必须的,它告诉 pip 完全不要尝试连 PyPI;--find-links指向本地目录。这两条一起用才是真正的离线安装,只写--find-links不写--no-index,pip 在本地找不到时会去网上找,然后卡在一个超时上,表现出来的现象是"装了半天没动静"。

4.3 conda 与 pip 混装时的冲突处理

混合方案的现场事故,八成出在 conda 和 pip 互相覆盖上。典型场景:conda 装了numpy 1.24,之后pip install某个包时顺手把numpy升级到了1.26,结果 conda 的conda-meta里仍记着 1.24,conda list显示 1.26 旁边挂了个<pip>标记,环境进入一种"两个包管理器都认为自己说了算"的薛定谔状态。

处理原则我总结成三条。第一,顺序固定:先 conda 后 pip,把能用 conda 解决的依赖尽量在 conda 层装完。第二,pip 安装时加--no-deps逐个装,尤其是那些依赖 numpy/scipy 的包,避免 pip 自作主张重装科学栈。第三,装完立刻验证:

# 检查有没有 pip 覆盖 conda 包的痕迹 conda list -n app | grep '<pip>' # 检查依赖树是否一致 pip check

pip check会输出所有版本冲突,conda list里的<pip>标记则告诉你哪些包的管理权已经跑到 pip 那边了。这两个信号一旦出现,我的做法不是修补,是回滚重建——离线环境里的依赖树一旦被搞乱,靠手工修回来的成本远高于照 explicit 清单重建一次,而且重建出来的结果是可复现的,修出来的不是。

5. 三种方案横向对比与企业级配套措施

5.1 一张表看清取舍

评估维度conda-pack离线频道缓存直搬 + wheelhouse
首次搭建耗时20 分钟2-4 小时(含包收集)1-2 小时
目标机能否新增包否(只读快照)能,受频道内容限制能,受缓存内容限制
依赖解析在哪完成联网机离线机联网机为主
跨平台能力仅同平台频道可含多平台仅同平台
多机分发成本高(每台都要传整包)低(共享频道)中
体积中等(压缩后)大(含全平台包)最大(缓存+轮子)
出问题后的可诊断性差(二进制黑盒)好(日志清晰)中
适合的团队规模1-3 人5 人以上2-10 人

表格里的"可诊断性"这一项,是我自己踩坑之后加进去的维度。conda-pack 出问题时,你面对的是一个几百兆的二进制包,报错信息只有一个ImportError,得靠ldd、readelf一点点倒推;而离线频道方案里,conda 会明确告诉你"哪个包的哪个依赖版本约束不满足",排查路径短得多。所以在多人协作的团队里,我更倾向推方案二。

5.2 归档规范、校验与回滚这三件事

前面讲的都是"怎么让环境跑起来",企业级落地还要多问一句:三个月后新同事还能 Reproduction 吗?这就需要三样东西。

归档目录规范。我习惯按"日期-环境名-平台-哈希前八位"命名,一个环境的所有产物放在同一个目录下,包括包文件、explicit 清单、.condarc副本、构建脚本、校验和文件。目录一旦定下来就不要改结构,因为半年后你会写自动化脚本去扫这些目录。

/data/artifacts/20240612-ml-prod-linux64-a1b2c3d4/ ├── ml-prod-20240612.tar.gz # conda-pack 产物 ├── ml-prod-20240612.sha256 ├── explicit-linux64.txt # 精确包清单 ├── wheelhouse/ # pip 轮子 ├── condarc.backup # 客户端配置 └── BUILD.md # 构建说明与已知问题

校验和。传输环节的静默损坏比想象中常见,尤其是通过 U 盘、内网文件共享这类介质。sha256 校验必须做,而且要在解压之前做。校验失败就直接重传,不要抱侥幸心理去解压——我见过一次 tar 包尾部损坏导致解压出来的环境缺了几个.so,排查了两小时才发现是传输问题。

回滚。离线机上新环境上线前,先mv老环境到envs/.backup/再部署新的,出问题一条mv就回去了。相比"就地升级",这个习惯救过我至少三次。配合conda env list手动指定envs_dirs也能让 conda 认出这些非标准路径下的环境。

6. 现场排查:那些报错背后真正的意思

6.1 高频故障的定位思路

CondaHTTPError: Connection refused或长时间卡在Solving environment。离线机上出现连接类错误,八成是没加--offline,或者.condarc里还留着远程频道。用conda config --show channels确认一下,把所有https://开头的频道删掉,再加--offline重试。

PackagesNotFoundError: The following packages are not available from current channels。先别怀疑包没下全,用conda search --offline -c file:///your-channel <pkg>直接查。如果搜得到但装不上,问题多半在依赖约束上——某个间接依赖的版本区间在频道里找不到匹配。用--info参数看包的所有 build 和依赖声明,往往能发现是个python_abi或libgcc-ng的版本卡住了。

conda-unpack: command not found。两种情况:一是环境没激活,二是conda-unpack没被正确打包进bin/。先检查ls /data/envs/xxx/bin/ | grep conda-unpack。如果文件在但还是找不到,说明 PATH 没生效,用绝对路径执行。

ImportError: libxxx.so: cannot open shared object file。这不是 Python 层的问题,是动态链接器找不到共享库。用ldd /path/to/xxx.so | grep "not found"定位缺哪个,然后判断是LD_LIBRARY_PATH没设,还是包里根本没有这个.so(后者常见于 conda-pack 跨机器迁移时,某些系统的库被假定存在)。

ERROR: Could not find a version that satisfies the requirement xxx(pip 阶段)。离线安装时报这个,通常不是 wheelhouse 里没有,而是目标 Python 版本和轮子的 ABI 标签对不上。检查pip --version显示的 Python 版本,再看 wheelhouse 里文件的命名后缀是不是cp310而目标机是cp39。

CondaVerificationError: The package for xxx located at ... appears to be corrupted。sha256 不匹配或者包文件被截断。先conda clean --tarballs清掉这个包,再重新从归档里拷一份过来。注意这里的conda clean --tarballs只清压缩包不清解压目录,相对安全,但离线环境里我仍然建议手工rm指定文件而不是跑 clean。

6.2 速查表

现象最可能原因快速验证处理
装包时卡住不动未启用离线模式conda config --show offline加--offline或设offline: true
包搜不到频道里没有该 subdirls /srv/offline-channel/linux-64 | grep 包名补包到正确子目录后重新conda_index
环境能激活但命令找不到shebang 路径未修复head -1 envs/x/bin/pip执行conda-unpack
二进制导入报 GLIBC 错误打包机 glibc 高于目标机ldd --version对比两端在低版本机重新打包
pip 离线装不上ABI 标签不匹配ls wheelhouse | grep cp3用目标机版本重新下载
环境体积异常大硬链接被打断du -sh与源端对比用 tar 或cp -a重新传输
Windows 打包报文件占用环境处于激活状态看激活项换环境执行conda pack
多环境同名包冲突频道优先级非 strictconda config --show channel_priority设为strict

这张表我打印贴在机房过,后来发现真正用得上的只有前三行,但后五行属于"遇见一次记一辈子"的类型。

最后分享一个实际用的技巧:在离线机上建一个"体检"脚本,把conda --version、conda config --show channels、conda config --show offline、conda env list、pip --version、pip check这几条的输出全部落到一个日志文件里,每部署一台机器跑一次。排查问题的时候,先看这份日志,能快速排除掉"配置没生效"这类低级但高发的故障——我遇到过三次明明改了.condarc但没生效,原因是同一个用户目录下还有一份优先级更高的配置文件在起作用。

返回列表