RPM(Red Hat Package Manager)是 Linux 系统中最主流的软件包管理工具之一,其优势在于用户只需一条命令即可安装预编译的软件。然而,自行构建 RPM 包却颇具挑战——依赖环境复杂、操作步骤繁多,稍有不慎就会出错。本文介绍一种巧妙的解决方案:利用 Docker 容器技术构建可跨平台使用的 RPM 包,使打包流程变得清晰、可重复且易于移植。
我们一直期望能够脱离持续集成(CI)工具,手动生成 RPM 包,以便在部署前验证安装是否正常并执行冒烟测试。而在我们的 CI 流程中,Docker 几乎无所不能,那么能否将 Docker 镜像与 RPM 打包整合在一起?理想情况下,构建 RPM 包的过程本质上就是构建 Docker 镜像:RPM 包的 %prep 阶段在特制的 Docker 镜像中高效完成,最终生成的 RPM 包原封不动地返回宿主机。这样一来,打包环境便实现了封闭且可复现,为 CentOS、Fedora、RHEL 乃至其他 Linux 发行版构建 RPM 包都变得异常便捷。
当然,并非没有其他变通方案,例如使用 chroot 环境。然而,如果在 RPM 构建过程中直接内建容器化机制(通过 chroot、Docker 或其他容器技术抽象的系统来完成打包),无疑是更加优雅的途径。由于该项目尚未完全落地,本文先验证一个核心思路:快速构建一个包含必要依赖的镜像。
该项目使用 PBR 自动生成版本号,其整体流程如下:首先在 build 目录下生成 tarball 并提取版本号,随后修改 spec 文件中的版本号,再利用新的 tar 包和 spec 文件构建 Docker 镜像。最后运行容器,将卷挂载至本地目录,待容器内的 start.sh 执行完毕后,RPM 包即可获得。
start.sh 的逻辑非常简洁:构建完 RPM 后,以 root 权限将其复制到卷目录中,宿主机即可从 output 目录获取生成的文件。此处并未使用对象存储等中间方案,因为在 CI 流程中,简单的本地拷贝已完全满足需求。
目录 SPECS/project.spec 与 SOURCES/* 存放了标准 RPM 包所需的 spec 文件、源码及补丁文件。唯一需要留意的是正确定义 %define_version 宏,并在 spec 文件中正确引用。下面展示几个关键脚本,尽管不能直接拿来使用,但足以让你领略这一构建思路的精妙之处。
以下为主构建脚本 build.sh,可直接从 CI 环境中调用:
#!/bin/bash
set -exf
PROJECT=myproject
CURDIR=$(dirname $(readlink -f $0))
TOPDIR=$(git rev-parse --show-topklevel 2>/dev/null)
rm -rf ${CURDIR}/.build/rpm
mkdir -p ${CURDIR}/.build/rpm/{BUILD,SRPMS,SPECS,RPMS/noarch}
cp -r ${CURDIR}/SOURCES ${CURDIR}/.build/rpm
pushd ${TOPDIR} >/dev/null
python setup.py sdist --dist-dir ${CURDIR}/.build/rpm/SOURCES/
SALADIER_VERSION=$(sed -n '/^Version/ { s/.* //; p}' ${PROJECT}.egg-info/PKG-INFO)
popd >/dev/null
sed -e "s/%define _version.*/%define _version ${SALADIER_VERSION}/" ${CURDIR}/SPECS/${MYROJECT}.spec > \
${CURDIR}/.build/rpm/SPECS/${MYPROJECT}.spec
docker build -t chmouel/buildrpm ${CURDIR}
docker run -v $CURDIR/.build:/data -it chmouel/buildrpm
if [[ -n ${ARTIFACT_DIR} ]];then
rm -rf ${ARTIFACT_DIR}/rpm
cp -a ${CURDIR}/.build/output ${ARTIFACT_DIR}/rpm
fi
接下来是 Dockerfile,其中包含一系列 Docker 缓存优化策略:
FROM fedora:21
MAINTAINER Chmouel Boudjnah
RUN yum -y groupinstall 'Development Tools'
RUN yum -y install fedora-packager
RUN yum -y install yum-utils
RUN yum -y install sudo
RUN sed -i.bak -n -e '/^Defaults.*requiretty/ { s/^/# /;};/^%wheel.*ALL$/ { s/^/# / ;} ;/^#.*wheel.*NOPASSWD/ { s/^#[ ]*//;};p' /etc/sudoers
RUN yum install -y https://rdo.fedorapeople.org/rdo-release.rpm
# 利用缓存优化:把spec文件提前放进去,避免每次构建都重跑builddep
ADD SPECS/project.spec /tmp/
RUN yum-builddep -y /tmp/project.spec
ADD bin/start.sh /start.sh
RUN useradd -s /bin/bash -G adm,wheel,systemd-journal -m rpm
WORKDIR /home/rpm
CMD /start.sh
ADD .build/rpm/ /home/rpm/rpmbuild/
RUN chown -R rpm: /home/rpm
USER rpm
最后是容器内部执行的启动脚本 start.sh:
#!/bin/bash
# script run inside the container
rpmbuild -ba rpmbuild/SPECS/project.spec || exit 1
[[ -d /data ]] || exit 0
sudo rm -rf /data/output
sudo cp -a rpmbuild/RPMS/noarch /data/output
这些脚本可能无法在你的环境中直接运行,但核心思路已清晰呈现:利用 Docker 将 RPM 打包环境彻底隔离,实现纯净且易于移植的构建流程,绝对值得一试。
