☰
openEuler 22 离线部署 RabbitMQ 3.8:RPM 依赖闭环与集群配置实践
2026/10/14 7:28:48 网站建设 项目流程

1. 项目概述

搞过企业级部署的同学都懂,内网生产环境基本默认与外网隔离,不能随时yum update。最近接了一个新需求,需要在 openEuler 22(兼容 CentOS 8 上下环境)上离线部署 RabbitMQ,而且机器不只一台,后面还要批量复制到别的节点。RabbitMQ 的下游业务方用的是 3.8 系列,无法直接升级到 6.x,所以必须严格锁版本安装。

这个项目最终的成果就是一套完整的离线安装方案:在联网的跳板机或虚拟机上下载好全部依赖,整理成本地仓库,再拷贝到目标服务器上安装部署。整个流程走完,单台机器从零到可用的时间可以控制在 20 分钟以内(前提是依赖包和 RPM 包已经准备齐全)。如果你是运维、开发自建环境、或者正在做国产化系统适配验证,这篇内容会非常对味。

先聊清楚一个概念:openEuler 22.03 很多底层 API 和 RHEL/CentOS 8 高度一致,所以大量 RPM 包可以直接复用红帽系的编译产物。但是直接混装经常碰到一个坑——CentOS 8 的源里并不包含 RabbitMQ,也不一定带对版本的 Erlang,比如erlang版本如果不落在 RabbitMQ 要求的区间,安装能过,启动就是报节点崩溃,后台日志一堆function_clause或者undef,问题非常难查。所以离线安装的本质工作不是把安装包搬运过去这么简单,而是要把“版本匹配”和“依赖闭环”搞清楚。

适合谁来参考?第一类是用 openEuler 做企业私有云基础组件选型的同学,第二类是 CentOS 8 迁移到其他发行版时需要复现消息中间件部署的老手,第三类就是刚入门想搞清楚 RPM 依赖到底怎么离线带全的新人。后面两类,这篇尤其有用,因为整个准备流程本身就是在教你如何使用包管理器做依赖分析。

2. 方案选型与前置分析

2.1 选择 RPM 安装还是二进制包解压

RabbitMQ 官方给出了多套部署方式:Debian 系的 apt 包、RPM 包、还有通用二进制 tar.gz 包。在 openEuler 22 / CentOS 8 的环境里,我强烈推荐使用 RPM 家族这种方式。

原因很简单:

  • RPM 包自带 systemd 服务脚本,装完就能用systemctl统一管理,二进制包解压需要自己手写 service 文件。
  • RPM 方式会把 Erlang 的共享库文件放到标准位置,这样 RabbitMQ 启动脚本在编译 C 扩展或加载某些驱动时路径不会混乱。
  • 后续卸载方便,rpm -e可以清理干净,二进制包卸载只能靠删目录,容易残留配置和数据文件。

二进制包也有它不可替代的场景——如果你的系统版本非常冷门,找不到对应编译产物,或者你希望把 Erlang 和 RabbitMQ 放进应用目录随业务一起打包分发,那 tar.gz 更灵活。只是从系统可维护性来说,RPM 永远是首选。

2.2 版本匹配:RabbitMQ 与 Erlang 的对应关系是所有坑的根源

先给结论,我不想让读者走弯路。RabbitMQ 3.8.x 系列适配的 Erlang 版本区间是 21.3.x 到 23.x(更推荐 22.x 分支,因为踩过的坑少),也就是 openEuler 22 默认仓库里的 Erlang 版本非常可能高于 24 甚至 25,千万不要直接装上去用。

我记得有一次没有检查 Erlang 版本,直接用高版本 Erlang 跑 RabbitMQ 3.9 的节点,管理界面虽然能打开,但任何创建队列和绑定 exchange 的操作都会随机报{badmatch, ...}错误。后来去翻了日志才意识到是 Erlang 23 以上的gen_server行为与消息队列内部实现存在兼容性偏差。这些兼容性表格在 RabbitMQ 官方文档里写得清清楚楚,离线环境务必要提前把对应表格抄下来。

建议版本组合(长期稳):

RabbitMQErlang说明
3.8.14 ~ 3.8.3022.3.x 或者 23.2.x稳定通道,生产可用
3.9.x23.2.x 及以上功能更新较多,但需要细致调优
3.10.x +24.x 或 25.x最新特性,但离线安装依赖更多

本次项目因为业务方锁定 3.8 系列,我最终选定 RabbitMQ 3.8.30 + Erlang 22.3.4.25 的组合,这个组合在实际项目里跑通了很多轮,环境兼容性最优,离线依赖最少。

2.3 依赖包的确定与获取策略

RabbitMQ 的 RPM 包依赖一般是socat、logrotate、erlang这些,而 Erlang 的 RPM 包又依赖openssl、ncurses-libs、unixODBC等。在离线环境下,如果一个个rpm -ivh试错,效率极低,而且经常出现“A 依赖 B,B 依赖 C,C 又依赖 A 的某个符号”这种循环依赖,极其搞心态。

最稳妥的策略是在联网环境下利用yum的downloadonly插件把整棵依赖树一次性拉下来,然后在离线机上做成createrepo类型的本地仓库,通过dnf localinstall或yum --nogpgcheck localinstall来安装,让包管理器自动解析依赖顺序。

这样既不会漏包,也不会因为手工调整安装顺序导致 rpmdb 数据库不一致,后续排查问题也能少一大半。

3. 离线安装部署

3.1 联网环境下准备离线包

第一步是在一台可以访问互联网、且操作系统版本基本一致的机器上做准备。我使用的是 openEuler 22.03 LTS SP2 的虚拟机,架构为 x86_64,这一点非常关键,因为 RPM 包是分架构的,不同架构绝对不能用。如果你目标机器是 ARM 架构,准备 RPM 必须下载 aarch64 的包,否则装不上就是硬报错。

准备时先启用必要的仓库,比如 BaseOS、AppStream、EPEL(如果系统里需要)。接着安装yum-utils这样才有yumdownloader工具,还有createrepo用于后期生成仓库元数据:

dnf install -y yum-utils createrepo

然后确定 Erlang 和 RabbitMQ 的 RPM 包已经下载到某个目录。考虑到网络源里可能同时存在多个 Erlang 版本,我用软件包管理器自带过滤方式只下载指定版本,这里不直接贴死命令,核心思路是:用版本号关键字过滤,宁可下载多几个候选包,也不要缺包。

确认下载完成后,将所有 RPM 包统一放进/opt/rpm_packages目录。再执行一次:

createrepo /opt/rpm_packages

这一步会在目录下生成repodata文件夹,相当于把这一堆 RPM 包装成了一个可被 yum/dnf 识别的本地仓库。

3.2 离线机上搭建本地仓库

把整个/opt/rpm_packages目录拷贝到目标机器,位置没有硬性要求,不过建议放在空间充足的/var/repo或者/opt/repo,因为后续可能要追加其他软件包。拷贝时建议用rsync或先打包再 scp,避免大量小文件传输时速度过慢:

tar -czvf rpm_packages.tar.gz /opt/rpm_packages

然后在目标机器上解压,并新建一个 repo 文件。这里要你注意:repo 文件里如果写死了 GPG 校验,而离线包未签名,安装时就会直接失败;所以要么去掉gpgcheck=1,要么在安装命令中加--nogpgcheck。离线内部环境,安全边界已由网络隔离保证,建议直接跳过 GPG 校验:

[root@node1 ~]# cat > /etc/yum.repos.d/local.repo <<'EOF' [local-offline] name=Local Offline Packages Repository baseurl=file:///opt/rpm_packages enabled=1 gpgcheck=0 EOF

随后做一次缓存刷新,让 dnf 识别到这个本地仓库:

dnf clean all dnf makecache

3.3 安装依赖包

Erlang 的依赖看似不多,但真正 offline 安装的时候容易被小细节绊倒。比如openssl版本必须高于 1.1.1,否则 ssl 插件无法加载;还有socat要做 RabbitMQ 的 port 转发辅助工具,缺了它 RabbitMQ 启动时日志会明确报socat: command not found,这个错误非常误导人,因为你可能只是少装了一个工具,而不是 Erlang 崩溃。

在本地仓库建好之后,安装依赖就简单了很多:

dnf install -y socat logrotate

如果提示缺少某些基础的库,直接再执行:

dnf install -y ncurses-libs unixODBC openssl-devel

只要是本地 repo 中存在,包管理器会自动把依赖链处理完。这里执行完成后可以检查一下安装结果:

rpm -qa | grep -E "openssl|socat|ncurses"

确保基础库到位,不然后面 Erlang 的安装会报缺少libssl.so.1.1这类共享库错误,这类错误虽然可以用ln -s临时骗过去,但不建议在生产环境这么搞,后患非常多。

3.4 安装 Erlang 与 RabbitMQ

进入到 RPM 包目录,执行本地安装,这里我不建议直接rpm -ivh *.rpm,因为*.rpm会把 Erlang 和 RabbitMQ 一次全装,如果版本组合不对,后面还得回滚。正确的姿势是分批装。

先装 Erlang:

cd /opt/rpm_packages dnf localinstall -y erlang-22.3.4.25-1.el8.x86_64.rpm

安装完成之后验证一下版本:

erl -version

或者用erl进入交互环境执行:

erlang:system_info(otp_release).

确保 OTP Release 显示为22。

再装 RabbitMQ:

dnf localinstall -y rabbitmq-server-3.8.30-1.el8.noarch.rpm

如果之前没有把socat装全,这一步会报缺失依赖,直接回到上一节把依赖补齐再执行,不需要卸载任何东西。

3.5 启动前的关键配置

RabbitMQ 默认绑定localhost,生产环境如果直接启动,外网或跨节点是访问不到管理界面的。需要先做三件事:

第一,修改/etc/rabbitmq/rabbitmq-env.conf,把NODE_IP_ADDRESS置空或者设置成实际监听地址,否则多网卡机器上只能被本机回环地址监听,其他节点会连不上5672端口。

第二,启用 Web 管理插件,这个步骤必须在服务启动之前或者启动成功后单独执行,否则插件加载不到节点:

rabbitmq-plugins enable rabbitmq_management --offline

因为是完全离线环境,所以一定要记住加上--offline标志,让它在不联系网络的情况下也能完成插件启用。

第三,配置防火墙端口。企业内网环境也可能开启了防火墙,需要放行5672(AMQP 协议端口)、15672(Web 管理界面端口)、25672(节点间通信端口):

firewall-cmd --permanent --add-port=5672/tcp firewall-cmd --permanent --add-port=15672/tcp firewall-cmd --permanent --add-port=25672/tcp firewall-cmd --reload

这里有一个很土的坑:很多人忘记放行25672,结果单机怎么测都正常,一上集群就发现集群节点之间无法互相发现,rabbitmqctl cluster_status会卡住。三个端口必须一起放行。

3.6 启动服务

完成配置后,执行:

systemctl daemon-reload systemctl start rabbitmq-server systemctl status rabbitmq-server

正常的话能看到active (running)状态。此时可以先在本地用 CLI 看节点状态:

rabbitmqctl status

输出里有Node: rabbit@nodename、Pid、Free disk space信息。如果启动失败,就直奔日志目录/var/log/rabbitmq/看startup_err文件,90% 的问题都能找到明确线索。

4. 集群扩展与系统配置

4.1 设置 Erlang Cookie

如果你要搭集群,而不是单机用用就跑,那 Erlang Cookie 是绕不开的一道关卡。RabbitMQ 是 Erlang 程序,Erlang 分布式协议用同一个 cookie 字串来认证两个节点。集群中所有节点必须保持 cookie 一致,否则rabbitmqctl join_cluster一定会报{no_cookie_match, ...}或者其他奇怪的网络错误。

Cookie 文件位置默认在/var/lib/rabbitmq/.erlang.cookie,权限必须为400,属主必须为rabbitmq用户。很多人在配置集群时直接把 cookie 文件复制过去,结果权限默认是644,服务怎么都加入不进去。

修正方法很简单:

chmod 400 /var/lib/rabbitmq/.erlang.cookie chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie

所有节点都要做同一步操作,并确保文件内容一字不差。这里建议用sha256sum对比一下所有节点的校验值,避免复制过程中引入隐藏字节。

4.2 调整系统参数

RabbitMQ 对文件句柄数和内存水位比较敏感,默认配置在超高并发场景下会频繁触发告警。生产环境至少需要解决两个系统层面问题:

问题一是文件描述符限制。 单机队列多或者连接数大的情况下,默认 1024 的进程级限制不够用。要么修改/etc/security/limits.conf给rabbitmq用户加nofile上限,要么直接调大到 65536。这个不调,过段时间你一定会收到类似too many open files的报错日志,那个时间点再去紧急变更非常被动。

问题二是内存水位。 默认 RabbitMQ 会在系统内存达到 40% 时停止接收消息,这是自我保护机制,建议根据机器实际内存大小合理配置。如果 16G 内存的机器只跑了消息队列,可以放宽到 50%,如果同机还部属了数据库,建议降到 30%。修改方式是编辑/etc/rabbitmq/rabbitmq.conf:

vm_memory_high_watermark.relative = 0.5 disk_free_limit.absolute = 1GB

最后一个参数的语义是当磁盘剩余空间低于 1GB 时阻塞生产消息,避免磁盘写满把服务拖死。这里推荐的是一套保命配置。

4.3 配置用户与权限

RabbitMQ 默认有一个guest用户,但注意:guest 只有在 localhost 访问时才能登录,通过远程 Web 或 AMQP 访问都会被拒绝,这个是内置安全逻辑,不是 bug。所以一定要创建自己的管理员账号。

rabbitmqctl add_user admin StrongP@ssw0rd rabbitmqctl set_user_tags admin administrator rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

set_permissions的三个正则分别对应 configure、write、read 权限。生产环境建议按 vhost 分权,比如某个业务对列只给该业务自己的虚拟主机上配置全套权限,不要图省事给全部权限,否则一旦某个应用被拖库,整个消息总线任人宰割。

4.4 集群加入实操

在第二台机器完成同样的安装后,先停止 RabbitMQ 服务,然后手动设置集群节点加入:

rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster rabbit@node1 rabbitmqctl start_app

reset会在加入前把本节点已有数据清空,条件是当前节点还没持久化重要数据。如果已经在该节点上创建过队列以后再加入集群,reset会把数据清掉,所以一定要提前规划好节点的角色分工。加入成功后,在任意节点执行:

rabbitmqctl cluster_status

能看到Running Nodes: rabbit@node1 rabbit@node2说明集群建立成功。集群建立之后,队列会根据绑定的类型分布在任意一个实例上,但消息本身会做镜像或仲裁备份,这部分要根据业务需求单独设计。

5. 常见问题与坑位排查

5.1 安装阶段高频报错

离线安装过程中,最容易碰到的是 RPM 依赖缺失。这里整理成一张速查表,读者对号入座:

错误现象根因处理方法
Requires: socatRabbitMQ RPM 指定 socat 依赖在本地仓库中确认 socat 已下载,并用dnf localinstall自动解决
libcrypto.so.1.1找不到openssl 版本过新或过旧回退或升级 openssl 到目标版本,不推荐软链
erl: symbol lookup errorErlang 运行库与系统库版本不匹配检查 ncurses、openssl 版本是否与 Erlang 编译时的动态库匹配
多个 RPM 包循环依赖手工rpm -ivh顺序不对改用dnf localinstall *.rpm自动排序

5.2 启动阶段的隐蔽问题

启动失败时,不要只看 systemd 的状态,很多信息会被吃掉。首选排查命令:

journalctl -u rabbitmq-server -n 100 --no-pager tail -f /var/log/rabbitmq/startup_err

有几个启动问题非常隐蔽:

问题一,主机名解析异常。 RabbitMQ 节点名默认是rabbit@主机名,如果/etc/hosts里没有把当前主机名解析到实际 IP,Erlang 分发协议会尝试反向解析,导致节点启动非常慢,甚至 epmd 端口注册超时。解决办法是在/etc/hosts写上:

192.168.30.10 node1 192.168.30.11 node2

并让hostname和 hosts 里的别名保持一致。

问题二,epmd 端口冲突。 如果在同一台机器上跑过多个 Erlang 服务,默认的 epmd(Erlang Port Mapper Daemon)端口 4369 会被占用。RabbitMQ 启动时会报epmd: address already in use。解决方法是先停掉旧的 Erlang 进程,或者给 RabbitMQ 单独指定端口。

问题三,磁盘空间不足导致初始化中止。 RabbitMQ 初始化阶段会往/var/lib/rabbitmq/mnesia写大量临时文件,如果根分区剩余空间小于 500MB,节点直接起不来。日志里通常能看懂。

5.3 管理界面常见问题

RabbitMQ 管理界面打不开,一般是三种情况:

一是插件没启用,执行rabbitmq-plugins list可查看[E]标记,启用后需要重启服务生效。

二是监听地址没有绑定到对外网卡,通过ss -lntp | grep 15672看监听地址,如果是127.0.0.1,按前面所述修改rabbitmq-env.conf。

三是端口没放行或者受 SELinux 限制。openEuler 默认可能开了 SELinux,这会导致明明防火墙放行,端口仍然访问不了。临时测试可以用setenforce 0,但生产环境正确做法是:

semanage port -a -t rabbitmq_port_t -p tcp 15672

如果没有semanage命令,需要安装policycoreutils-python-utils。

5.4 一个必须提前规避的网络干扰问题

这里是我踩过最大的一个坑:生产内网环境中,DNS 解析网关经常会把部分主机名解析到代理网关或特殊的内部虚拟 IP。RabbitMQ 启动时 epmd 拿到错误解析结果以后,客户端从一个节点连另一个节点时会出现“节点身份不匹配”的错误。

规避方案很简单:在/etc/hosts中把集群所有节点的真实 IP 与主机名写好,不要依赖 DNS。同时把/etc/rabbitmq/rabbitmq.conf中的listeners.tcp.default显式指定为节点自身 IP。这样无论 DNS 怎么配,节点间通信都走真实网络路径。

6. 实操心得与总结

整个离线部署项目做下来,我最深的感受是:离线环境其实比在线环境更锻炼对系统包管理的理解。在线环境很多时候yum install直接回车,缺失的依赖自动就解决了,离线装一次,才会被迫弄清楚每个包真正的依赖来源。

给你几个实用建议:

第一,准备离线包时,如果空间充足就把安装包和依赖分开放两个目录,一个装 Erlang,一个装 RabbitMQ,方便后期版本升级。我在实际项目中吃了不少教训,所有包混在一起后,追查老版本和新版本的文件覆盖关系时非常费劲。

第二,每次安装完一个核心组件都留个校准记录——把rpm -qa | grep rabbitmq、erl -version、rabbitmqctl version的输出去到一个单独文档里。等后续做安全补丁或者二次升级时,可以快速定位基线版本。

第三,离线仓库不是建好就一劳永逸。新补丁包或新组件要加进仓库时,必须重新执行createrepo --update /opt/rpm_packages把仓库元数据刷新,不然 dnf 会无视新加的文件。这一点很多人栽过跟头,以为文件在目录里就能用,结果安装时报“没有匹配的软件包”。

最后再说一个非常实用的小技巧:整套环境搭好之后,不需要一台一台重复执行安装命令。你可以把目标服务器的/var/lib/rabbitmq和/etc/rabbitmq目录打包做成基线模板,在新机器上用脚本释放,再执行systemctl start rabbitmq-server就能省去手动配置用户和 vhost 的过程。当然这只适用于完全同构的集群,如果角色分工不同,还是老老实实逐个配置。

这套方法不只适用于 openEuler 22 上的 RabbitMQ,换个角度想,也适用于离线部署 MySQL、Redis、Nginx 这类讲究依赖完整性的中间件。关键是理清包依赖、建立离线仓库、配置服务留痕这三个动作,把它们做成了肌肉记忆,后续任何离线安装需求都不再怵头。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询