CentOS内网离线安装stress与CPU/内存/IO压测基线

发布时间:2026/10/29 2:26:18
CentOS内网离线安装stress与CPU/内存/IO压测基线

简介:面向需要在 CentOS 等 Linux 环境中开展离线压力测试的运维与开发人员,这套资料围绕 stress-1.0.4 的部署与使用整理,重点解决无外网环境下压测工具及相关依赖难以安装的问题,也适合性能测试初学者熟悉 CPU、内存、IO 等负载模拟场景。压缩包共 43 个文件,约 46.66MB,包含 stress 源码包、configure/install 等编译构建脚本、readme 与说明文档,以及 gcc-g++ 等 11 个 rpm 依赖包,并附带 sar 命令相关材料,便于完成离线安装、依赖补齐和基础性能采样。已有 2598 人学习下载。读者可据此获得较完整的 stress 编译安装素材、依赖组件和排错参考,用于搭建本地压测环境、验证系统负载表现并辅助日常性能排查。

1. 内网机器上没有 stress:先看清离线安装这件事的边界

linux 上做压力测试,stress 大概是最省事的那一类工具:一条命令就能把 CPU、内存、IO 里的某一项或几项压满,跑完自己退出,不留垃圾。可一旦落到 centos 的内网机器上——没有外网源、没有 EPEL、连 yum 都只能指向一个空仓库——stress 的离线安装就变成了一道依赖解析题:包从哪来、依赖闭包怎么算、glibc 版本对不对得上、装完怎么验证它真的在压而不是空转。这篇写的就是这条链路,从选包到跑出可复现的基线。适合手上有一批不能出网、又要交付性能数据的机器的人;也适合第一次给 centos 做离线 rpm 交付、被 Failed dependencies 卡过半天的人。

2. 选型与依赖闭包:stress、stress-ng 与 glibc 的三方对齐

离线安装最容易翻车的地方不在安装命令,而在“包从哪台机器上拿”。同一句rpm -ivh,包来自同版本机器就是三秒钟的事,来自高一个版本的机器就是一堆GLIBC_2.xx not found。所以顺序应该是:先定目标机的系统底盘,再定用哪个工具,最后才算依赖。

2.1 先摸清目标机的 glibc 与发行版分支

在目标机上跑这几条,把结果记下来,后面挑包全靠它:

cat /etc/centos-release # 发行版与次版本,例如 7.9 / 8.5 / Stream 9 uname -m # x86_64 还是 aarch64,架构必须一致 getconf GNU_LIBC_VERSION # glibc 版本,EL7 是 2.17,EL8 是 2.28,EL9 是 2.34 rpm -q glibc glibc-common # 确认 glibc 包没有被替换过 rpm -qa | grep -E '^(epel|stress)' || true # 看看是不是已经装过 EPEL 或旧版 stress

getconf GNU_LIBC_VERSION这一条最值得单独看。EL7 的 glibc 是 2.17,EL8 是 2.28,两者符号版本不兼容,从 EL8 上搬过来的 rpm 在 EL7 上会直接报version 'GLIBC_2.28' not found。血泪经验是:不要相信“都是 centos,应该差不多”,次版本号差一位都可能让依赖链断掉。取包机器的判据只有三条——同发行版大版本、同架构、同 glibc 分支;三条里缺一条,就去源码编译那条路(见 3.3)。

2.2 依赖闭包怎么算:用一台联网的同版本机器做预演

依赖闭包的意思是:stress 这个 rpm 自己声明的 Requires,加上它依赖的包再往上依赖的所有东西。手工一个个看太蠢,用工具拉。

# 在一台能出网、且与目标机同版本的机器上执行 sudo yum install -y yum-utils epel-release mkdir -p /tmp/pkg # 方式一:只拉 stress 及其解析出的依赖 yumdownloader --resolve --destdir=/tmp/pkg stress # 方式二:拉全量依赖树,适合之后要给几十台机器用 repotrack -a x86_64 -p /tmp/pkg stress stress-ng ls -l /tmp/pkg | wc -l

--resolve会按当前机器的已装包状态做补集运算:机器上已经有的库不下载。这正是它的坑——如果取包机器是完整安装、目标机是最小化安装,补集就不一样了,装到目标机上照样缺依赖。所以给内网做交付,我一般直接上repotrack,它不管你有没有装,把整棵树的 rpm 全拉下来,包多几个但省掉第二轮返工。-a x86_64指定架构,-p指定输出目录。

EL8 以上没有yumdownloader的话换 dnf 版本:

sudo dnf install -y 'dnf-command(download)' dnf download --resolve --alldeps --destdir=/tmp/pkg stress stress-ng

--alldeps是关键,不加它只下载主包不下载依赖。

2.3 包完整性、签名与目录规划

包拿回来别急着拷,先在取包机器上过一遍校验:

rpm -K /tmp/pkg/*.rpm # 校验摘要与签名,输出 digests signatures OK 才算过 rpm -qpR /tmp/pkg/stress-*.rpm | sort # 打印该包声明的全部依赖,人工过一眼 rpm -qpl /tmp/pkg/stress-*.rpm # 看它到底往系统里写哪些文件

rpm -qpR的输出值得看,因为这是目标机上rpm -ivh报错时唯一的对照依据。如果输出里出现rpmlib(PayloadIsZstd)之类的条目,说明包用了 zstd 压缩,EL7 的 rpm 解不开,必须换包——这个问题在跨大版本搬包时非常常见,报错却只写一句含糊的依赖失败。

目录我习惯这样规划,交付时整包 tar 走,目标机解压即用:

路径用途说明
/opt/offline/stress/rpms原始 rpm 包只读,保留原名
/opt/offline/stress/repocreaterepo 生成的本地源由 rpms 复制而来
/opt/offline/stress/scripts安装与压测脚本与包解耦,方便单独更新
mkdir -p /opt/offline/stress/{rpms,repo,scripts} cp /tmp/pkg/*.rpm /opt/offline/stress/rpms/ tar czf stress-offline-$(date +%Y%m%d).tar.gz -C /opt/offline stress sha256sum stress-offline-*.tar.gz > stress-offline-$(date +%Y%m%d).tar.gz.sha256

带上 sha256 是有实际意义的:内网靠 U 盘或跳板机传输,文件截断的概率比想象中高,解压出来的 rpm 没报错但rpm -K挂掉,排查起来很浪费时间。传完先跑sha256sum -c,五秒钟的事。

3. 三条离线安装路径:rpm 手装、本地仓库与单文件编译

包备齐之后,落地路径有三条。选哪条不看技术高低,看你要装几台机器:一两台用 rpm 手装最快,十台以上做本地源,版本对不上才走编译。

3.1 路径一:rpm 手装,先 --test 再一次性装齐

rpm有个容易被忽略的特性:它只会在“本次命令给出的所有包之间”解析依赖。所以正确姿势是把整个目录的包一次喂给它,而不是一个一个装。

# 1. 先做一次演练,只看依赖缺不缺,不实际写入 rpm -ivh --test /opt/offline/stress/rpms/*.rpm # 2. 演练通过后一次性安装(rpm 会在同批次内互相满足依赖) rpm -ivh /opt/offline/stress/rpms/*.rpm # 3. 如果提示包已存在且版本相同,用替换模式重装 rpm -Uvh --replacepkgs /opt/offline/stress/rpms/stress-*.rpm # 4. 校验安装结果 rpm -q stress rpm -V stress # 无输出表示文件校验全部通过

--test这一步是后悔药,它把依赖检查完整跑一遍但不落盘,报错信息和真装时一模一样。我见过太多人跳过它,结果装到一半失败,系统里留下一半已装、一半未装的库包,回滚比重装还麻烦。

--replacepkgs处理的是“同名同版本已存在”的场景,--oldpackage处理的是“要降级”的场景——后者在离线环境里要慎用,因为降级一个被其他包依赖的库,可能连带把别的服务搞挂。真正缺依赖又不想污染系统时,考虑--nodeps?不建议。--nodeps只是把检查关掉,程序运行时照样error while loading shared libraries,等于把问题从安装期推迟到运行期,更难查。

3.2 路径二:createrepo 做本地源,给多台机器用

机器一多,手装就不划算了,而且 rpm 手装不解决“以后还要装别的包”的问题。做成本地源,后续所有离线安装都走同一条通道。

# 目标机上,先准备好 createrepo(它自己也必须离线装) rpm -ivh /opt/offline/stress/rpms/createrepo_c-*.rpm \ /opt/offline/stress/rpms/python3-*.rpm # 依赖按 rpm -qpR 的输出顺序补 # 建源 mkdir -p /opt/offline/stress/repo cp /opt/offline/stress/rpms/*.rpm /opt/offline/stress/repo/ createrepo_c /opt/offline/stress/repo # 写 repo 文件 cat > /etc/yum.repos.d/offline-stress.repo <<'EOF' [offline-stress] name=offline stress packages baseurl=file:///opt/offline/stress/repo enabled=1 gpgcheck=0 EOF # 只启用这一个源来装,避免内网其他坏源拖慢或直接报错 yum --disablerepo='*' --enablerepo=offline-stress install -y stress stress-ng yum --disablerepo='*' --enablerepo=offline-stress list installed | grep -E '^stress'

说明几个参数。baseurl=file://走本地文件系统,不需要 http 服务;如果要多机共享,把 repo 目录放到内网 http 下,baseurl换成http://<内网IP>/stress即可。gpgcheck=0是因为本地包没有导入对应的 GPG key,关掉校验能少一轮报错;对包来源有要求的环境,把 key 一起带过去rpm --import后改回gpgcheck=1。

--disablerepo='*'这个参数看着粗暴,其实是省时间的关键。内网机器上常常残留着指向外网的 repo 配置,yum install会先去尝试连接、超时、重试,一次几十秒,报的错还跟真正的问题无关。

装完别忘验证二进制能不能跑:

which stress stress-ng stress --version stress-ng --version 2>&1 | head -3

3.3 路径三:源码编译,版本对不上时的出路

前面说过,跨大版本搬 rpm 会因为 glibc 符号版本挂掉。这时候源码编译是唯一出路。好消息是 stress 本体极其轻量——它主体就是单个 C 文件,只依赖 libc 和 pthread,离线机器上只要装了 gcc 就能编。

# 前提:有 gcc 与 make。最小化安装的机器从 ISO 挂本地源装 mount -o loop /opt/iso/CentOS-*.iso /mnt cat > /etc/yum.repos.d/local-iso.repo <<'EOF' [local-iso] name=local iso baseurl=file:///mnt enabled=1 gpgcheck=0 EOF yum --disablerepo='*' --enablerepo=local-iso install -y gcc make glibc-devel # 编译安装 tar xf stress-*.tar.gz cd stress-*/ ./configure --prefix=/usr/local make -j"$(nproc)" make install /usr/local/bin/stress --version

如果连 configure 都嫌麻烦,stress 的源码包里通常带着stress.c,直接一句编完:

gcc -O2 -pthread -o /usr/local/bin/stress stress.c /usr/local/bin/stress --version

-O2打开优化让压测循环不至于被编译器拖慢,-pthread必须加,否则链接阶段会报undefined reference to pthread_create。这条路的代价是没有 rpm 数据库记录,rpm -q stress查不到,后面做资产盘点容易漏,所以我在脚本里都会留一行注释写清来源和编译时间。

stress-ng 就别想这条路了。它的依赖面宽得多,libaio、libattr、libbsd、libgcrypt、zlib 都在可选列表里,离线环境凑齐这些开发包的成本远高于直接搬 rpm。真要在低版本系统上用 stress-ng 的高级功能,优先考虑把它跑在容器里或者干脆换一台机器。

4. 参数配方:CPU、内存、IO 三类压测怎么设才测得出问题

装完只是开始。stress 的参数少,但每一个都有“设了等于没设”的写法,压出来的数字漂亮,实际问题一个没暴露。

4.1 CPU 与内存:worker 数和字节数要分开算

先看机器的底:

nproc # 逻辑核数,超线程算两个 free -m # 看 available,不是 free awk '/MemAvailable/{print int($2/1024)" MB"}' /proc/meminfo

CPU 压测简单,worker 数等于逻辑核数就能把调度器的队列压满:

stress --cpu "$(nproc)" --timeout 600s --verbose

内存压测才是容易出事的地方。--vm-bytes是每个 worker的分配量,不是总量,这一点必须在脑子里焊死:

# 2 个 worker,每个 1G,合计约 2G;--vm-hang 0 表示分配并触页后立刻释放,反复制造缺页 stress --vm 2 --vm-bytes 1G --vm-hang 0 --timeout 300s

--vm-hang 0和给一个大值的区别在于压测形态:0 是“不停地 mmap、写页、munmap”,压的是缺页异常和内存分配路径;给 300 就是“分配 1G 常驻 300 秒”,压的是常驻内存对系统的影响。前者容易暴露内存管理路径的问题,后者更接近真实业务的内存占用形态,两个都值得跑。总量控制上,我一般让worker 数 × vm-bytes落在MemAvailable的 70% 到 80%,剩下 20% 留给系统和监控进程——压到 100% 的后果不是“压得更狠”,而是 OOM killer 直接把 stress 或者更重要的进程干掉。

stress-ng 在这块更省心,它支持百分比:

stress-ng --vm 2 --vm-bytes 70% --vm-keep --timeout 300s --metrics-brief

--vm-keep让内存保持映射不释放,--metrics-brief在结束时打印 bogo ops 和实际耗时,是判断“它到底干了多少活”的依据。

4.2 IO 与组合场景:先分清 sync 和真写盘

--io和--hdd是两个完全不同的东西,混用会得出错误结论:

# io worker 只反复调用 sync(),制造的是内核回写压力,本身不产生新数据 stress --io 4 --timeout 120s # hdd worker 真的创建文件、写入、删除,默认落在执行 stress 时的当前目录 mkdir -p /data/stress-tmp && cd /data/stress-tmp stress --hdd 2 --hdd-bytes 1G --timeout 300s

在一台干净的、没有脏页的机器上跑--io,iostat的%util几乎不动,因为sync()没东西可刷。这不是工具坏了,是压测目标选错了。要压磁盘吞吐就用--hdd,并且一定cd到真实块设备上的目录再跑——/tmp在很多镜像里是 tmpfs,写进去的是内存,压出来的是内存带宽不是磁盘。

组合压测用来找资源争抢的拐点:

stress --cpu "$(nproc)" --io 2 --vm 2 --vm-bytes 1G \ --hdd 1 --hdd-bytes 512M --timeout 15m

这一条把四类负载叠在一起,观察的是系统在满负荷下的调度行为——比如 CPU 压满时 IO 的await会不会被拉长,内存压力上来后si/so有没有开始交换。stress-ng 对应写法用--temp-path显式指定落盘目录,比依赖当前目录靠谱:

stress-ng --cpu 4 --hdd 2 --hdd-bytes 2G --temp-path /data/stress-tmp \ --timeout 300s --metrics-brief

4.3 监控侧:让采样窗口和压测窗口对齐

压测不配监控,等于闭着眼睛开车。三个采集器够了:

# 后台起监控,采样次数与压测秒数一致,保证窗口对齐 vmstat 1 600 > /var/tmp/vmstat.log 2>&1 & iostat -x 1 600 > /var/tmp/iostat.log 2>&1 & MPID=$! stress --cpu "$(nproc)" --timeout 600s -v > /var/tmp/stress.log 2>&1 wait $MPID || true

vmstat里重点看四列:r是运行队列长度,CPU 压满时应该接近核数;cs是上下文切换,做 CPU 压测时不应该暴涨,暴涨说明有锁竞争或调度异常;si/so是换入换出,非零就说明内存压过头了;wa是 IO 等待。iostat -x里重点看%util、r/s、w/s、await四列,await突然从个位数跳到几十毫秒,说明队列开始堆积。

事后一定要查 OOM:

dmesg -T | grep -i -E 'out of memory|killed process'

这条命令是黑匣子。stress 被 OOM killer 干掉时,终端上只有一行Killed,看不出是谁干的,dmesg里才有完整的Out of memory: Killed process <pid> (stress)记录,连当时的 meminfo 快照都带着。

5. 避坑与排查:五条现场记录,每条都带现象

5.1 rpm -ivh 报一堆 Failed dependencies

现象:error: Failed dependencies: libaio.so.1()(64bit) is needed by stress-ng-...,一屏能刷十几行。

原因:依赖是在另一台机器上解析的,取包机的已装包状态和目标机不一致;或者用了yumdownloader的默认补集逻辑,把“取包机已装”的库跳过了。

解决:回到 2.2 用repotrack拉全量树,或者直接上本地源让 yum 自己算。临时补一两个库时,用rpm -qpR对照报错清单逐个确认版本,注意()(64bit)后缀说明要 64 位版本。绝对不要用--nodeps绕过去。

5.2 压测跑着跑着进程没了

现象:stress --vm 2 --vm-bytes 4G在 8G 内存的机器上跑到第二分钟,进程消失,终端只留一句Killed。

原因:--vm-bytes是每个 worker 的量,2 × 4G 已经顶到物理内存上限,再加上系统自身占用,OOM killer 必然介入。容器里还要再叠一层 cgroup 限制。

解决:先算总量再跑,控制在MemAvailable的 70% 左右。容器环境额外看cat /sys/fs/cgroup/memory.max(cgroup v2)或/sys/fs/cgroup/memory/memory.limit_in_bytes(v1)。跑完用dmesg -T | grep -i oom确认是不是真的 OOM,别把段错误误判成内存不足。

5.3 压测数字好看,业务却没感觉

现象:stress --io 8跑满,top里 stress 的 CPU 占用很高,但iostat -x的%util几乎不动,磁盘吞吐没变化。

原因:--ioworker 干的事就是反复sync(),没有脏页时它只是空转消耗 CPU,压根不产生磁盘写入。另外如果压测目录是 tmpfs,写的是内存不是盘,iostat自然看不到动静。

解决:压磁盘吞吐换--hdd,并且确认落盘目录是真实块设备:df -hT /data/stress-tmp看到ext4/xfs才算数,看到tmpfs就换目录。要更精确的 IO 模型,用 fio 交叉验证,stress 的--hdd不适合做 IOPS 基准。

5.4 --hdd-bytes 设太大,把宿主机写满

现象:跑完一轮压测,/或/data的可用空间少了一大截,重启也没恢复。

原因:--hdd会在结束时删掉临时文件,但进程被 kill、超时异常、或者机器在压测中途重启,临时文件就留下来了。--hdd-bytes乘上 worker 数就是峰值占用,很容易超过分区余量。

解决:--hdd-bytes不超过目标分区可用空间的 50%,worker 数也别开太多。异常退出后用find /data/stress-tmp -name 'stress.*' -size +100M -delete清残留,跑之前先看一眼df -h养成习惯。

5.5 装完 command not found 或者版本不对

现象:明明rpm -q stress有输出,stress --version却是旧版本;或者which stress找不到。

原因:系统里原本就有同名包,rpm -ivh因为已存在被拒(但很多人没注意到那行报错);或者源码装到了/usr/local/bin,而某些受限 PATH 下找不到;也有可能是 shell 的 hash 缓存还指着旧路径。

解决:按这个顺序查——rpm -q stress看包的版本,type -a stress看 PATH 里所有同名命令,ls -l /usr/bin/stress /usr/local/bin/stress看实际文件,最后hash -r清缓存。要用新版本就用绝对路径调用,别指望 PATH 顺序自动帮你选对。

6. 做成可复现的基线:一个脚本和四个必须看的数

单次压测的价值有限,能复现、能对比的基线才有价值。下面这个脚本我一般在每台新机器交付前跑一遍,参数只有两个:时长和落盘目录。

#!/usr/bin/env bash set -euo pipefail DURATION="${1:-300}" # 压测时长,单位秒 TMP_PATH="${2:-/data/stress-tmp}" # hdd worker 落盘目录,必须是真实块设备 OUT="/var/log/stress-baseline/$(date +%Y%m%d-%H%M%S)" mkdir -p "$OUT" "$TMP_PATH" command -v stress >/dev/null 2>&1 || { echo "stress 未安装,先回到第 3 章"; exit 1; } CPU_N="$(nproc)" AVAIL_MB="$(awk '/MemAvailable/{print int($2/1024)}' /proc/meminfo)" VM_MB="$(( AVAIL_MB * 70 / 100 / 2 ))" # 2 个 vm worker,各占可用内存的 35% # 监控先起,保证采样窗口覆盖整个压测过程 PIDS=() vmstat 1 "$DURATION" > "$OUT/vmstat.log" 2>&1 & PIDS+=("$!") if command -v iostat >/dev/null 2>&1; then iostat -x 1 "$DURATION" > "$OUT/iostat.log" 2>&1 & PIDS+=("$!") else echo "未装 sysstat,跳过 iostat 采样" > "$OUT/iostat.log" fi # 压测本体:CPU 满核 + 内存 70% + 单路 hdd ( cd "$TMP_PATH" && stress --cpu "$CPU_N" --vm 2 --vm-bytes "${VM_MB}M" \ --hdd 1 --hdd-bytes 1G --timeout "${DURATION}s" -v ) > "$OUT/stress.log" 2>&1 for p in "${PIDS[@]}"; do wait "$p" || true; done dmesg -T 2>/dev/null | tail -n 200 > "$OUT/dmesg.log" || true if grep -qiE 'out of memory|killed process' "$OUT/dmesg.log"; then echo "警告:压测期间出现 OOM,把 VM_MB 的系数调小后重跑" fi echo "结果目录:$OUT"

几个关键点。set -euo pipefail让脚本在任一命令失败时立刻停,避免拿着一份不完整的数据做结论。MemAvailable才是可分配内存的真实口径,MemFree会把缓存算成已用,用它算出来的VM_MB会小得离谱,压不出任何压力。监控进程先起、压测后跑、最后逐一wait,是为了让vmstat的采样窗口把整个压测过程包住,事后对齐时间轴不会出现“压测开始前十秒没有数据”的尴尬。dmesg单独落到文件里,出问题时不用回现场。

结果出来后只盯四个数,多一个都别看:

观察项取值方式参考判断
运行队列rvmstat.log的第六列CPU 压测期间接近nproc说明压满;远超核数说明有阻塞
上下文切换csvmstat.log的第十二列CPU 纯计算压测时不应暴涨,暴涨优先怀疑锁竞争
换入换出si/sovmstat.log的第七、八列非零即内存压过头,与 OOM 记录对照看
磁盘awaitiostat.log的await列与空载基线比,翻倍以上说明队列已堆积

判断这一轮压测有没有效,标准很朴素:r要起得来,si/so要压得住,dmesg要干净,stress的退出码要是 0。四个都满足,这份数据才有资格进交付文档;任何一项不满足,先回头查参数,别急着下结论说机器性能不行。

最后留一句自己的教训。早年给一批内网机器做压测,图快用了--nodeps装 rpm,程序能跑起来,压测数据也出得挺漂亮,交付两周后业务方复现时全崩——缺的那个库是 IO 相关模块的,只在特定负载下才被调用,平时根本看不出来。从那以后我每次做离线交付,都强制走一遍rpm -qpR对依赖、--test演练、装完rpm -V校验这三步,宁可多花五分钟。希望帮到你。

本文还有配套的精品资源,点击获取