ARTICLE DETAIL

资讯详情

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

Windows上打arm64 deb包:容器化打包与验证实战指南

Windows上打arm64 deb包:容器化打包与验证实战指南 最早接到“在 Windows 上打一个 arm64 的 deb 包”这个需求时我内心是拒绝的。我手头是一台 Windows 笔记本目标设备是一块 arm64 架构的 Linux 板子要分发的是一个内部用的命令行小工具。当时我的第一反应是deb 不是 Linux 的东西吗这不得先装个 Ubuntu 虚拟机等我把整个流程真正跑通以后再回头看发现当时有三个地方想错了每一个都让我多花了至少半天时间。这篇就聊聊这三个想错的地方以及最后沉淀下来的、能在 Windows 上反复使用的打包和验证流程。适合跟我一样在 Windows 上开发但需要给 arm64 Linux 设备出内部工具包的人看。我会尽量把命令和踩坑细节都写出来你可以直接照着抄。1. 误区一我差点把 deb 包当成“压缩文件换个壳”1.1 拆开一个 deb 包里面根本不是你以为的那种“目录”最开始我的直觉很简单deb 包嘛不就是把文件塞进一个压缩归档里跟 zip 差不多。这个直觉只有一半是对的。deb 包的容器部分确实和架构无关它本质上是一个 ar 归档里面装了三样东西debian-binary一个纯文本内容是2.0标注包格式版本control.tar.xz存放包的控制信息包括control、md5sums、conffiles以及安装前后要执行的维护脚本data.tar.xz存放真实要安装到系统里的文件比如/usr/bin/下的可执行文件。在 Windows 侧你可以用 7-Zip 直接打开一个 deb 包看到的差不多就是这三个成员。所以“打包这件事本身不挑架构”是对的ar、tar、xz 都是完全平台无关的格式。问题出在下一层包里装的东西和控制元数据里声明的东西是不是真的和目标机匹配。1.2 Architecture 字段才是真正的“目标架构证”deb 包在安装时dpkg 会校验一个非常关键的字段Architecture。它写在control.tar.xz里的control文件中。对于 arm64 设备这个字段必须写arm64。为什么说它是“目标架构证”因为 dpkg 在安装时是这样判断的如果包声明的架构和当前系统的dpkg --print-architecture不一致它直接拒绝安装报错类似package architecture (amd64) does not match system (arm64)唯一的例外是Architecture: all它表示“不区分架构”通常用于纯脚本、文档、字体这一类包。但有一个很多人没意识到的问题all包在任何架构上都能装dpkg 不会拦你。如果你把一个里面装着 arm64 二进制的包装成all安装时一点错都不会报团队里的人自然也会默认这个包是跨架构的直到某一天有人在 x86 机器上解开它发现里面是个 ARM 可执行文件才知道出了大事。我当时的错误就是这个之前做过一个纯脚本工具Architecture填的是all于是想都没想给带二进制的工具也填了all。装是装上了但后续交接时同事问“这个包不是 all 的吗怎么里面是二进制”我才意识到元数据不诚实比安装失败更麻烦。写包时请记住这个判断逻辑Architecture 字段安装时行为什么时候用arm64只能在 arm64 设备上安装包含 arm64 编译产物的二进制包amd64只能在 amd64 设备上安装x86_64 架构的二进制包all任何架构都能装纯脚本、文档、配置类包另外一个辅助性的判断标准是文件名。Debian 官方的包命名习惯是包名_版本号_架构.deb比如armdiag_1.0.0_arm64.deb文件名里的_arm64后缀和包内control的Architecture字段应该保持一致。虽说 dpkg 看的主要是内部字段但文件名是给人看的保持一致能少很多误会。1.3 怎么快速核查自己打的包架构写没写对打完包以后我建议养成一个习惯立刻用dpkg-deb --info看一眼元数据不要等拷到板子上再报错。dpkg-deb --info armdiag_1.0.0_arm64.deb输出里会直接列出Architecture: arm64、Depends: libc6 ( 2.31)这些关键字段。这一步在 Windows 上跑不了原生 dpkg但放进容器里就是一行命令的事后面会说具体怎么做。这个误区给我最大的教训是deb 包的“壳”确实与架构无关但“壳”里必须是一份诚实的声明。你可以在 x86 机器上构造出装着 arm64 程序的 deb但如果你不把 Architecture 写对这个包要么装不上要么装上去了也名不正言不顺。2. 误区二我以为在 Windows 上打 deb 必须先装一套完整 Linux2.1 三条路摆在一起容器的轻量优势太明显了既然 deb 是 Linux 生态的东西我一开始的想法就是装个虚拟机装个 Ubuntu然后在里面打包。这条路肯定走得通但性价比极低。实际上在 Windows 上打 deb 包常见的是三条路方案启动速度环境可复现性文件互通维护成本完整虚拟机VirtualBox/VMware慢分钟级差快照很难版本化需要共享文件夹配置高WSL2 发行版快秒级中依赖个人发行版状态通过/mnt/c互通中Docker 容器快秒级高Dockerfile 可版本化-v挂载目录一键互通低实际操作中你需要的不是一个“完整的 Linux 系统体验”而是一个能跑dpkg-deb、dpkg-gencontrol、lintian这些工具的 Linux 用户态环境。Docker Desktop 在 Windows 上正好提供这个敲一行docker run一个干干净净的 Debian 容器就起来了用完即弃。你可能会问那 dpkg 有没有原生 Windows 版本说实话我在需要打包时压根没想去给 dpkg 做移植因为容器这件事已经把问题解决得足够好了。把一个需要长期维护的打包工具链硬塞进 Windows 原生环境是在给自己找不痛快。2.2 在 Windows 终端里直接调用容器的基本姿势Docker Desktop 跑起来以后最常用的命令是这样的docker run --rm -v ${PWD}:/work -w /work debian:bookworm bash -c cat /work/DEBIAN/control这里有几个需要注意的细节${PWD}在 PowerShell 里会被替换成当前目录Docker Desktop 会自动把 Windows 路径转换成容器内能识别的路径。如果你在 cmd 里可以用%cd%。--rm表示容器执行完就删除打包这种一次性任务不需要留容器。-w /work把工作目录切到挂载进来的目录这样命令里就不用写绝对路径了。我第一次跑通这个命令的时候第一反应是就这么简单之前纠结装虚拟机纠结了两天。容器化的思路其实很简单我在 Windows 上缺的不是一个完整的 Linux而是一个能执行 dpkg 工具链的运行环境。Docker 容器恰好把“运行环境”和“日常系统”彻底分开了。2.3 为什么后来我把 WSL2 也放掉了Docker Desktop 本身默认就是跑在 WSL2 后端上的所以我不是说 WSL2 不好。我的意思是不要单独再开一个 WSL 发行版手动装打包环境。原因有二第一WSL 发行版是一个“长期存活”的系统你今天装的工具、今天改的配置都会留在里面。三个月后再打包你很难说清楚当时是怎么打出来的。而 Docker 容器每次从同一个镜像启动环境完全可复现这是工程化上的巨大优势。第二团队协作时一个 Dockerfile 就能把编译器、依赖、打包脚本全部固化下来别人 clone 仓库后docker build一下得到的构建环境和你一模一样。WSL 发行版做不到这件事它太依赖个人机器的状态了。所以我的最终选择是Windows 上只保留 Docker Desktop所有打包相关操作全部封装在容器里。这个选择后来在 CI 上也很好用因为 GitHub Actions 的 Windows runner 同样可以跑 Docker同一套脚本 Windows 和 CI 都能用。3. 误区三换成交叉编译器之后以为剩下只是 gcc 参数问题3.1 第一拳打在链接期库的架构对不上工具本身是 C 写的依赖了 libcurl我觉得这很常规装个交叉编译器gcc-aarch64-linux-gnu把gcc换成aarch64-linux-gnu-gcc不就行了结果一链接就报错错误信息大概是这样/usr/lib/gcc-cross/aarch64-linux-gnu/12/../../../../aarch64-linux-gnu/bin/ld: skipping incompatible /usr/lib/x86_64-linux-gnu/libcurl.so when searching for -lcurl“skipping incompatible”这句话我当时看了好几遍才反应过来我的交叉链接器在找 arm64 版本的 libcurl但容器里默认装的是 x86_64 的 libcurl。交叉编译器要连接的库必须也是 arm64 的。这里有三条路我按推荐程度排个序依赖简单时用 multiarch 装 arm64 库。在 Debian 容器里执行dpkg --add-architecture arm64然后apt-get update再apt-get install libcurl4-openssl-dev:arm64。装完以后链接器还要能找到库通常需要手动加-L/usr/lib/aarch64-linux-gnu头文件路径也要处理pkg-config的环境变量要指到PKG_CONFIG_LIBDIR/usr/lib/aarch64-linux-gnu/pkgconfig。这条路我折腾过依赖少的时候还好依赖一多头文件路径和库路径互相打架是常有的事。依赖复杂时直接在 arm64 容器里编译。见后面第四节的方案 C。这个方法不折腾交叉编译环境容器里apt install libcurl4-openssl-dev装的库天然就是 arm64链接和编译行为与在真实板子上几乎一致。如果只是临时验证也可以考虑静态链接所有依赖。但对有网络解析、动态库加载等复杂行为的程序静态链接会带来一些行为差异后面再说。我当时因为已经配了一半的交叉编译环境硬着头皮把 multiarch 折腾完了。现在回想如果一开始就评估“这个工具依赖了不少库”直接用方案 2 会省很多事。3.2 第二拳打在运行期glibc 版本向下不兼容链接问题解决以后我把交叉编译出来的二进制打进了 deb 包拷到目标板子上安装成功然后运行时报错./armdiag: /lib/aarch64-linux-gnu/libc.so.6: version GLIBC_2.34 not found这个错是典型的构建环境 glibc 比目标系统新导致的。glibc 引入了符号版本机制新版本的 glibc 可能会让二进制引用新符号而目标系统上的旧 libc 里没有这些符号加载时直接失败。我在 Debian bookwormglibc 2.36容器里交叉编译目标板子是 Ubuntu 20.04glibc 2.31于是中招了。解决思路有两个用接近目标系统的发行版容器来构建。如果目标设备是 Ubuntu 20.04就拉一个ubuntu:20.04容器在它里面交叉编译或原生编译。编译时加-static静态链接。把依赖的 libc 也编进二进制从根源上避开目标系统的 glibc 版本问题。但要注意静态链接下涉及getaddrinfo、NSS 这类系统解析能力的调用行为会发生变化适合简单命令行工具不适合网络组件过于复杂的程序。检查二进制依赖了哪些 glibc 版本符号可以用这个命令readelf -V armdiag | grep GLIBC输出里会列出所有引用的GLIBC_x.y.z版本和你目标系统的ldd --version对比一下就能提前预判会不会翻车。3.3 元数据和维护脚本也是“架构相关”的一部分链接问题和 glibc 问题都解决了以后我一度以为终于完了但打包本身的另一半还没处理deb 包里的控制脚本和依赖声明。一个 deb 包里可以包含postinst、prerm、postrm这些维护脚本它们在包安装、升级、卸载时执行。这些脚本是跑在目标机上的不是跑在构建机上的。我见过有人写postinst时顺手在里面调了一个只在 x86 构建环境里存在的路径装上后脚本直接报错。另外Depends字段也要针对 arm64 生态去写。比如你交叉编译时链接了 libcurl安装依赖是libcurl4不是libcurl4-openssl-dev因为-dev是编译时需要的开发包不是运行时的东西。如果目标系统的 dpkg 依赖解析看到Depends里写了一个不存在的包名安装会失败即使包本身是好的。总结一下这个误区的本质交叉编译只是解决了“二进制从哪来”的问题deb 包作为安装单元它的元数据、脚本、依赖关系仍然是一整套要在目标架构上成立的逻辑。把这两半都做好才叫真的“打成包了”。4. 在 Windows 上可复现的三套打包流水线4.1 三套方案怎么选综合前面踩的坑我最终整理出三套方案。按需求不同选方案适用场景核心思路A只打包已有二进制你手里已经有一个编译好的 arm64 可执行文件只需要套上 deb 壳x86 容器里直接dpkg-deb --buildB交叉编译 打包依赖简单或者你愿意处理 multiarch 库路径x86 容器里用aarch64-linux-gnu-gcc编译再打包CQEMU 模拟 arm64 环境原生编译 打包依赖复杂不想折腾交叉编译环境docker run --platform linux/arm64在 arm64 容器里原生编译并打包以我内部工具的经验程序只调 libc依赖很少方案 B 最合适一旦依赖 curl、openssl、多个第三方库我会直接选方案 C省下的时间远超模拟带来的开销。4.2 目录结构和 control 文件无论哪个方案最终打包时目录结构都是一样的。下面是一个最小可用的例子工具名叫armdiagarmdiag/ ├── DEBIAN/ │ ├── control │ └── md5sums # 可选可稍后生成 └── usr/ └── bin/ └── armdiagDEBIAN/control文件内容Package: armdiag Version: 1.0.0 Section: utils Priority: optional Architecture: arm64 Depends: libc6 ( 2.31) Maintainer: Your Name youexample.com Description: A small diagnostic tool for ARM64 devices4.3 打包命令和 Windows 权限坑用方案 B 举例完整流程是这样。在 Docker 容器里安装交叉编译工具链和打包工具apt-get update apt-get install -y gcc-aarch64-linux-gnu dpkg-dev然后在容器里编译并整理目录aarch64-linux-gnu-gcc -o /work/armdiag/usr/bin/armdiag /work/src/armdiag.c chmod 0755 /work/armdiag/usr/bin/armdiag最后打包cd /work dpkg-deb --build --root-owner-group armdiag armdiag_1.0.0_arm64.deb从 Windows 侧直接调用的完整 PowerShell 命令类似docker run --rm -v ${PWD}:/work -w /work debian:bookworm bash -c apt-get update apt-get install -y gcc-aarch64-linux-gnu dpkg-dev aarch64-linux-gnu-gcc -o /work/armdiag/usr/bin/armdiag /work/src/armdiag.c chmod 0755 /work/armdiag/usr/bin/armdiag dpkg-deb --build --root-owner-group /work/armdiag /work/armdiag_1.0.0_arm64.deb这里有两个我必须强调的坑。第一个是权限位。Docker Desktop 挂载 Windows 目录时文件权限经常是“看起来可读可写但可执行位不确定”的状态。如果你在容器里编译出来的二进制落在 Windows 挂载目录里再基于这个目录打包包内/usr/bin/armdiag的权限很可能变成0644安装后不可执行。解决办法就是我上面写的打包前在容器内显式chmod 0755并且用--root-owner-group让包内所有文件统一属于root:root避免 owner 混乱。第二个是不要在 Windows 侧用 tar 或 7-Zip 手动拼 deb。虽然 deb 的容器格式简单但 control 部分的压缩方式、字段校验、md5sums生成都有讲究dpkg-deb会替你处理这些手动拼包很容易拼出一个 dpkg 不认的坏包。5. 不碰开发板怎么从 Windows 验证 arm64 deb 合格5.1 静态检查三连打包完成后不要急着把包发给别人。哪怕没有 arm64 开发板在手上也能在 Windows 上完成大部分验证。第一步是静态检查dpkg-deb --info armdiag_1.0.0_arm64.deb dpkg-deb --contents armdiag_1.0.0_arm64.deb第一条看元数据和依赖第二条看文件清单。然后解开包用file看二进制本身的架构dpkg-deb -x armdiag_1.0.0_arm64.deb /tmp/armdiag-extracted file /tmp/armdiag-extracted/usr/bin/armdiag readelf -h /tmp/armdiag-extracted/usr/bin/armdiag | grep Machine正常输出应该是ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1 Machine: AArch64如果输出是x86-64那说明前面又回到误区一了。5.2 在 arm64 模拟容器里真实跑一遍静态检查只能说明“这个包看起来是 arm64 的”不能说明“装上去能跑”。更进一步的验证是在 Windows 上直接启动一个 arm64 的 Debian 容器把包装进去试试。Docker 在 x86 的 Windows 宿主机上运行 arm64 镜像时需要 QEMU 的用户态模拟。如果你的 Docker Desktop 版本较新通常直接支持如果遇到exec format error先执行一次注册命令docker run --rm --privileged multiarch/qemu-user-static --reset -p yes然后跑一个 arm64 容器在里面安装并运行刚打好的包docker run --platform linux/arm64 --rm -v ${PWD}:/work -w /work debian:bookworm bash -c dpkg -i armdiag_1.0.0_arm64.deb armdiag --version这一步非常接近真实设备上的行为因为它是在真正的 arm64 Debian 用户态环境里跑的dpkg 校验、依赖解析、二进制加载都是 arm64 语义。和真实板子的差异主要在性能不在正确性。我实际跑的时候发现这个验证还能提前暴露一类问题动态链接的 arm64 二进制在容器里找不到某些系统库。因为debian:bookworm容器是最小化系统如果你依赖的库没有写进Depends容器里没有装dpkg -i会报依赖缺失。这其实就是目标机上会遇到的场景提前在家里炸出来总比在客户现场炸出来好。5.3 让 lintian 帮你做最后一轮把关最后再用lintian这个 Debian 官方质检工具检查一遍包apt-get install -y lintian lintian armdiag_1.0.0_arm64.deb它会对包做大量检查包括 control 字段合法性、文件权限、维护脚本语法、重复文件等。输出里会分Eerror和Wwarning。我的经验是E级别的都要处理都是会直接影响安装或使用的问题W级别里像no-md5sums-control-file这种对内部工具包可以接受但如果要对外发布建议还是补上。生成 md5sums 也不难在打包前执行cd /work/armdiag find usr -type f -exec md5sum {} DEBIAN/md5sums你可能会觉得 lintian 检查有点“形式主义”但它在包里文件权限错误、维护脚本缺解释器这类问题上真的能救命。有一次我打包时忘了给usr/bin/armdiag设可执行权限lintian 直接报了文件权限的可疑警告比我把包拷到板子上才发现要快得多。我现在已经把这一整套流程固化成了项目根目录下的build.ps1从拉取容器镜像、交叉编译、处理权限、打包到静态检查、arm64 容器模拟运行、lintian 验收全部串在一起。以后再有类似“Windows 上出 arm64 包”的需求我只改版本号剩下的都是脚本自由。如果你也正卡在 Windows 出 arm64 的 deb 包这件事上我最真诚的建议是先别着急找 dpkg 的 Windows 移植版老老实实租一个 Debian 容器把前面三个认知纠正过来。一次跑通比任何花里胡哨的技巧都重要。
返回列表