从零开始打包RPM:运维必备技能,让软件部署不再是噩梦
今天聊聊RPM打包,对于很多内网环境,每次部署新软件都是一场灾难。要么依赖包找不到,要么版本冲突,要么就是在不同环境上跑不起来。后来学会了自己打RPM包,这些问题基本就解决了。
RPM包就像是给软件穿了一件标准化的外衣,不管在哪台CentOS或者RHEL机器上,都能优雅地安装和卸载。今天就跟大家分享一下,怎么从头开始制作一个RPM包,相信我,掌握了这个技能,你的运维生涯会轻松很多。
RPM包的基本概念
RPM全称是Red Hat Package Manager,虽然名字里有Red Hat,但现在几乎所有基于Red Hat的发行版都在用,包括CentOS、Fedora、SUSE等等。
一个RPM包其实就是一个压缩文件,里面包含了:
- 要安装的文件
- 安装脚本
- 依赖关系信息
- 软件描述信息
比如你安装nginx的时候,RPM包会告诉系统需要哪些依赖库,安装到哪些目录,需要创建什么用户,启动什么服务等等。
我记得以前手动部署一个Java应用,光是配置环境就要半天,现在打成RPM包,一条yum install命令就搞定了。这就是标准化的威力。
搭建RPM构建环境
想要制作RPM包,首先得搭建构建环境。我一般会专门准备一台虚拟机来做这个事情,避免污染生产环境。
安装必要工具
yum install -y rpm-build rpmdevtools rpmlint
这几个包的作用:
- rpm-build:核心构建工具
- rpmdevtools:提供一些便利脚本
- rpmlint:检查RPM包质量的工具
创建构建目录结构
rpmdev-setuptree
这个命令会在你的家目录下创建rpmbuild目录,结构是这样的:
~/rpmbuild/
├── BUILD/ # 编译过程中的临时目录
├── RPMS/ # 生成的RPM包存放目录
├── SOURCES/ # 源码和补丁文件
├── SPECS/ # spec文件存放目录
└── SRPMS/ # 源码RPM包存放目录
这个目录结构是标准的,不要随意改动。我刚开始的时候总想按自己的习惯来组织,结果各种报错。
SPEC文件详解
SPEC文件是RPM包的核心,它告诉rpmbuild工具怎么构建包。我们来看一个实际的例子,假设要打包一个简单的Shell脚本工具。
基本信息段
Name: my-backup-tool
Version: 1.0.0
Release: 1%{?dist}
Summary: A simple backup tool for system administrators
License: MIT
URL: https://github.com/mycompany/backup-tool
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
Requires: bash >= 4.0, rsync, tar
%description
This is a simple backup tool that helps system administrators
to backup important files and directories automatically.
It supports incremental backup and compression.
这里有几个要注意的点:
- Name必须是小写,不能有空格
- Version遵循语义化版本规范
- Release通常从1开始,每次重新打包就递增
- BuildArch设为noarch表示不依赖特定架构
准备阶段
%prep
%setup -q
# 这里可以应用补丁
# %patch0 -p1
%prep段主要是解压源码包,%setup -q会自动解压Source0指定的文件。
构建阶段
%build
# 对于脚本类软件,通常不需要编译
# 如果是C/C++程序,这里会有make命令
echo "Nothing to build for shell scripts"
安装阶段
%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local/bin
mkdir -p $RPM_BUILD_ROOT/etc/backup-tool
mkdir -p $RPM_BUILD_ROOT/var/log/backup-tool
# 安装主程序
install -m 755 backup-tool.sh $RPM_BUILD_ROOT/usr/local/bin/backup-tool
# 安装配置文件
install -m 644 backup-tool.conf $RPM_BUILD_ROOT/etc/backup-tool/
这个阶段要把文件复制到$RPM_BUILD_ROOT目录下,这个目录模拟了真实的文件系统结构。
文件列表
%files
%defattr(-,root,root,-)
/usr/local/bin/backup-tool
%config(noreplace) /etc/backup-tool/backup-tool.conf
%dir /var/log/backup-tool
%files段列出了包中包含的所有文件。%config(noreplace)表示这是配置文件,升级时不会覆盖用户的修改。
安装前后脚本
%pre
# 安装前执行
getent group backup >/dev/null || groupadd -r backup
getent passwd backup >/dev/null || useradd -r -g backup -d /var/lib/backup -s /sbin/nologin backup
%post
# 安装后执行
systemctl daemon-reload
systemctl enable backup-tool.service
%preun
# 卸载前执行
if [ $1 -eq 0 ]; then
systemctl stop backup-tool.service
systemctl disable backup-tool.service
fi
%postun
# 卸载后执行
systemctl daemon-reload
if [ $1 -eq 0 ]; then
userdel backup 2>/dev/null || true
groupdel backup 2>/dev/null || true
fi
这些脚本很重要,特别是涉及到系统服务的时候。我见过很多人忽略这些脚本,结果卸载包的时候留下一堆垃圾。
实战:打包一个Web应用
理论说完了,我们来个实际例子。假设要打包一个基于Node.js的Web应用。
准备源码包
首先要把源码打包成tar.gz格式:
cd /path/to/your/project
tar czf ~/rpmbuild/SOURCES/my-webapp-1.0.0.tar.gz .
编写SPEC文件
Name: my-webapp
Version: 1.0.0
Release: 1%{?dist}
Summary: My awesome web application
License: MIT
URL: https://github.com/mycompany/webapp
Source0: %{name}-%{version}.tar.gz
Requires: nodejs >= 12.0, npm
BuildRequires: nodejs, npm
%description
This is my awesome web application built with Node.js.
It provides REST API services for business operations.
%prep
%setup -q
%build
npm install --production
%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/opt/my-webapp
mkdir -p $RPM_BUILD_ROOT/etc/systemd/system
mkdir -p $RPM_BUILD_ROOT/var/log/my-webapp
# 复制应用文件
cp -r * $RPM_BUILD_ROOT/opt/my-webapp/
# 安装systemd服务文件
cat > $RPM_BUILD_ROOT/etc/systemd/system/my-webapp.service << 'EOF'
[Unit]
Description=My Web Application
After=network.target
[Service]
Type=simple
User=webapp
WorkingDirectory=/opt/my-webapp
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
%files
%defattr(-,webapp,webapp,-)
/opt/my-webapp
%attr(644,root,root) /etc/systemd/system/my-webapp.service
%dir %attr(755,webapp,webapp) /var/log/my-webapp
%pre
getent group webapp >/dev/null || groupadd -r webapp
getent passwd webapp >/dev/null || useradd -r -g webapp -d /opt/my-webapp -s /sbin/nologin webapp
%post
systemctl daemon-reload
systemctl enable my-webapp.service
systemctl start my-webapp.service
%preun
if [ $1 -eq 0 ]; then
systemctl stop my-webapp.service
systemctl disable my-webapp.service
fi
%postun
systemctl daemon-reload
if [ $1 -eq 0 ]; then
userdel webapp 2>/dev/null || true
groupdel webapp 2>/dev/null || true
fi
%changelog
* Wed Dec 13 2023 Your Name <your.email@company.com> - 1.0.0-1
- Initial package
构建RPM包
cd ~/rpmbuild
rpmbuild -ba SPECS/my-webapp.spec
如果一切顺利,你会在RPMS/noarch/目录下看到生成的RPM包。
常见问题和解决方案
在实际打包过程中,经常会遇到各种问题。我把常见的几个列出来:
依赖问题
最常见的就是依赖包找不到:
error: Failed dependencies:
some-package >= 1.0 is needed by my-webapp-1.0.0-1.noarch
解决方法是检查Requires字段,确保依赖包名称正确。有时候包名和你想的不一样,可以用这个命令查找:
yum provides */some-file
rpm -qf /path/to/file
文件权限问题
经常会遇到这样的错误:
error: File not found: /builddir/build/BUILDROOT/my-webapp-1.0.0-1.el7.x86_64/some/file
这通常是%files段中列出的文件在%install阶段没有正确安装。检查路径是否正确,权限是否设置对了。
脚本执行失败
%pre、%post等脚本如果执行失败,会导致安装失败。我的建议是:
- 所有命令都要考虑失败的情况
- 使用条件判断,比如
[ -f /some/file ] && do_something
- 在脚本末尾加上
exit 0
确保返回成功
循环依赖
有时候会遇到包A依赖包B,包B又依赖包A的情况。这种问题比较复杂,通常需要重新设计包的结构,把公共部分提取出来。
高级技巧和最佳实践
使用宏定义
SPEC文件支持宏定义,可以让配置更灵活:
%define app_user webapp
%define app_dir /opt/%{name}
# 然后在其他地方使用
User=%{app_user}
WorkingDirectory=%{app_dir}
条件构建
有时候需要根据不同的发行版或架构进行条件构建:
%if 0%{?rhel} >= 7
Requires: systemd
%else
Requires: upstart
%endif
子包
一个SPEC文件可以生成多个RPM包:
%package devel
Summary: Development files for %{name}
Requires: %{name} = %{version}-%{release}
%description devel
This package contains development files for %{name}.
%files devel
/usr/include/%{name}/
版本管理
我建议在git仓库中管理SPEC文件,每次发布新版本时:
# 更新版本号
sed -i 's/Version:.*/Version: 1.0.1/' my-webapp.spec
# 更新changelog
cat >> my-webapp.spec << EOF
* $(date '+%a %b %d %Y') $(git config user.name) <$(git config user.email)> - 1.0.1-1
- Bug fixes and improvements
EOF
自动化构建
可以写个简单的脚本来自动化构建过程:
#!/bin/bash
# build-rpm.sh
SPEC_FILE="$1"
if [ -z "$SPEC_FILE" ]; then
echo "Usage: $0 <spec-file>"
exit 1
fi
# 检查SPEC文件语法
rpmlint "$SPEC_FILE"
if [ $? -ne 0 ]; then
echo "SPEC file has errors"
exit 1
fi
# 构建RPM包
rpmbuild -ba "$SPEC_FILE"
# 检查生成的RPM包
find ~/rpmbuild/RPMS -name "*.rpm" -exec rpmlint {} \;
测试和质量保证
打包完成后,一定要测试。我一般会在干净的虚拟机上测试安装:
基本安装测试
# 安装
yum localinstall my-webapp-1.0.0-1.noarch.rpm
# 检查服务状态
systemctl status my-webapp
# 检查文件是否正确安装
ls -la /opt/my-webapp
ls -la /var/log/my-webapp
# 卸载测试
yum remove my-webapp
# 检查是否有残留文件
find / -name "*webapp*" 2>/dev/null
升级测试
这个很重要,要确保从旧版本升级到新版本不会出问题:
# 安装旧版本
yum localinstall my-webapp-1.0.0-1.noarch.rpm
# 修改一些配置文件
echo "test config" >> /etc/my-webapp/config.conf
# 升级到新版本
yum localinstall my-webapp-1.0.1-1.noarch.rpm
# 检查配置文件是否保留
cat /etc/my-webapp/config.conf
依赖测试
在最小化安装的系统上测试,确保依赖关系正确:
# 在干净的CentOS minimal上
yum localinstall my-webapp-1.0.0-1.noarch.rpm
如果缺少依赖,yum会提示你安装。
发布和分发
RPM包制作完成后,就要考虑怎么分发了。
本地YUM仓库
可以搭建一个本地的YUM仓库:
# 创建仓库目录
mkdir -p /var/www/html/repo
# 复制RPM包
cp ~/rpmbuild/RPMS/noarch/*.rpm /var/www/html/repo/
# 创建仓库元数据
createrepo /var/www/html/repo/
# 配置nginx或apache提供HTTP服务
然后在客户端配置YUM源:
cat > /etc/yum.repos.d/local.repo << EOF
[local]
name=Local Repository
baseurl=http://your-server/repo/
enabled=1
gpgcheck=0
EOF
私有仓库
对于企业环境,我推荐使用Nexus或者Artifactory(下一期来讲讲他们两个!!!)这样的仓库管理工具。它们提供了更好的权限控制和版本管理功能。
签名验证
生产环境的RPM包最好要签名:
# 生成GPG密钥
gpg --gen-key
# 签名RPM包
rpm --addsign my-webapp-1.0.0-1.noarch.rpm
# 导出公钥
gpg --export -a "Your Name" > RPM-GPG-KEY-yourname
总结
RPM打包确实是个技术活,但掌握了之后真的能大大提高工作效率。从手动部署到一键安装,从混乱的依赖关系到标准化的包管理,这个转变是质的飞跃。
如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!
公众号:运维躬行录
个人博客:躬行笔记