ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CentOS手动解包containerd.io RPM报错cpio: rename的排查与修复

CentOS手动解包containerd.io RPM报错cpio: rename的排查与修复 如果你在 CentOS 上折腾 containerd.io 这个 RPM 包想手动解开看看里面的二进制、配置或者 service 文件十有八九会遇到cpio: rename这个报错。我头一回碰到的时候也愣了一下明明 rpm2cpio 解出来的流是正常的cpio 却在 rename 阶段翻车最后文件也没落地。这篇就是我跑一遍完整排查后的记录先从 cpio 解包原理讲清楚报错到底在抱怨什么再给出能直接抄的修复命令顺便把手动解包时最容易踩的坑一起列出来适合想离线提取 containerd 二进制、或者喜欢搞明白包内部结构的人看。1. 这个报错的本质cpio 解包流程拆解1.1 rpm、cpio 与 containerd.io 的关系要搞懂报错得先知道一条链路RPM 包其实不是一个简单的压缩文件它有一个 rpm 头部里面存了包名、版本、依赖关系、脚本等元数据而真正装进系统里的文件是以 cpio 归档的形式塞在包体里的。所以想解开 RPM 包看内容标准做法就是用rpm2cpio把包体转成 cpio 流再交给cpio去抽取文件。containerd.io 就是容器运行时 containerd 的官方包名。在 Kubernetes 节点上kubelet 通过 CRI 和 containerd 打交道所以这个包是很多环境下绕不开的依赖。常规安装很简单一条yum install containerd.io就完事但如果你在多台离线机器上分发或者想检查包里的二进制版本、提取 containerd 和 runc 出来单独用就难免会走到“手动解包”这条路上。手动解包的典型命令组合是rpm2cpio containerd.io-1.6.28-3.1.el8.x86_64.rpm | cpio -idmv这里的i是解包d是自动创建目录m是保留文件修改时间v是显示详细过程。命令看着很简单但恰恰是这个 cpio 解包环节最容易冒出cpio: rename的幺蛾子。1.2 cpio: rename 到底在说什么cpio 解包的时候不是直接在当前目录“放下”文件的。它会在目标位置附近先写一个临时文件等文件内容完整写完之后再把临时文件 rename 成最终的目标文件名。这个“先写临时文件再重命名”的策略是为了避免解包途中崩溃或磁盘满导致目标目录里留着半个文件。这跟文本编辑器保存文件时先写 swap 再 rename 是同一个道理。所以当你看到这类输出./usr/bin/containerd cpio: rename ./usr/bin/containerd: File exists或者cpio: rename ./usr/lib/systemd/system/containerd.service: No such file or directorycpio 的本意是临时文件写好了准备给它取正式名字。结果正式名字所在位置不对劲或者已经有人占了坑或者那层目录根本没建出来于是 rename 就失败了。为什么这些“不对劲”会经常出现在 containerd.io 这种包上因为这个包的文件数量不算特别多但层级很深像./usr/lib/systemd/system/containerd.service、./etc/containerd/config.toml这种路径都有。只要解包时缺了某个参数或者所在目录里已经有残留文件cpio 的 rename 阶段就会先一步爆给你看。2. 排查问题的完整路线与根因定位2.1 还没开始就翻车先确认环境里有 rpm 和 rpm2cpio先说一个比较常见的低级坑有时候你已经准备好了 containerd.io 的 RPM 包结果敲rpm2cpio提示“command not found”再敲rpm也提示“没找到rpm命令”。这种情况我在最小化安装的系统、还有某些精简 Docker 镜像里都遇到过。RPM 工具链本身并不是所有系统的默认安装项。比如你用 CentOS 的容器镜像跑临时环境里面可能连 rpm 都没有。这时候后面一切都无从谈起。先检查command -v rpm command -v rpm2cpio如果rpm2cpio缺失而rpm存在通常装一下 rpm 的配套工具就能解决yum install -y rpm 2/dev/null || dnf install -y rpm在 Debian/Ubuntu 这种非 rpm 系环境里虽然没有 rpm 体系但也可以装rpm2cpio和cpioapt-get update apt-get install -y rpm2cpio cpio这类问题是纯环境问题和 containerd.io 包本身一点关系都没有。先把工具链捋顺再看下面真正的报错现场。2.2 解包现场的复现与日志解读我在一台 CentOS 7.9 的测试机上复现过。先把 containerd.io 的 RPM 包放到/root/rpm/下进入一个空目录执行cd /root/rpm/extract rpm2cpio ../containerd.io-1.6.28-3.1.el7.x86_64.rpm | cpio -idmv第一次执行时报出来的就是./etc/containerd/config.toml cpio: rename ./etc/containerd/config.toml: No such file or directory ./etc/systemd/system/containerd.service cpio: rename ./etc/systemd/system/containerd.service: No such file or directory注意看cpio 已经打印出了文件路径说明文件内容已经写出来了但紧接着 rename 失败。这种情况下把输出倒回看就能发现根因是我用了cpio -imv少了-d参数。cpio -d的作用是“必要时自动创建目录”。rpm 包里的路径是./etc/containerd/如果你解包前没有手动创建./etc/containerd/cpio 又不知道去建目录那它写完临时文件之后想去 rename 到./etc/containerd/config.toml结果发现./etc/containerd/这个目录根本不存在rename 自然失败。还有一种更隐蔽的情况是当前解包目录里已经有从旧版本 RPM 里解出来的残留文件./usr/bin/containerd cpio: rename ./usr/bin/containerd: File existscpio 默认不覆盖已存在的文件。如果你只给了-idv而没有-u当目标文件已经存在时cpio 不会主动覆盖而是把这个“文件已存在”当做一个错误返回。所以你看到File exists通常不是包损坏而是解包目录不够干净。我把常见的报错类型和根因整理成了一张表方便你对照报错信息大概率原因排查方向cpio: rename xxx: No such file or directory缺少-d参数父目录没自动创建检查是否加了-d或手动建目录cpio: rename xxx: File exists目标文件已存在cpio 默认不覆盖清空解包目录或加-u强制覆盖cpio: rename xxx: Permission denied当前用户对目标目录无写权限用ls -ld检查目录权限或切 rootcpio: rename xxx: Invalid cross-device link目标跨了文件系统临时文件写不回来看是不是挂载点嵌套换到同一分区内解包cpio: rename xxx: Read-only file system当前目录在只读挂载上mount查看挂载选项换可写目录2.3 从 rename 失败追到元凶报错出现之后别急着盲试。我一般会按下面几步定位先看当前目录有没有旧文件ls -la find . -name config.toml -o -name containerd | head如果有说明重名冲突。没有再看是不是缺目录ls -ld ./etc/containerd如果目录不存在基本可以确定问题出在 cpio 参数上。还有一个容易忽略的点cpio 的临时文件默认写在目标目录下。如果目标目录所在文件系统空间满了写临时文件那一瞬间可能成功但后续 rename 时没有空间更新目录项也会报错。所以顺手看一眼磁盘df -h /root/rpm另外如果/root/rpm是个跨挂载点的路径比如/root和/root/rpm/extract不在同一个文件系统上rename 也会受到限制。虽然正常目录层级很少遇到跨设备但如果是临时 mount 一个分区来解包就要留意。把这一圈排查走完根因基本就浮出水面了。绝大多数情况就是“缺-d”或者“目标目录有残留文件”二选一。3. 实操复盘两种解法与手把手命令3.1 方案一干净目录 正确的 cpio 参数最稳的解包姿势是开一个全新的空目录然后带上完整参数去解rm -rf /root/rpm/extract mkdir -p /root/rpm/extract cd /root/rpm/extract rpm2cpio ../containerd.io-1.6.28-3.1.el7.x86_64.rpm | cpio -idmuv --no-absolute-filenames参数拆开解释一下-i解包模式表示从 stdin 读入 cpio 归档。-d自动创建目录解决No such file or directory。-m保留文件 mtime。-u无条件覆盖已存在文件避免File exists。-v把解包的文件名列出来方便观察进度。--no-absolute-filenames如果 rpm 包里有绝对路径比如/usr/bin/containerd这个参数会剥离前导/避免写到系统根目录里。手动解包时候必须格外小心不加这个参数、恰好包里有绝对路径可能直接覆盖系统文件。执行完以后用find . -type f | head看一下解出来的文件就能确认是不是完整了。我每次解包 containerd.io习惯先确认这几个关键路径find . -name containerd find . -name runc find . -name config.toml如果文件都在就可以继续做离线分发或者二进制提取。3.2 方案二不折腾解包直接用 dnf/yum 或 rpm 安装说实话如果你不是非要“看”包里的文件只是想装上 containerd那完全没必要和 cpio 较劲。RPM 包本身设计出来就是给包管理器用的yum或者dnf在处理事务时会自动处理依赖、覆盖、目录创建这些麻烦事。本地安装一个 rpm 文件最直接yum localinstall ./containerd.io-1.6.28-3.1.el7.x86_64.rpmCentOS 8 及以上用 dnfdnf install ./containerd.io-1.6.28-3.1.el8.x86_64.rpm如果包依赖比较复杂可以先让 yum/dnf 自动解析dnf install -y containerd.io只需联网且仓库里配好它会把依赖一起装掉。对于离线环境就把 rpm 包和依赖包都拷贝到目标机器再用yum install本地路径的方式装。如果你非得用rpm命令硬上也可以rpm -ivh containerd.io-1.6.28-3.1.el7.x86_64.rpm但rpm不会自动拉依赖遇到依赖缺失会直接报错。这时候先用rpm -qpR查依赖列表再决定要不要加--nodeps。我的建议是--nodeps是最后手段只在你非常清楚自己在干什么的时候才用否则很容易装一个运行时缺依赖后患无穷。为什么推荐用 yum/dnf 而不是手动解包因为包管理器会把 RPM 头部的脚本、依赖、文件冲突策略都跑完很多“cpio: rename”问题在包管理器层面根本碰不到。这是省事的一条路。3.3 顺手解决文件名冲突用 rename 工具批量加编号有时候你解包不是一次两次而是反复解不同版本的 containerd.io 包做对比。不同版本里的config.toml都叫同一个名字放在同一个目录就会互相覆盖。我常做的一个操作是把解出来的文件先批量重命名加上版本号再做 diff。这里就要用到 rename 工具。很多人听到 rename 以为只有一个命令其实在不同 Linux 发行版上有两个同名但功能不一样的 renameutil-linux 的 rename只支持简单的字符串替换用法是rename 旧串 新串 文件。Perl 版本的 rename支持正则表达式用法是rename s/旧正则/新串/ 文件。先确认你用的是哪一个rename --version 2/dev/null || rename -V如果看到util-linux那就是简化版。如果要给每个文件加编号我一般不用 rename 硬拼而是直接一个 for 循环i0 for f in ./etc/containerd/*; do mv $f ${f}.bak.$((i)) done如果系统里是 Perl 版本 rename想给每个文件加编号可以这么干rename s/^/0$_ / ./etc/containerd/*这种写法是把当前计数当作前缀塞到文件名开头实际使用要看具体场景。还有一个更直观的 bulk rename utility就是命令行里的批量重命名工具在多数仓库里名字就叫rename或prename。你可以先用-n参数做 dry-run预览修改结果rename -n s/(.*)/$1.old/ *.toml这里-n会打印“如果改名会变成什么”而不真的执行。批量重命名前先预览是我觉得最值得养成的习惯你永远不知道正则里多加一个空格会把多少个文件改错。3.4 检查解包结果关键二进制验证解包解完不是终点还要确认拿到的 containerd 能跑。我最常用的验证套路file ./usr/bin/containerd ./usr/bin/containerd --version如果版本能打出来说明二进制架构和动态链接基本没问题。再看一眼动态库依赖ldd ./usr/bin/containerdldd可以列出 containerd 运行时需要的共享库。如果某些.so在目标机器上缺失你就算把二进制拷过去也跑不起来。这步在离线分发时候特别重要。另外注意解出来的 containerd 一般是 ELF 可执行文件不要看它有./usr/bin/路径就从普通文件的角度理解只有ldd过了、--version能输出才算真能用。4. 踩坑细节与排查套路总结4.1 一定要知道的 cpio 参数陷阱cpio 这个工具年代久远参数风格和 tar 不太一样理解不深的人特别容易踩坑。除了前面提到的-d和-u我再补充几个我在实际操作中遇到的陷阱-t只列出内容不解包想快速查看 containerd.io 包内文件路径可以用rpm2cpio xxx.rpm | cpio -t这个操作不会产生文件也不会触发 rename。--no-absolute-filenames这个参数不是所有 cpio 版本都有老旧版本可能不认这时候可以在解包前进入临时目录然后通过cd把根目录切走减少绝对路径影响。解包时如果不希望子目录被拆开记得-d和-u一起带。单独-u可能只是覆盖文件但目录不建立单独-d可能因为文件重名而中断。cpio 解包默认是“保留所有者在原包的 uid/gid”。如果你用普通用户解包遇到 uid 无法映射会提示错误正常 root 下没这问题。但离线环境常有非 root 用户操作的情况这时可以加--no-preserve-owner来避免。这些参数细节光靠记不行。我自己的习惯是每次解包前先执行一次cpio --help确认当前版本支持哪些选项避免在老旧系统上踩“选项不存在”的坑。4.2 CentOS 上 python 与 rpm 包的连带问题说一个很经典的连带现象在 CentOS 7 上rpm 命令本身依赖 Python 2 的一些库。有一次我在最小化环境里手贱动过系统 Python导致rpm -qa都跑不起来。结果去解 containerd.io 包时rpm2cpio也跟着报“libpython2.7.so.1.0: cannot open shared object file”。这种情况一般不是 cpio 的问题而是你系统里的 python 和 rpm 包之间出现了依赖错位。CentOS 的 yum 是 Python 写的rpm 命令也会链接 Python 运行库。如果你用源码方式装了个新 Python或者把系统默认 python 指向了其他路径rpm 工具链很容易抽风。遇到这种问题我建议优先恢复系统自带的 Python 路径而不是去替换 rpm。可以这么查rpm -qa | grep ^python which python python2 python3 rpm -V rpmrpm -V rpm会校验 rpm 包本身的文件完整性。如果输出里出现/usr/bin/python这类路径缺失通常就是被替换过。恢复手段是把原来的 python2 包重新装一遍yum reinstall -y python python-libs这种连带问题属于典型的环境修复杂症。解包报错只是表象真正要治的是系统里被你弄乱的 Python 动态库。4.3 系统级注意点SELinux 与磁盘空间再往深处走一点cpio: rename有时候不全是 cpio 的锅。SELinux 开启的状态下如果解包目录的安全上下文不对rename 也可能被拦截。你看到的报错可能是Permission denied但背后其实是 SELinux 策略在起作用。快速验证方式getenforce如果输出是Enforcing先临时允许解包setenforce 0然后重新解包。没问题再恢复setenforce 1注意setenforce 0只是临时缓解不建议长期关闭。真正要做的是给目标目录打上正确的 context或者把解包目录放在允许写的路径下比如/var/tmp。磁盘空间方面我踩过一次很低级的坑df -h看着还有几个 G但/var/tmp所在分区已经满了。而 cpio 的临时文件就写在解包目录里所以会报 rename 失败。排查时不要只盯当前目录的df -h用df -h .和df -i .看 inode 使用量。有些小文件特别多的包磁盘没用多少inode 满了也会 rename 失败。对了解包目录别放在/tmp下。某些系统/tmp带了 nosuid 或 noexec 挂载选项虽然 cpio 不一定受影响但解出来的二进制要执行时会有问题。放/root或者/var/tmp都更稳妥。4.4 一套顺手就能用的排查速查表把上面所有经验浓缩成一张速查表下次遇到cpio: rename可以直接对着走步骤动作典型命令1确认工具存在rpm2cpio --version2进入干净目录rm -rf extract mkdir extract cd extract3带全参数解包rpm2cpio ../containerd.io.rpm | cpio -idmuv --no-absolute-filenames4看报错类型按第1.2节表格对照5检查磁盘/inodedf -h . df -i .6检查 SELinuxgetenforce7验证解包结果file ./usr/bin/containerd、ldd ./usr/bin/containerd这套流程我后来不止用在 containerd.io 上解其他 rpm 包遇到类似报错也能直接用。本质上都是 cpio 解包和文件系统交互出了问题套路是通用的。最后再分享一个小技巧如果解包只想要 containerd 这一个二进制完全不必把所有文件都解出来。可以把rpm2cpio的流过滤一下只抽取需要的路径rpm2cpio containerd.io.rpm | cpio -idmv ./usr/bin/containerd ./usr/bin/runccpio 后面可以直接跟要抽取的路径模式这样又快又干净还能少踩好几次 rename 冲突的坑。我后来离线给内网机器补 containerd 的时候都是这么干的。
返回列表