简介:gpmall-repo.rar 是面向云计算与分布式电商平台运维学习者的离线软件仓库资源包,主要服务于 gpmall 服务部署、实验环境搭建及内网依赖安装场景,可缓解外网源访问不稳定、组件版本不一致等常见问题。压缩包共收录173个文件,约304.19MB,其中166个为RPM安装包,覆盖Logstash、MongoDB、OpenJDK、Elasticsearch等常用服务端组件;另有gz、bz2格式的仓库元数据及1个XML文件,便于构建本地YUM源并完成依赖索引。已有187人学习下载,适合初中级运维人员和云计算课程实践者参考。使用时可直接获得较完整的离线依赖集合,减少逐个查找安装包的重复工作,也便于对照组件版本梳理部署流程、复现实验环境并排查依赖冲突,提升gpmall相关服务搭建与验证效率。
1. 一份压缩包,解决的是内网机器装不上中间件这件事
上周有人发来一个压缩包,说机房的内网机器根本连不上外网,yum install一条命令都跑不通,一个电商微服务项目卡在装 MySQL 上卡了整整两天。我打开看了看,包名叫 gpmall-repo.rar,解压出来是成堆的 rpm 包和一批 Maven 依赖 jar。这种资源在云计算方向的课程设计和微服务实训里出现频率很高:把一套商城系统跑起来需要的运行时、中间件、依赖包整体离线打包,让没有外网的机器也能把环境从零搭起来。它解决的是环境依赖供给的问题,并非业务代码层面的问题。适合三类人:断网机房里要做部署实训的学生、需要给客户演示一套微服务 Demo 但对方网络封闭的工程师、以及想把一次性环境固化成内网基线的人。下面按我实际的拆包顺序写,能照着复现。
2. 先拆包再动手:gpmall-repo 里到底放了什么
拿到一个几十上百兆的 rar,最忌讳的动作是直接扔到/usr/local下面一通解压,然后rpm -ivh *.rpm一把梭。原因很实际:这个包里的组件是有依赖顺序的,一口气装完大概率会撞上冲突,而且装坏了很难回退。我的习惯是先花十分钟把目录结构和文件清单摸清楚,再决定这套东西装到哪个路径、按什么顺序装。
2.1 解包、校验与三层目录盘点
Linux 上处理 rar 不像 zip 那么顺手,unrar未必在源里,用7z更稳。解压目标目录单独开一个,比如/opt/offline-repo,别和系统自带的/usr、/usr/local混在一起,后面要撤的时候直接删目录就行,不用一个个rpm -e。
# 1) 先不解压,只看目录树,判断包体规模 7z l gpmall-repo.rar | head -40 # 2) 解到独立目录,-o 后面不能有空格 mkdir -p /opt/offline-repo 7z x gpmall-repo.rar -o/opt/offline-repo -y # 3) 生成校验清单,往后每次搬运或改包都对比一次 cd /opt/offline-repo find . -maxdepth 3 -type d | sort find . -type f -name "*.rpm" | wc -l md5sum $(find . -type f -name "*.rpm") > /opt/repo.md57z l只列内容不出文件,用来判断包体里是不是还有嵌套压缩;-o指定输出路径,写成分开的形式会报参数错误;最后那份 md5 清单是后悔药,跨机器拷贝、U 盘搬运之后跑一遍md5sum -c /opt/repo.md5,能立刻发现是不是传坏了。这一步在实际操作里没少救场:曾有次内网拷贝后有 3 个 rpm 是半截文件,yum报的错却是一句含糊的“文件损坏”,排查了两小时才想到校验。
目录盘点时重点看三层结构:最外层通常是安装脚本和说明,中间层是 rpm 包集合,可能还有一层独立放 Maven 依赖或者 SQL 初始化脚本。这三层的处理方式完全不同:rpm 要走 yum 源,Maven 依赖要落到settings.xml指向的本地仓库,SQL 脚本要在服务起来之后手工灌。
# 按扩展名分类,看清包体构成 find /opt/offline-repo -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn # 看 rpm 包的架构和版本,确认和目标机器匹配 rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' \ /opt/offline-repo/**/*.rpm 2>/dev/null | sort -u | head -30第二条命令是关键。gpmall-repo这种离线包最常见的问题是架构不对:在 x86_64 机器上解出来的却是 aarch64 的包,yum install会直接告诉你“没有可用的软件包”,而包明明就在目录里。先确认%{ARCH}和目标机uname -m一致,能省掉一半的无效排查。
2.2 从文件清单反推部署拓扑与端口
文件清单本身就是一张部署图。包里出现了zookeeper,基本就能断定 Kafka 是跑在它上面的;出现了elasticsearch,就意味着后面要调内核参数;出现了nginx和一堆*-web之类的 war 或 jar,说明前端和网关是分开部署的。把这套组件映射成端口表,装机的时候才知道该开哪些防火墙规则。
| 组件 | 包里对应物 | 默认端口 | 前置依赖 |
|---|---|---|---|
| JDK | jdk-*.rpm | 无 | 无,最先装 |
| MySQL | mysql-community-*.rpm一组 | 3306 | 需先清掉mariadb-libs |
| Redis | redis-*.rpm或源码目录 | 6379 | 无 |
| ZooKeeper | zookeeper-*.rpm | 2181 / 2888 / 3888 | JDK |
| Kafka | kafka_*.tgz或 rpm | 9092 | ZooKeeper 必须先起 |
| Elasticsearch | elasticsearch-*.rpm | 9200 / 9300 | JDK,需调vm.max_map_count |
| Nginx | nginx-*.rpm | 80 / 443 | 无 |
这张表不是抄来的模板,是从文件清单里逐个核对出来的。如果包里只有 Kafka 没有 ZooKeeper,那说明部署方案里用的是 Kafka 内置的 KRaft 模式,或者 ZK 在外部队列机器上,这时候装完直接起 Kafka 会一直卡在“等待连接”,得回头确认。
还有一类信息藏在 rpm 的依赖声明里:
# 查单个包的依赖,判断装机顺序 rpm -qpR /opt/offline-repo/rpms/mysql-community-server-*.rpm | head -20 # 反过来查:包里哪些 rpm 是给别人当依赖的 rpm -qp --provides /opt/offline-repo/rpms/*.rpm 2>/dev/null | grep -i "libaio\|ncurses"rpm -qpR打印的是这个包需要什么,--provides打印的是它能提供什么。两边一对,安装顺序基本就自动排出来了。我一般会把这份依赖关系先落到一张纸上,装的时候照着走,比装一步报一次错再去搜要快得多。
3. 把静态目录变成 YUM 源:createrepo 与 repo 文件的两端配置
rpm 文件放在目录里不等于能用yum装。yum认的是 repodata 元数据,也就是一个repodata/子目录里那几份 xml 文件。没有它,yum走到这个目录面前会当作不存在。这一步是整个离线部署的分水岭,做对了后面一路顺,做错了就是无穷无尽的 “Cannot retrieve repository metadata”。
3.1 服务端:目录发布与 createrepo 元数据生成
先明确一件事:包里的 rpm 是不是全都堆在一个目录里。如果分了os/、extras/这种子目录,那每个子目录都是一个独立的仓库,要分别建元数据;如果全平铺在一个目录下,一次createrepo就够。
# 1) 统一 rpm 存放路径,避免多层嵌套 mkdir -p /opt/offline-repo/rpms find /opt/offline-repo -name "*.rpm" -exec mv -n {} /opt/offline-repo/rpms/ \; # 2) createrepo 未必在系统里,先用包内自带的装上 rpm -ivh /opt/offline-repo/rpms/createrepo*.rpm \ /opt/offline-repo/rpms/python-deltarpm*.rpm 2>/dev/null || \ yum install -y createrepo # 3) 生成元数据,-v 看进度,--update 用于后续增量 createrepo -v /opt/offline-repo/rpms # 目录内容有变化时用增量重建,速度快很多 createrepo --update -v /opt/offline-repo/rpms # 4) 校验元数据确实生成了 ls -l /opt/offline-repo/rpms/repodata/createrepo的路径参数必须指向 rpm 所在的那一层,指向上一层会生成一个空仓库,repodata里的primary.xml记录数为 0,客户端yum list什么都看不到,但又不报错,这种情况最容易让人以为配置写错了。生成完拿ls看一眼repodata里是不是有repomd.xml、primary.xml.gz、filelists.xml.gz,缺哪个都不行。
发布方式有两种,取决于拓扑。单机自用直接走file://,不用起服务:
# 方式一:本地路径,注意 file:// 后面是三个斜杠 baseurl=file:///opt/offline-repo/rpms要给内网其他机器用,起个 Nginx 更合适,还能顺便限速、看访问日志:
# 方式二:HTTP 发布 cat > /etc/nginx/conf.d/offline-repo.conf <<'EOF' server { listen 8080; server_name _; root /opt/offline-repo; autoindex on; # 方便浏览器直接看目录 location /rpms/ { alias /opt/offline-repo/rpms/; autoindex on; } } EOF nginx -t && systemctl reload nginx # 防火墙放行,只对内网段开放 firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=192.168.0.0/16 port port=8080 protocol=tcp accept' firewall-cmd --reload curl -I http://127.0.0.1:8080/rpms/repodata/repomd.xmlautoindex on不是为了好看,是排错时能直接在浏览器里翻目录,确认某台机器访问到的路径到底对不对;alias和root的区别要留神,alias是把 location 前缀整个替换掉,写错一个斜杠就会 404。最后那条curl是必做的验证:返回 200 才算服务端这一侧过关,返回 403 通常是 SELinux 拦了目录读权限,返回 404 是路径写错了。
3.2 客户端:baseurl、gpgcheck 与优先级
客户端这边就是写一个.repo文件。看着简单,但参数怎么填直接决定后面会不会翻车。
# /etc/yum.repos.d/local-offline.repo [local-offline] name=Local Offline Repo baseurl=http://192.168.10.20:8080/rpms/ # 单机自用改成:baseurl=file:///opt/offline-repo/rpms/ enabled=1 # 离线包通常没有签名,这里关掉;生产环境应导入自建 GPG key gpgcheck=0 # 数字越小优先级越高,避免公网源的同名包覆盖离线版本 priority=1 # 只装仓库里有明确声明的包,禁止自动去公网补齐 skip_if_unavailable=0gpgcheck=0是离线场景的常见妥协,但要知道代价:包被替换过也发现不了。稳妥做法是把包内附带的 GPG 公钥rpm --import进来,然后写gpgcheck=1加gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-local。priority需要yum-plugin-priorities插件,如果系统没装,这个字段会被静默忽略,不报错也不生效。
配置写完先清缓存再建缓存,yum的缓存很顽固,改完 repo 不清等于没改:
yum clean all rm -rf /var/cache/yum/* yum makecache # 验证仓库真的被识别,且包数量合理 yum repolist all yum --disablerepo="*" --enablerepo="local-offline" list available | wc -lyum repolist all的输出里,local-offline那一行的状态要是enabled,包数量不能是 0。数量为 0 而状态正常,八成是baseurl指到了rpms的上一级。--disablerepo="*" --enablerepo=...这个组合是用来做隔离测试的,能确认某个包到底是从离线源装的还是从别处漏进来的,排查版本错乱时非常好用。
4. 安装顺序与版本对齐:依赖链上先装谁
有了可用的源,剩下的问题就变成“按什么顺序装”和“怎么防止装错版本”。这两件事在离线环境里比在线环境更敏感,因为在线环境装错了yum还能去公网找替代版本,离线环境里装错了只有重装系统这一条路。
4.1 从 JDK 到 Kafka 的启动依赖顺序
顺序的原则只有一条:被依赖的先装、被连接的要先起。中间件之间的“依赖”分两种,装机依赖(rpm 层)和运行时依赖(进程层),两者不是一回事。JDK 是典型的装机依赖,没它 Kafka 的 rpm 装都装不上;ZooKeeper 对 Kafka 是运行时依赖,rpm 装得上,但 Kafka 起不来。
#!/bin/bash # deploy-offline.sh —— 按依赖顺序离线部署,任一步失败即终止 set -euo pipefail REPO="--disablerepo=* --enablerepo=local-offline" echo "[1/7] JDK" yum install -y $REPO java-1.8.0-openjdk java-1.8.0-openjdk-devel java -version echo "[2/7] MySQL:先清冲突包再装" rpm -q mariadb-libs >/dev/null 2>&1 && yum remove -y mariadb-libs || true yum install -y $REPO mysql-community-server mysql-community-client systemctl enable --now mysqld # 首次启动生成的临时密码在日志里 grep 'temporary password' /var/log/mysqld.log | tail -1 echo "[3/7] Redis" yum install -y $REPO redis sed -i 's/^bind 127.0.0.1/bind 0.0.0.0/' /etc/redis.conf sed -i 's/^protected-mode yes/protected-mode no/' /etc/redis.conf systemctl enable --now redis redis-cli ping echo "[4/7] ZooKeeper" yum install -y $REPO zookeeper systemctl enable --now zookeeper echo ruok | timeout 2 nc 127.0.0.1 2181 echo "[5/7] Kafka:必须在 ZK 之后" yum install -y $REPO kafka systemctl enable --now kafka sleep 10 && ss -lntp | grep 9092 echo "[6/7] Elasticsearch:先调内核参数,否则会被 OOM killer 带走" sysctl -w vm.max_map_count=262144 echo 'vm.max_map_count=262144' >> /etc/sysctl.conf yum install -y $REPO elasticsearch systemctl enable --now elasticsearch echo "[7/7] Nginx" yum install -y $REPO nginx nginx -t && systemctl enable --now nginx逐段说明关键点。set -euo pipefail是必须的,中间任何一步失败就停,不然脚本会带着残缺环境一路跑到底,最后你看到的是“七个组件都装了但一个都不通”。--disablerepo=* --enablerepo=local-offline把安装来源锁死在离线源,避免机器上残留的某个公网 repo 混进来,装出半新半旧的组合。
MySQL 那一段先删mariadb-libs,因为多数发行版自带 MariaDB 的兼容库,和 MySQL 官方包的文件路径重叠,yum会报file /usr/lib64/libmysqlclient.so.18 conflicts。这个冲突不解决,后面装什么都没用。Redis 改bind和protected-mode只是因为内网演示图省事,任何可路由环境都该配密码加 ACL。
ZooKeeper 用echo ruok | nc验证,返回imok说明四字命令通道通畅;Kafka 那一步sleep 10后再看9092端口,是因为 Kafka 启动要先去 ZK 注册,起得太快查端口会误判。Elasticsearch 的vm.max_map_count必须在装之前或启动之前设,默认的 65530 会导致 JVM 在初始化 mmap 时被内核拒绝,进程直接消失,日志里只有一行max virtual memory areas ... is too low。
4.2 版本锁定:excludes、priority 与手动 rpm 的边界
离线环境最怕的不是装不上,是装上了不兼容的版本。比如 MySQL 8 的驱动连 MySQL 5.7 的服务端,认证插件对不上,报的错是Unable to load authentication plugin 'caching_sha2_password',看着像代码问题,实际是版本问题。
锁定版本有三个层次,力度从小到大:
| 手段 | 写法 | 适用场景 | 风险 |
|---|---|---|---|
| 指定完整版本 | yum install mysql-community-server-5.7.36-1.el7 | 单次安装 | 依赖仍可能被升级 |
| repo 级排除 | exclude=mysql* kafka* | 防止某类包被误升级 | 需要维护列表 |
| versionlock 插件 | yum versionlock add mysql-community-server | 长期锁定 | 解锁需显式操作 |
实际做法是先看包里到底有哪几个版本:
# 列出离线源里某个组件的所有可用版本 yum --disablerepo="*" --enablerepo="local-offline" --showduplicates list mysql-community-server # 查已装的版本和来源 yum list installed | grep -i mysql rpm -qi mysql-community-server | grep -E "Version|Release|Vendor"--showduplicates这个参数很关键,默认yum list每个包只显示最新一条,你会以为包里只有一个版本,实际上老版本也在,只是被隐藏了。部署前先跑一遍,把每个组件的可用版本记下来,和项目要求的版本对一遍,比装完再改省事得多。
关于rpm -ivh --nodeps,我的态度很明确:除了极少数明确知道后果的场景,不要用。--nodeps强装跳过了依赖检查,装完yum check会报一堆 broken dependency,后续任何yum操作都可能被卡住。真遇到 rpm 之间互相依赖成环,正确做法是把相关的几个包放在一条命令里一起装,让yum或rpm自己排序:yum install -y a.rpm b.rpm c.rpm,yum会解析这一批文件的内部依赖,比逐个rpm -ivh靠谱得多。这个技巧叫 localinstall,是离线装第三方包的常规手法。
5. 避坑与排查:内网源上线后最容易翻车的五个点
下面这五条都是实际环境里撞出来的,每条按“看到什么、为什么、怎么解”写,遇到时可以直接对号入座。
5.1 元数据与缓存类问题
现象一:yum makecache报Cannot retrieve repository metadata (repomd.xml) for repository: local-offline,但curl访问那个 URL 明明能通。
原因有两个方向。一是baseurl少写了一层:元数据在/rpms/repodata/下,而baseurl写成了/,yum会去/repodata/repomd.xml找,自然 404。二是createrepo执行时的工作目录不对,元数据生成在了rpms的上一层,或者repodata目录权限是 700,Nginx 的 worker 用户读不到(这时curl从本机 root 跑是通的,浏览器访问却是 403)。
解决:先用curl -I精确访问http://host:8080/rpms/repodata/repomd.xml,看状态码是 404 还是 403。404 改baseurl层级,403 执行chmod -R 755 /opt/offline-repo并检查 SELinux:getenforce返回Enforcing时补一条chcon -R -t httpd_sys_content_t /opt/offline-repo。改完记得yum clean all再试,否则缓存里还是旧的失败结果。
现象二:仓库状态是 enabled,但yum list available出来是 0 条。
原因是repodata存在但内容为空,createrepo跑的时候目录里还没有 rpm,或者跑完之后才把 rpm 挪进去。这种“元数据比文件旧”的状态最阴,yum不会报错,只会安静地告诉你没东西可装。
解决:createrepo --update -v /opt/offline-repo/rpms重建一次,然后grep -c "<package" /opt/offline-repo/rpms/repodata/*primary.xml*确认条目数和ls *.rpm | wc -l对得上。对不上就是路径搞错了,先去核对find / -name "*.rpm"的实际分布。
5.2 依赖与版本类问题
现象三:装完某个组件后systemctl start报error while loading shared libraries: libaio.so.1: cannot open shared object file。
原因是之前用rpm -ivh --nodeps强装过,把依赖检查绕过去了,运行时才发现缺底层库。libaio、numactl-libs、ncurses-libs是离线包里最容易被漏掉的几个“小依赖”,因为它们在在线环境里通常早就装好了,打包时不会有人特意想。
解决:rpm -qpR <包名>.rpm把完整依赖列表拉出来,逐个用yum install从离线源补齐;yum deplist <包名>更直观,会列出每个依赖当前由哪个包满足。补完之后跑一次yum check和rpm -Va,前者看依赖完整性,后者看文件是否被改坏。装任何第三方 rpm 之前跑一遍yum localinstall ./xxx.rpm,让 yum 自己解依赖,比手工rpm -ivh稳。
现象四:MySQL 能装但起不来,systemctl status mysqld显示Failed to start,日志里是权限或端口占用。
端口占用的排查顺序是ss -lntp | grep 3306,看是不是机器上还有个 MariaDB 或者容器占着。权限问题多半出在数据目录:如果之前手动初始化过datadir,或者从别的机器拷过数据目录,mysql用户的属主丢失,mysqld会拒绝启动。这种问题日志里往往只有一行[ERROR] InnoDB: Operating system error number 13,不熟悉的话很难联想到权限。
解决:chown -R mysql:mysql /var/lib/mysql并确认datadir在my.cnf里指的确实是这个路径;如果是全新安装,直接rm -rf /var/lib/mysql/*再mysqld --initialize重新初始化,比修权限快。别在还有残留ibdata1的情况下反复重启,容易把数据目录搞成半初始化状态。
5.3 运行环境类问题
现象五:Elasticsearch 起来几秒就没了,日志只有Killed或max virtual memory areas vm.max_map_count [65530] is too low;Kafka 则是启动后一直重连 ZooKeeper。
ES 这条前面提过,vm.max_map_count默认值不够,Lucene 的内存映射被内核拒绝。注意这个参数要在启动之前设,设完sysctl -p生效,已启动的进程不会自动应用。
Kafka 重连 ZK 的原因通常是三种:ZK 没起(echo ruok | nc验证)、server.properties里zookeeper.connect写的主机名解析不了(getent hosts验证)、或者两台机器时间差超过 session timeout(date对比,配chrony同步)。我遇到过一次是主机名大小写不一致,hosts里写的是Kafka01,配置文件写的是kafka01,ping不通但yum一切正常,排查了很久。这类问题没有捷径,就是按“进程 → 端口 → 域名解析 → 时间”四层依次验。
6. 把它固化成内网基线:自检脚本与增量更新
离线包最大的价值不在第一次装通,而在于后面每次重装都能得到一模一样的结果。我一般会把上面这些验证动作收成一个自检脚本,装完之后跑一遍,输出一份环境快照。这套习惯是从一次教训里来的:某次演示前夜重装了机器,MySQL 装了 8.0 而应用依赖 5.7 的驱动,现场报认证插件错误,翻日志翻了半小时才发现是版本漂了。
# env_check.py —— 离线环境自检,输出结构化报告 import subprocess, shutil, socket CHECKS = [ ("jdk", ["java", "-version"]), ("mysql", ["mysql", "--version"]), ("redis", ["redis-server", "--version"]), ("nginx", ["nginx", "-v"]), ] PORTS = [3306, 6379, 2181, 9092, 9200, 80] def run(cmd): try: out = subprocess.run(cmd, capture_output=True, text=True, timeout=10) return (out.stdout + out.stderr).strip().splitlines()[0] except Exception as e: return f"MISSING ({e})" def port_open(p): with socket.socket() as s: s.settimeout(0.5) return s.connect_ex(("127.0.0.1", p)) == 0 report = {} for name, cmd in CHECKS: report[name] = shutil.which(cmd[0]) and run(cmd) or "NOT INSTALLED" report["ports"] = {p: port_open(p) for p in PORTS} report["ulimit_nofile"] = subprocess.run( ["bash", "-c", "ulimit -n"], capture_output=True, text=True).stdout.strip() report["selinux"] = subprocess.run(["getenforce"], capture_output=True, text=True).stdout.strip() for k, v in report.items(): print(f"{k:16} {v}")这段脚本做三件事:查二进制在不在路径上、查关键端口有没有监听、查ulimit和 SELinux 这两个经常被忽略的系统状态。shutil.which判断的是可执行文件是否存在,比直接跑命令快;connect_ex返回 0 表示端口可连,比ss解析文本稳定;ulimit -n单独拉出来是因为 Kafka 和 ES 默认的文件句柄上限经常不够,日志里报Too many open files时才想起来查就晚了。
增量更新这块也有个固定套路。包里新加了 rpm,不需要删掉repodata重来:
# 加入新包后增量重建,只处理变化的条目 cp new-component-*.rpm /opt/offline-repo/rpms/ createrepo --update -v /opt/offline-repo/rpms # 客户端侧强制刷新 yum clean metadata && yum makecache--update只扫描新增和变化的包,包里几千个 rpm 时比全量重建快一个数量级。但要注意它不会清理已删除包留下的旧条目,如果是从目录里删了 rpm,得用createrepo --update --database配合--skip-stat全量重建,或者干脆删掉repodata重来。这个细节不注意,会出现“源里明明删了某个包,客户端还能装”的怪现象。
从那以后我每次拿到这类离线包,第一件事都是先跑一遍环境快照,把版本号、端口、依赖清单全部落成文件存进包里,下次重装直接 diff 对比。版本漂移这种事,事后查是黑匣子,事前留底就是后悔药。希望帮到你。
本文还有配套的精品资源,点击获取