幻世域-公会争霸活动网


今天聊聊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包,首先得搭建构建环境。我一般会专门准备一台虚拟机来做这个事情,避免污染生产环境。

安装必要工具

代码语言:javascript复制yum install -y rpm-build rpmdevtools rpmlint这几个包的作用:

• rpm-build:核心构建工具• rpmdevtools:提供一些便利脚本• rpmlint:检查RPM包质量的工具创建构建目录结构

代码语言:javascript复制rpmdev-setuptree这个命令会在你的家目录下创建rpmbuild目录,结构是这样的:

代码语言:javascript复制~/rpmbuild/

├── BUILD/ # 编译过程中的临时目录

├── RPMS/ # 生成的RPM包存放目录

├── SOURCES/ # 源码和补丁文件

├── SPECS/ # spec文件存放目录

└── SRPMS/ # 源码RPM包存放目录这个目录结构是标准的,不要随意改动。我刚开始的时候总想按自己的习惯来组织,结果各种报错。

SPEC文件详解SPEC文件是RPM包的核心,它告诉rpmbuild工具怎么构建包。我们来看一个实际的例子,假设要打包一个简单的Shell脚本工具。

基本信息段

代码语言:javascript复制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表示不依赖特定架构准备阶段

代码语言:javascript复制%prep

%setup -q

# 这里可以应用补丁

# %patch0 -p1%prep段主要是解压源码包,%setup -q会自动解压Source0指定的文件。

构建阶段

代码语言:javascript复制%build

# 对于脚本类软件,通常不需要编译

# 如果是C/C++程序,这里会有make命令

echo "Nothing to build for shell scripts"安装阶段

代码语言:javascript复制%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目录下,这个目录模拟了真实的文件系统结构。

文件列表

代码语言:javascript复制%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)表示这是配置文件,升级时不会覆盖用户的修改。

安装前后脚本

代码语言:javascript复制%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格式:

代码语言:javascript复制cd /path/to/your/project

tar czf ~/rpmbuild/SOURCES/my-webapp-1.0.0.tar.gz .编写SPEC文件

代码语言:javascript复制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 - 1.0.0-1

- Initial package构建RPM包

代码语言:javascript复制cd ~/rpmbuild

rpmbuild -ba SPECS/my-webapp.spec如果一切顺利,你会在RPMS/noarch/目录下看到生成的RPM包。

常见问题和解决方案在实际打包过程中,经常会遇到各种问题。我把常见的几个列出来:

依赖问题

最常见的就是依赖包找不到:

代码语言:javascript复制error: Failed dependencies:

some-package >= 1.0 is needed by my-webapp-1.0.0-1.noarch解决方法是检查Requires字段,确保依赖包名称正确。有时候包名和你想的不一样,可以用这个命令查找:

代码语言:javascript复制yum provides */some-file

rpm -qf /path/to/file文件权限问题

经常会遇到这样的错误:

代码语言:javascript复制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文件支持宏定义,可以让配置更灵活:

代码语言:javascript复制%define app_user webapp

%define app_dir /opt/%{name}

# 然后在其他地方使用

User=%{app_user}

WorkingDirectory=%{app_dir}条件构建

有时候需要根据不同的发行版或架构进行条件构建:

代码语言:javascript复制%if 0%{?rhel} >= 7

Requires: systemd

%else

Requires: upstart

%endif子包

一个SPEC文件可以生成多个RPM包:

代码语言:javascript复制%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文件,每次发布新版本时:

代码语言:javascript复制# 更新版本号

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自动化构建

可以写个简单的脚本来自动化构建过程:

代码语言:javascript复制#!/bin/bash

# build-rpm.sh

SPEC_FILE="$1"

if [ -z "$SPEC_FILE" ]; then

echo "Usage: $0 "

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 {} \;测试和质量保证打包完成后,一定要测试。我一般会在干净的虚拟机上测试安装:

基本安装测试

代码语言:javascript复制# 安装

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升级测试

这个很重要,要确保从旧版本升级到新版本不会出问题:

代码语言:javascript复制# 安装旧版本

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依赖测试

在最小化安装的系统上测试,确保依赖关系正确:

代码语言:javascript复制# 在干净的CentOS minimal上

yum localinstall my-webapp-1.0.0-1.noarch.rpm如果缺少依赖,yum会提示你安装。

发布和分发RPM包制作完成后,就要考虑怎么分发了。

本地YUM仓库

可以搭建一个本地的YUM仓库:

代码语言:javascript复制# 创建仓库目录

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源:

代码语言:javascript复制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包最好要签名:

代码语言:javascript复制# 生成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打包确实是个技术活,但掌握了之后真的能大大提高工作效率。从手动部署到一键安装,从混乱的依赖关系到标准化的包管理,这个转变是质的飞跃。

如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!

公众号:运维躬行录

个人博客:躬行笔记