Ubuntu上fpm多架构打包实战

Ubuntu 上用 fpm 一次打出 deb/rpm 双格式、x86/arm 双架构安装包

需求一句话:手里只有一份「编译好的二进制」,目标客户机器上既有 x86_64 也有 arm64,既要出 deb 也要出 rpm,而且分发时希望每个包上都明确标记架构。本文记录完整方案与踩坑实录。


一、背景与需求

产品是 Java + 本地引擎混合架构的服务,引擎是 C/C++ 编译的原生二进制(必须按 CPU 架构分别编译),而 Java 部分和配置文件是架构无关的。打包前我们拿到的是:

bash 复制代码
1build/2├── engine_x86_64      # x86_64 编译的引擎(15MB)3└── engine_aarch64     # arm64 编译的引擎(15MB)

需求拆开是四件事:

  1. 双格式:deb(Debian/Ubuntu 系)+ rpm(RedHat/CentOS 系)都要;
  2. 双架构:x86_64 和 arm64 都要支持;
  3. 包要带架构标记:实施/分发时按目标机器架构选包,一眼能认出来;
  4. 在 Ubuntu 上完成打包:没有 CentOS/Fedora 打包机。

先给结论:这一切用 fpm 一个工具就能搞定,核心就两件事------搞懂架构命名口径、写一个循环脚本。


二、先搞懂:同一个架构,有三套名字

这是最容易懵的地方。同一个物理架构,在 uname、deb、rpm 三个体系里各有各的叫法:

物理架构 uname -m deb 体系(Debian/Ubuntu) rpm 体系(RedHat/CentOS)
Intel/AMD 64位 x86_64 amd64 x86_64
ARM 64位 aarch64 arm64 aarch64

为什么不对齐? 历史原因:

  • x86-64 指令集由 AMD 率先推出,正式名 AMD64 ,所以 Debian 系用 amd64;RedHat 系直接沿用 uname -m 的输出 x86_64
  • ARM 64 位指令集官方名 AArch64 ,RedHat 系用 aarch64,Debian 系简化成 arm64

坑点 :这些名字是包管理器各自"法定"的,不能混用------deb 里写 x86_64,dpkg 不认识;rpm 里写 amd64,rpm 直接拒绝。所以打包时要做一次映射:

css 复制代码
1# deb 用 Debian 规范名,rpm 用 RPM 规范名2declare -A DEB_ARCH=( [x86]=amd64 [arm]=arm64 )3declare -A RPM_ARCH=( [x86]=x86_64 [arm]=aarch64 )

三、核心设计:运行时按 uname -m 自动选引擎

我们的启动脚本 run.sh 有一个很省事的设计------不写死引擎文件名,运行时用 uname -m

ini 复制代码
1SYSTEM_ARCH=$(uname -m)2# x86 机器上 SYSTEM_ARCH=x86_64,arm 机器上是 aarch643nohup ${BASE_DIR}/bin/core/myapp_${SYSTEM_ARCH} start &

对应地,bin/core/ 下放两个引擎,命名必须uname -m 输出严格一致:

bash 复制代码
1bin/core/2├── myapp_x86_64      # uname -m 输出 x86_64 → 命中3└── myapp_aarch64     # uname -m 输出 aarch64 → 命中

这个设计带来的好处:安装目录里可以同时放两套架构的引擎,装到哪台机器都能跑------也就有了下面两种打包思路。

细节:uname -m 在 32 位系统上会输出 i686 / armv7l。如果引擎只有 64 位版本,32 位机器装不上(deb 的 amd64/arm64 架构标签会在安装时直接拦截),这在分发上是"装不上比装错更安全"。


四、方案抉择:单包双架构 vs 四包标记架构

有了"目录里双引擎共存"的能力,就有两种打包路线:

方案 A:单包双架构(1 个包全平台通吃)

  • deb 用 Architecture: all,rpm 用 Architecture: noarch(架构无关包)
  • 一个包发出去,x86/arm 都能装,运行时各自选引擎

优点 :只维护 1 个安装包,分发极简;缺点:包不体现目标架构,仓库/实施流程如果按架构筛选就不好办。

方案 B:四包标记架构(最终采用)

  • deb × (amd64 / arm64) + rpm × (x86_64 / aarch64) = 4 个包
  • 内容完全相同(都含双引擎),仅架构标签不同
csharp 复制代码
1out/2├── myapp_1.0.0_x86.deb        # Architecture: amd64  → 发给 x86 客户3├── myapp_1.0.0_arm.deb        # Architecture: arm64  → 发给 arm 客户4├── myapp-1.0.0-1.x86_64.rpm   # Architecture: x86_645└── myapp-1.0.0-1.aarch64.rpm  # Architecture: aarch64

优点 :架构标签明确,分发、归档、审计都清晰;缺点:要出 4 个包(但脚本循环一次搞定)。

实际项目中最终选了方案 B------客户现场实施人员更习惯"按机器架构拿对应包",4 个包由脚本自动产出,成本几乎为零。


五、实战:fpm 一键四包

5.1 安装 fpm

复制代码
1sudo apt update2sudo apt install -y ruby ruby-dev rubygems build-essential rpm3sudo gem install fpm

公司内网 gem 可能拉不动,换清华源:

arduino 复制代码
1sudo gem sources --remove https://rubygems.org/2sudo gem sources --add https://mirrors.tuna.tsinghua.edu.cn/rubygems/3sudo gem install fpm

rpm 包也要装------fpm 打 rpm 时依赖系统的 rpmbuild(见踩坑一)。

5.2 素材目录

素材按安装路径组织(装到 /opt/myapp),fpm 打包时 --prefix / + 输入 opt 即可:

arduino 复制代码
1staging/2└── opt/3    └── myapp/4        ├── bin/5        │   ├── core/6        │   │   ├── myapp_x86_647        │   │   ├── myapp_aarch648        │   │   └── myapp.properties9        │   └── lib/          # 词库/数据,架构无关10        ├── config/           # 配置文件11        └── script/12            └── run.sh        # 启动脚本,运行时自动选引擎

5.3 一键打包脚本(核心)

matlab 复制代码
1#!/bin/bash2set -euo pipefail34APP=myapp5VER=1.0.06DESC="myapp by example"7SRC="${1:-$(pwd)}"            # 素材根(含 opt/ 的那层)8OUT="${2:-$SRC/out}"9BASE="$SRC/opt/myapp"1011# 0) 环境:fpm + rpmbuild 分别检查(root 直接执行,普通用户 sudo)12SUDO=""; [ "$(id -u)" -eq 0 ] || SUDO="sudo"13command -v fpm      >/dev/null || { $SUDO apt install -y ruby ruby-dev rubygems build-essential; $SUDO gem install fpm; }14command -v rpmbuild >/dev/null || { $SUDO apt install -y rpm; }   # 注意:独立检查!1516# 1) 校验素材:用 file 确认引擎架构没放反17file "$BASE/bin/core/myapp_x86_64"  | grep -qi "x86-64"  || { echo "x86 引擎不对"; exit 1; }18file "$BASE/bin/core/myapp_aarch64" | grep -qi "aarch64" || { echo "arm 引擎不对"; exit 1; }1920# 2) 权限(Windows 拷来的文件没有执行位,必须补)21chmod 755 "$BASE/bin/core/myapp_"* "$BASE/script/run.sh"22chmod 644 "$BASE/bin/core/myapp.properties" "$BASE/config/"*23sed -i 's/\r$//' "$BASE/script/run.sh"     # 顺手清 CRLF2425# 3) 安装后脚本:赋权/初始化(可选)26cat > "$SRC/postinst" <<'EOF'27#!/bin/bash28set -e29chmod -R 777 /opt/myapp30chmod +x /opt/myapp/bin/core/*31chmod +x /opt/myapp/script/*32echo "myapp 安装成功"33EOF34chmod +x "$SRC/postinst"3536# 4) 打 4 个包:内容相同,仅架构标签不同37mkdir -p "$OUT"38declare -A DEB_ARCH=( [x86]=amd64 [arm]=arm64 )39declare -A RPM_ARCH=( [x86]=x86_64 [arm]=aarch64 )4041for key in x86 arm; do42  fpm -s dir -t deb -a "${DEB_ARCH[$key]}" -n "$APP" -v "$VER" \43      --description "$DESC" \44      -C "$SRC" --prefix / --after-install "$SRC/postinst" \45      -p "$OUT/${APP}_${VER}_${key}.deb" opt46  fpm -s dir -t rpm -a "${RPM_ARCH[$key]}" -n "$APP" -v "$VER" \47      --description "$DESC" \48      -C "$SRC" --prefix / --after-install "$SRC/postinst" \49      -p "$OUT/${APP}-${VER}-1.${RPM_ARCH[$key]}.rpm" opt50done

几个关键参数说明:

fpm 参数 作用
-s dir / `-t deb rpm`
`-a amd64 arm64
-C <dir> --prefix / <dir> 为根,安装到 /(素材里自带 opt/
--after-install postinst 安装后执行脚本(deb 的 postinst / rpm 的 %post)
-p <路径> 指定产物文件名

六、踩坑实录(都是真踩过的)

坑一:Need executable 'rpmbuild' to convert dir to rpm

现象:deb 正常打出,rpm 报错。

根因 :fpm 打 rpm 必须调用系统 rpmbuild。很多教程把 rpm 装进"fpm 不存在才装"的条件里------一旦机器上 fpm 已经装好,整个判断跳过,rpmbuild 就没装上。

修复 :对 rpmbuild独立检查,不要挂在 fpm 的判断里:

javascript 复制代码
1command -v fpm      >/dev/null || { ...装 fpm... }2command -v rpmbuild >/dev/null || { sudo apt install -y rpm; }

坑二:Windows 拷来的文件丢权限 + CRLF

现象 :包打出来了,装完启动脚本报 Permission denied;或 bash 脚本报 $'\r': command not found

根因:素材从 Windows 复制到 Linux,① 文件没有执行位(Windows 无 chmod 概念);② 文本文件带 CRLF 换行,bash 不认。

修复

perl 复制代码
1chmod 755 引擎、脚本2sed -i 's/\r$//' run.sh     # 清 CRLF

坑三:root 环境下 sudo 的兼容

打包机如果是 root 登录,脚本里写死 sudo apt 在个别精简系统上会找不到 sudo。用 id -u 判断统一提权前缀:

bash 复制代码
1SUDO=""; [ "$(id -u)" -eq 0 ] || SUDO="sudo"2$SUDO apt install -y rpm

坑四:gem 安装超时

内网环境 gem install fpm 大概率超时,换清华源即可(见 5.1)。脚本里可以做失败自动切换。


七、打包后的验证

javascript 复制代码
1# 元数据:确认架构标签2dpkg-deb -I myapp_1.0.0_x86.deb | grep Architecture   # 应输出 amd643rpm -qpi myapp-1.0.0-1.aarch64.rpm | grep Architecture # 应输出 aarch6445# 内容:确认两个引擎都在包内6dpkg-deb -c myapp_1.0.0_x86.deb | grep myapp_          # 应看到 x86_64 + aarch647rpm -qpl myapp-1.0.0-1.aarch64.rpm | grep myapp_89# 安装实测(x86 机器装 x86 包)10sudo dpkg -i myapp_1.0.0_x86.deb11/opt/myapp/script/run.sh status1213# arm 无真机时用容器验证14docker run --rm --platform linux/arm64 -v $PWD:/pkg ubuntu:22.04 \15  bash -c "dpkg -i /pkg/myapp_1.0.0_arm.deb && /opt/myapp/script/run.sh status"1617# rpm 用 rockylinux 容器验证18docker run --rm -v $PWD:/pkg rockylinux:9 \19  bash -c "dnf install -y /pkg/myapp-1.0.0-1.x86_64.rpm && /opt/myapp/script/run.sh status"

八、小结

  • 架构命名 :deb 用 amd64/arm64,rpm 用 x86_64/aarch64,各体系法定规范名,不可混用;
  • 一包双架构 :启动脚本用 uname -m 运行时拼引擎文件名,目录里就能同时放两套引擎;
  • 四包 vs 单包:架构无关包(all/noarch)省事,但分发/审计场景下"四包带标签"更清晰,脚本循环成本为零;
  • fpm 要点-a 打标签、--prefix / 配素材自带 opt/--after-install 挂 postinst;
  • 最容易翻车的三件事rpmbuild 独立检查、Windows 文件的执行位与 CRLF、root/sudo 兼容。

最后一句真心话:fpm 本身很简单,难的全是"环境细节"------而这正是写进脚本、一次搞定、永不再踩的价值。

相关推荐
JavaGuide1 小时前
轻量开源版 IDEA 来了!
前端·后端
Json____1 小时前
springboot开发高校选课管理系统:从零构建前后端分离的全栈实践
java·spring boot·后端·毕业设计·毕设·wwwoop.com
站大爷IP2 小时前
Python的列表推导式把我写崩了,原来圆括号和方括号不是一回事
后端
梦在远山后2 小时前
Python 中字符串常用写法
人工智能·后端
geovindu2 小时前
python:HandWriting Recognition using paddlepaddle
开发语言·后端·python·paddlepaddle
步行cgn2 小时前
为何以继承方式引入SpringBoot
java·spring boot·后端
Mr_hou2 小时前
左手.NET右手Node:一个后端开发的双修之路
后端
wno7042 小时前
Spring Boot配合Hibernate Validator参数校验
spring boot·后端·hibernate
astronautyi3 小时前
Go 运行时内存分配与 GC 位图深度剖析
开发语言·后端·golang