☰
Anaconda离线迁移:conda-pack与CondaPackError排查
2026/9/30 1:09:33 网站建设 项目流程

内网机器、隔离网段的服务器、客户现场那台连不上外网的工控机——只要你做过 Python 项目交付,这几样东西里总有一样会撞上。"Anaconda 环境离线迁移"这件事,说起来就一句话:把源机器上跑得好好的 conda 环境,原封不动搬到没有外网的机器上还能跑。但真动手的时候,"CondaPackError"这行红字经常在你敲完打包命令的第三秒就跳出来,之后就是一屏一屏的文件列表刷过去,看着头皮发麻。

我前后给三四个隔离环境搬过 conda 环境,踩的坑从 pip 和 conda 混装互相覆盖,到软链接指向源机器绝对路径,再到 conda-unpack 被跑了两遍把路径重写坏,基本上能犯的错都犯过一遍。这篇就把整套流程和 CondaPackError 的排查思路摊开讲清楚:这套方案适合谁?适合所有需要把 Python 环境搬进内网、或者需要给多台离线机器批量部署同一套环境的同学,包括做交付的、做算法验证的、做实验复现的。不需要你有多深的 conda 底层知识,但需要你愿意动手敲命令、看报错。

1. 离线迁移方案怎么选:别一上来就拷贝 envs 目录

1.1 先搞清楚"离线"到底卡在哪一步

很多人第一次做环境迁移,下意识的动作是cp -r ~/anaconda3/envs/myenv /目标机器/anaconda3/envs/,然后在新机器上conda activate myenv,结果发现 python 能起来,但 pip 报错、脚本 shebang 指向老路径、某些包 import 的时候直接找不到动态库。原因是 conda 环境里到处写死了"prefix",也就是环境所在的绝对路径。这不是几个配置文件的问题,是二进制文件里、脚本头里、.pth文件里、甚至.pyc里都可能带着老路径。

所以"离线迁移"真正的难点从来不是"怎么把文件搬过去",而是"搬过去之后怎么让所有路径重新对上"。理解了这一点,方案选型的标准就清楚了:谁的路径重写能力最靠得住,谁就是首选。

注意:路径写死这件事,跟环境里装了多少 pip 包强相关。纯 conda 装的环境,路径基本都规范;pip 装得越杂,后期修路径越痛苦。

1.2 三种主流方案的正面对比

我把常见的三条路子列出来,你自己对号入座:

方案核心原理优点硬伤适用场景
conda env export导出 yaml导出依赖清单,目标机器重新解析安装文件极小,几 KB;跨平台友好目标机器必须有离线 channel 全量包,否则解析失败目标机器有内网 conda 镜像源
conda-pack打包把整个 env 目录压成 tar,用conda-unpack重写 prefix完全离线;一次打包多次复用;一致性极强源/目标架构、glibc 必须兼容无任何内网源,纯离线交付
直接cp -renvs 目录暴力复制零学习成本路径全乱,基本不可用不推荐,除非环境是极简纯 conda 环境

第一种方案的问题在于,"完全离线"这四个字很致命。conda env export导出的是依赖关系,目标机器要真的装上,还是得去 pkgs 目录或者 channel 里找包。如果内网没有搭建完整的镜像源,这条路基本走不通。而且 yaml 里经常混着pip:段落,pip 部分照样得联网。

第二种方案就是本文的主角。它把环境目录整个打包,到目标机器解压之后跑一次conda-unpack,把所有记录在案的前缀字符串替换掉。这个过程不需要网络,也不需要目标机器有任何 conda 包缓存。

第三种方案我不建议,但你可以把它当成"兜底手段"记在心里——万一 conda-pack 死活打不出来包,环境又急着要交付,cp -r加手工修 shebang 也能救场,只是工作量会翻几倍。

1.3 conda-pack 的路径重写机制,值得多讲两句

conda-unpack为什么能修路径?它的做法是在打包时把所有文件扫一遍,记录下哪些文件里出现了源 prefix 这个字符串,解包时把这些出现位置替换成目标 prefix。文本文件(比如脚本、.pth、activate)直接字符串替换就好;二进制文件(.so、可执行文件)麻烦一点,因为替换后长度可能变化,会破坏文件结构,所以它的处理方式是把新路径后面用空字节补齐,保证总长度和原来一致。

这个机制解释了两个常见现象:一是为什么conda-unpack只能跑一次——跑第二次的时候 prefix 已经变了,再按老规则替换就会把路径改坏;二是为什么路径长度差异太大偶尔会出问题——极少数二进制文件对尾部空字节敏感。知道了原理,遇到诡异现象时排查方向就明确了。

实操心得:打包前尽量把源环境放在和目标环境"长度接近"的路径下。比如源是/home/user/anaconda3/envs/myenv,目标尽量别是/opt/a/b,长度差太夸张虽然一般没事,但真出问题时你会怀疑人生。

2. 动手之前:环境体检和打包前的清理

2.1 四个必须确认的兼容性指标

打包只需要一行命令,但打包前如果不做体检,大概率是把问题从源机器搬到目标机器,然后在解包现场炸掉。我一般固定查这四项:

# 1. 操作系统与内核 cat /etc/os-release uname -r # 2. glibc 版本,这个最关键 ldd --version | head -1 # 3. CPU 架构 uname -m # 4. Python 版本与环境路径 conda activate myenv python -c "import sys, platform; print(sys.version); print(platform.machine()); print(sys.prefix)"

glibc 的兼容规则是"向下兼容,不向上兼容"。在高版本 glibc 的机器上打包,拿到低版本 glibc 的机器上跑,通常会报GLIBC_2.xx not found。反过来(低版本打包、高版本运行)基本没问题,因为新系统带着老符号。所以如果源机器和目标机器系统版本不一致,尽量选系统版本更低的那台作为打包源。

架构这条更简单粗暴:x86_64打的包,aarch64上一定跑不了,没有商量余地。给 ARM 边缘设备搬环境,就得在 ARM 机器上打包,或者用 qemu 模拟一套 ARM 环境来打。

2.2 conda-pack 本身的安装(离线情况下怎么办)

联网环境里,一行命令搞定:

conda install -c conda-forge conda-pack

麻烦的是源机器本身也不联网。这时候有两个办法。

第一个办法是从别的能上网的机器上,把 conda-pack 的包文件(.tar.bz2或.conda格式)下下来,拷进内网,然后本地安装:

conda install --offline /path/to/conda-pack-0.7.1-pyhd8ed1ab_0.tar.bz2

--offline是让 conda 别去联网找依赖,直接用本地包。如果报缺依赖,就得把依赖包一起下下来,按顺序装。

第二个办法是走 pip 的 whl 离线包:

pip install --no-index --find-links=/path/to/wheels conda-pack

我个人更推荐第一种,因为 conda-pack 装在哪个环境其实无所谓,装在 base 里最省事。装在 base 里,用conda pack -n myenv一样能打包其他环境。

注意:conda pack -n myenv是在当前环境执行 conda-pack,但打包的目标是myenv。容易搞混的是-n和-p:-n后面跟环境名,-p后面跟环境路径。别写反了。

2.3 打包前必做的三件清理工作

清理这件事,做完能省一堆麻烦,还能显著缩小包体积。

第一件,删除 Editable 安装。这是 CondaPackError 最高频的触发源,下一章会展开。现在先自查:

pip list --editable

如果输出里有一堆指向本地目录的-e安装,要么卸载重装成正式版本,要么心里有数一会儿要加参数跳过。

第二件,清理缓存和编译产物。这些文件不影响功能,但会让包膨胀:

pip cache purge find $CONDA_PREFIX -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null find $CONDA_PREFIX -name "*.pyc" -delete 2>/dev/null

第三件,检查环境里有没有塞业务数据。我见过有人把几个 G 的训练数据、日志、甚至模型权重直接放在环境目录下,打出来的包 8 个 G,传输和接收双方都很痛苦。环境目录就该只放环境,业务数据单独传输。

du -sh $CONDA_PREFIX du -sh $CONDA_PREFIX/* | sort -rh | head -10

这个命令能快速定位体积大头。如果发现lib/python3.x/site-packages下面某个包特别大(比如 torch 的几个 G),那是正常的,不用管;如果发现根目录下有个data文件夹好几个 G,那就该移走。

3. conda-pack 打包全流程实操

3.1 核心命令与参数逐条拆解

最基础的一条命令:

conda pack -n myenv -o /data/backup/myenv.tar.gz

看起来简单,但实际生产里我一般会加上几个参数:

conda pack -n myenv \ -o /data/backup/myenv.tar.gz \ --ignore-missing-files \ --dereference \ --n-threads 4 \ --compress-level 6

每个参数解决什么问题:

  • --ignore-missing-files:跳过那些"记录在 conda-meta 里、但实际已经不存在"的文件。这个参数是把双刃剑,用了能绕过deleted/overwritten报错,但可能带出一个残缺环境。我通常先用它把包打出来,再去目标机器上验证功能,确认没问题才敢交付。
  • --dereference:把符号链接实体化成真实文件。不带它的话,包里的软链接会保持原样,到目标机器上可能指向一个根本不存在的路径。这个参数会让包变大一些,但省心。
  • --n-threads 4:多线程压缩。环境大的时候(比如带 torch 的),单线程能压十几分钟,开 4 线程能砍掉大半时间。
  • --compress-level 6:压缩级别 1 到 9,越高越小越慢。6 是个甜点值,再往上收益很小,时间成本陡增。

还有一个很有用的参数是--exclude,可以把不需要的目录排除掉:

conda pack -n myenv -o myenv.tar.gz \ --exclude "lib/python3.10/site-packages/tests/*"

能把包体积压下来不少,但要小心别排除掉运行时真正需要的模块。

3.2 打包过程的现场记录与产物校验

一条正常打包命令的输出大概长这样:

Collecting packages... Packing environment at '/home/user/anaconda3/envs/myenv' to '/data/backup/myenv.tar.gz' [########################################] | 100% Completed | 38.2s

看到进度条跑满,先别急着高兴,做三个校验:

# 1. 文件能不能正常解出 tar -tzf myenv.tar.gz > /dev/null && echo "archive OK" # 2. conda-meta 是否完整 tar -tzf myenv.tar.gz | grep -c "conda-meta/.*\.json" # 3. 记录校验和,用于传输后比对 md5sum myenv.tar.gz > myenv.tar.gz.md5

第二项尤其重要。conda-meta目录是环境的"身份证",记录着每个包的元信息和所有文件清单。如果打包后这个目录里的 json 文件数量明显少于conda list的包数量,说明包本身就有问题,解包后conda-unpack会找不到元数据。传输完成后,在目标机器上再跑一次md5sum -c,确认文件没在传输中损坏。大文件跨网段拷贝损坏不是小概率事件,我就遇到过一次,包解到一半报 gzip 校验错,重新传才好的。

4. CondaPackError 深度排查手册

4.1 报错定位的正确姿势

conda-pack 的异常体系不算复杂,但报错信息有时候非常长,动辄列几百行文件路径,直接看第一屏很容易被吓到。我的习惯是先看第一行异常类型,再看最后一段总结,中间那一大堆文件列表只当佐证材料。

conda pack -n myenv -o myenv.tar.gz 2> pack_error.log echo $? tail -50 pack_error.log

把 stderr 重定向到文件,tail看结尾。conda-pack 的问题描述通常放在最后,前面是它扫描出来的问题文件清单。这个操作能帮你省掉大量翻屏时间。

从异常类型看,主要分这么几类:editable packages(可编辑安装)、deleted/overwritten(文件被覆盖或删除)、does not appear to be a conda environment(不是有效的 conda 环境)、Unknown package manager(包管理器识别失败),以及权限、磁盘、路径相关的系统级错误。下面逐个说。

4.2 CondaPackError:环境里有 Editable 安装包

完整报错形态:

CondaPackError: Cannot pack an environment with editable packages installed (e.g. from `python setup.py develop` or `pip install -e`). Editable packages found: - myproject - utils

根因很直接:pip install -e .这种可编辑安装,装进去的不是真实文件,而是一个指向源码目录的链接(通常是一个.egg-link文件加一条.pth路径记录)。源码目录不在环境里,打包自然打不进去。就算强行打进去了,目标机器上那个.pth指向的路径也必然不存在。

三种处理方式,按推荐度排:

  1. 卸载后正式安装(最推荐):

    pip uninstall myproject pip install . # 注意没有 -e

    装完再打一次包,问题消失。代价是改了源码需要重装才能生效,但离线交付场景本来也不需要在目标机器上改源码。

  2. 加参数跳过:

    conda pack -n myenv -o myenv.tar.gz --ignore-editable-packages

    包能打出来,但目标机器上就没有这几个包了。如果这几个包是可有可无的辅助工具,问题不大;如果是核心依赖,千万别这么干。

  3. 源码目录一起拷,目标机器单独装:把源码目录和环境 tar 包一起传过去,在目标机器上先激活环境,再pip install ./myproject。这个方式适合那种确实需要经常改的场景。

实操心得:我一般会在打包脚本里加一句pip list --editable | grep -v "^Package" | wc -l,结果不为 0 就直接中断打包。前置拦截比事后排查便宜得多。

4.3 CondaPackError:文件被删除或覆盖(最高频)

报错长这样,通常非常长:

CondaPackError: Files managed by conda were found to have been deleted/overwritten in the following packages: - numpy 1.24.3 <missing> lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-x86_64-linux-gnu.so - pandas 2.0.1 <overwritten> lib/python3.10/site-packages/pandas/__init__.py

这是我最常遇到的一类,几乎没有之一。根本原因就一个:conda 和 pip 混装同一个包。conda 装了 numpy,后来 pip 又装了一遍 pandas 或者某个依赖,pip 把 conda 装的同名文件覆盖了;或者反过来,pip 装了之后 conda 又装,conda 把 pip 的文件删了。conda 的元数据记的是"我当初装的这批文件的指纹",一旦指纹对不上,conda-pack 就会认为文件被篡改,拒绝打包。

处理路径分三步走。

第一步,定位是哪个包的问题。从报错列表里找包名,比如上面就是numpy和pandas。

第二步,用 conda 强制重装,把文件恢复成 conda 认可的状态:

conda install --force-reinstall numpy pandas

--force-reinstall会重新下载(或从本地 pkgs 缓存取)并覆盖安装,把文件指纹恢复。装完再打一次包试试。

第三步,如果重装解决不了,或者环境已经乱到没法理清,用--ignore-missing-files绕过:

conda pack -n myenv -o myenv.tar.gz --ignore-missing-files

这个参数的含义是"文件清单和实际不一致时,不报错,按实际存在的文件打包"。包能出来,但你要接受一个事实:这个环境的元数据已经是脏的了。后续如果再conda install新包,可能会出现更诡异的问题。

所以我的建议是:能重装就重装,实在不行才用--ignore-missing-files,并且用完之后立刻在目标机器上跑一遍功能验证。

想从根上避免这个问题,就一条纪律:

一个 conda 环境里,同一个包不要既用 conda 装又用 pip 装。装新包之前先conda list <pkgname>查一下有没有装过。查无此包再用 pip。

这条纪律听起来简单,但在"先 conda 装了一堆,后来 pip install -r requirements.txt 一把梭"的工作流里特别容易破功。requirements.txt 里如果列了 conda 已经装过的包,pip 就会毫不犹豫地覆盖一遍。

4.4 CondaPackError:这不是一个有效的 conda 环境

报错形态:

CondaPackError: Environment /home/user/anaconda3/envs/myenv does not appear to be a conda environment

或者:

CondaPackError: Unknown package manager: unknown

根因是 conda-pack 在环境目录下找不到conda-meta目录,或者conda-meta下的 json 文件全部损坏、缺失。常见诱因有三个:环境是用python -m venv建的,只是恰好放在了envs目录下;环境是直接cp -r拷过来的,conda-meta在拷贝过程中丢了;有人手工清理环境目录时把conda-meta当垃圾删了。

诊断命令:

ls -la /home/user/anaconda3/envs/myenv/conda-meta/ | head ls /home/user/anaconda3/envs/myenv/conda-meta/*.json | wc -l

正常环境这个目录下应该有几十到几百个 json 文件。如果目录不存在或者文件数为 0,就不用纠结了,重建环境最省时间:

conda create -n myenv2 --clone myenv

--clone会按 conda 的理解重建一份,但前提是 conda 至少部分认识原环境。如果原环境的conda-meta彻底没了,--clone也会失败,那就只能拿pip freeze的清单在源环境重新装一遍。

顺带说一句,venv建的环境不要用 conda-pack,两者的机制完全不同。venv 环境的迁移有另一套简单办法:直接cp -r,然后把bin/目录下所有脚本的 shebang 从老路径改成新路径,再删掉pyvenv.cfg让它重新生成。虽然土,但针对 venv 挺好用。

4.5 权限、软链接和文件占用类报错

这一类报错信息通常不带CondaPackError前缀,直接是 Python 的PermissionError、OSError或FileNotFoundError,容易被误以为是环境问题。

PermissionError: [Errno 13] Permission denied一般是源环境目录下某些文件属主不对,或者输出目录没有写权限。检查方式:

ls -ld /data/backup find $CONDA_PREFIX -not -readable -ls | head

修复就是chmod或者换个有写权限的输出目录。有个坑是:如果你的 conda 装在/opt下、由 root 装的,普通用户跑conda pack就可能读不到某些文件,这时用sudo -u切到对应属主,或者干脆用 root 打包(打包场景下风险可控)。

软链接类的报错更隐蔽。打包时如果环境里有指向环境外部的软链接(比如有人ln -s /data/models/big.bin $CONDA_PREFIX/lib/xxx),默认打包会保留这个链接,到目标机器上就断了。带上--dereference能实体化这些链接,代价是包变大;如果那个链接指向的是个几十 G 的目录,包会撑爆,所以打包前最好先找出所有外链:

find $CONDA_PREFIX -type l -exec sh -c 'echo "$(readlink "$1") | $1"' _ {} \; | grep -v "^$CONDA_PREFIX"

这条命令列出所有指向环境外部的软链接,看一眼心里就有数了。

4.6 CondaPackError 速查表

把上面这些整理成一张表,出问题的时候直接对号入座:

报错关键字根本原因首选处理备选方案
Cannot pack an environment with editable packages存在pip install -e安装卸载后pip install .正式安装--ignore-editable-packages
Files managed by conda were found to have been deleted/overwrittenconda 与 pip 混装,文件被覆盖conda install --force-reinstall <pkg>--ignore-missing-files
does not appear to be a conda environment缺少conda-meta目录重建环境确认是否 venv 环境
Unknown package manager元数据损坏重建环境检查 json 文件完整性
PermissionError: Errno 13文件属主或目录权限换输出目录 / 调整属主用对应属主用户执行
No space left on device目标磁盘满换到大容量分区清理临时文件
File name too longWindows 长路径限制用--format zip缩短环境路径
解包后软链接失效打包保留了外链重打包加--dereference目标机器手工补链接

5. 目标机器上的解包、激活与验证

5.1 解包与 conda-unpack 的正确顺序

包传过去之后,在目标机器上:

# 1. 选一个最终要长期使用的路径,别用临时目录 mkdir -p /opt/envs/myenv # 2. 解包 tar -xzf myenv.tar.gz -C /opt/envs/myenv # 3. 激活(注意是包自带的 activate,不是 conda 的) source /opt/envs/myenv/bin/activate # 4. 关键一步:重写路径前缀 conda-unpack

这四步的顺序不能乱,第三步和第四步尤其重要。conda-unpack必须在激活之后执行,因为它依赖环境内的 Python 来运行自己。如果你在没激活的情况下直接敲conda-unpack,系统可能找到的是别的环境里的那个命令,那就会把别的环境改坏。

还有个容易被忽略的点:解包路径一旦确定,就别再移动了。因为conda-unpack是把前缀重写成"你执行它时所在的路径"。你解包到/opt/envs/myenv,跑完 unpack,再把这个目录mv到/home/app/envs/myenv,路径又对不上了,所有硬编码路径再次失效。这种问题非常隐蔽,因为python还能起来,只是某些包里会莫名其妙找不到文件。

Windows 上流程类似,激活脚本换成:

.\Scripts\activate conda-unpack

5.2 conda-unpack 只能跑一次

这条必须单独强调,因为它造成的破坏是不可逆的。

conda-unpack的工作原理是"把当前环境里的字符串 A 替换成字符串 B"。第一次执行,A 是老路径、B 是新路径,替换正确。如果你手抖再执行一遍,它会以为当前路径就是"老路径",再去找一个"新路径"来替换,结果就是把路径改成了一堆乱七八糟的东西,或者直接报错退出。这时候没有简单的回滚办法,只能重新解包。

注意:如果实在不确定有没有跑过,可以看环境里有没有conda-unpack留下的完成标记。或者最稳的做法是——重新解包到一个干净目录,重来一遍。反正解包就几十秒,比重装环境便宜太多。

还有一种情况:环境里有嵌套的软链接或者多层目录,conda-unpack跑完之后你发现某几个包的路径还是老的。这种一般是因为那几个文件在打包时就没被纳入管理(比如手动塞进去的文件)。处理办法是手工grep一下:

grep -rl "/home/user/anaconda3/envs/myenv" /opt/envs/myenv/bin /opt/envs/myenv/lib 2>/dev/null | head -20

把残留的老路径文件列出来,逐个用sed替换:

sed -i "s|/home/user/anaconda3/envs/myenv|/opt/envs/myenv|g" <文件路径>

5.3 一份可以抄的验证清单

解包和 unpack 都做完了,别急着交付,按下面的清单过一遍:

# 1. 解释器路径对不对 which python # 期望输出:/opt/envs/myenv/bin/python # 2. Python 版本对不对 python -V # 3. 关键包能不能 import python -c " import numpy, pandas, scipy print('numpy', numpy.__version__) print('pandas', pandas.__version__) print('scipy', scipy.__version__) " # 4. 装了哪些包,数量对不对 pip list | wc -l # 5. 动态库依赖是否齐全 python -c "import ctypes; print('ctypes ok')" ldd /opt/envs/myenv/lib/python3.10/site-packages/numpy/core/_multiarray_umath*.so | grep "not found" # 6. 跑一遍真实业务入口 python /path/to/your_main.py --help

第四步的包数量,最好和源机器上pip list | wc -l的结果对比一下。数量差个一两个正常(有些包在打包时被跳过),差十几个就说明打包时跳过太多东西了,得回头查。

第六步最关键。前面五步全是"环境能不能起来"的检查,只有第六步能验证"业务能不能跑"。我踩过一次坑:环境检查全部通过,结果业务脚本跑到一半报某个包的数据文件找不到,原因是那个包的数据文件不在conda-meta记录里,打包时被漏掉了。这种问题只有跑业务才能暴露。

6. 跨平台坑、动态库冲突与环境维护

6.1 glibc 和 CPU 架构的隐形坑

前面提过 glibc 向下兼容,这里补一个具体的排查手段。如果目标机器上 import 某个包报这种错:

ImportError: /lib64/libm.so.6: version `GLIBC_2.29' not found

先确认目标机器的 glibc 版本:

ldd --version | head -1

再确认报错的那个.so需要什么版本:

objdump -T /opt/envs/myenv/lib/python3.10/site-packages/numpy/core/_multiarray_umath*.so | grep GLIBC_2

如果.so要求的版本高于系统版本,唯一的正规解法是:换一台 glibc 版本够高的机器,或者回到低版本系统重新打包。想去手工替换系统libm.so.6是极其危险的操作,容易把系统搞崩,不建议。

架构问题没有绕路的空间。aarch64的机器只能用aarch64打出来的包。如果你的开发机是 x86,目标设备是 ARM,那就在 ARM 上搭一套打包环境,或者用容器模拟 ARM 环境打包。这条路上没有捷径。

6.2 GLIBCXX 版本冲突的处理

另一类高频报错:

ImportError: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found

这个和上一条不是一回事。GLIBCXX 是 C++ 标准库的符号版本,报错说明系统自带的libstdc++.so.6太老,而环境里的某个扩展模块需要更新的版本。

好消息是这个问题通常有救,因为 conda 环境里一般自带一份比较新的libstdc++:

ls $CONDA_PREFIX/lib/libstdc++.so.6* strings $CONDA_PREFIX/lib/libstdc++.so.6 | grep GLIBCXX_3.4.26

如果环境里的这一份包含所需版本,让动态链接器优先找环境里的即可:

export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH

再重新 import 试试。要让这个设置在每次激活环境时自动生效,可以写进环境激活脚本:

cat >> $CONDA_PREFIX/etc/conda/activate.d/env_vars.sh << 'EOF' export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH EOF

activate.d目录下的脚本会在环境激活时自动执行,这是 conda 官方支持的扩展方式,比在.bashrc里写死环境路径要干净得多。

实操心得:LD_LIBRARY_PATH设成全局的很容易引发其他程序的问题,所以尽量只在这个环境的 activate.d 里设,出环境就自动失效。

6.3 路径硬编码的排查与修补

conda-unpack覆盖了绝大部分路径重写,但有几类它管不到:

  • 手工添加的文本文件:比如你手写的一个sitecustomize.py,里面写死了绝对路径。
  • 二进制文件里编译进去的 RPATH:某些自己编译的 C 扩展,如果编译时加了-Wl,-rpath,/home/user/anaconda3/envs/myenv/lib,这个路径被编译进了 ELF 的 RPATH 段,conda-unpack的字符串替换机制未必能覆盖到。
  • pip 安装的包生成的 console_scripts:大多数情况下 conda-pack 能处理,但如果这些脚本是在打包之后才生成的,就没戏了。

排查方式:

grep -rl "anaconda3/envs/myenv" $CONDA_PREFIX --include="*.py" --include="*.sh" --include="*.pth" 2>/dev/null find $CONDA_PREFIX -name "*.so" -exec sh -c 'readelf -d "$1" 2>/dev/null | grep -q "RPATH\|RUNPATH" && echo "$1"' _ {} \;

第一条找文本文件里的残留路径,第二条找带 RPATH 的二进制文件。文本文件用sed替换就行,二进制文件的 RPATH 就得用patchelf处理了:

patchelf --set-rpath '$ORIGIN/../../..' some_module.so

$ORIGIN是 ELF 的相对路径记号,表示"这个文件自己所在的目录"。用它来写相对路径,可移植性最好,环境搬到哪都不会失效。不过这条属于进阶操作,不到万不得已不用。

6.4 环境迭代后怎么再做一次迁移

离线环境不是一次交付就完事的,后面大概率要加包、升级版本。这时候不要傻乎乎地在源机器上重新打一个大包。

如果目标机器上已经有了环境,只是在里面加几个包,那就在能联网的机器上把需要的包下成离线包,拷过去本地安装:

# 联网机器上,只下载不安装 conda install --download-only -n myenv somepackage # 去 pkgs 缓存目录找到下载好的包 ls ~/anaconda3/pkgs/ | grep somepackage

把这个.tar.bz2或.conda文件拷到目标机器,本地安装:

conda install --offline /path/to/somepackage-1.0-xxx.tar.bz2

如果是 pip 包,同理:

pip download somepackage -d ./wheels # 拷过去 pip install --no-index --find-links=./wheels somepackage

只有在大规模变更(比如整体升 Python 版本)的时候,才值得重新走一遍 conda-pack 全流程。

另外还有个容易被忽略的维护点:conda-pack 打出来的环境,在目标机器上是可以继续用 conda 管理的,只要你把 conda 本体和 pkgs 缓存也准备好了。但如果没有,那就把它当成一个普通的虚拟环境用,pip install离线包、python xxx.py都没问题,只是别再指望conda install能联网干活了。

我个人在几次离线迁移里最大的体会是:打包前的十分钟检查,能省掉现场两个小时的手忙脚乱。具体来说就是三个动作——跑一遍pip list --editable看有没有可编辑安装,跑一遍conda list和pip list交叉比对看有没有混装覆盖,跑一遍du -sh看体积有没有异常。这三条做到位,CondaPackError 里最烦人的那两类基本就不会出现。至于conda-unpack只能跑一次这条,我的做法是在交付文档里用最大号字体写出来,并且在解包脚本里加一个标记文件判断,防止接手的人重复执行——毕竟这个错误一旦发生,除了重新解包没有别的办法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询