上周帮客户处理一台内网数据库服务器,CentOS 7.9,跑着一个老监控程序,需要重新编译一个模块,结果发现机器上连 gcc 都没有。更麻烦的是这台机器在隔离网段,连不了外网,yum install gcc 直接报错,根本无法解析镜像地址。折腾了一下午,踩了不少坑,最后把离线安装 gcc 及全部依赖的流程完整梳理了一遍。这篇文章就讲清楚一件事:CentOS 7 在没有外网的环境下,怎么把 gcc 装好,装完还要能正常编译 C/C++ 程序。
先说结论,离线安装 gcc 的关键不是 gcc 这个包本身,而是它的依赖。gcc 链接了 cpp、glibc-devel、libmpc、mpfr 等一系列底层包,且这些包之间还有层层依赖。如果只是下载几个 rpm 直接装,大概率会卡在依赖循环或者版本冲突上。我这次把常用的几种下载和安装思路都试了一遍,整理成了可以直接照着操作的步骤,下面慢慢展开。
1. 离线环境装 GCC,难点到底在哪
1.1 内网机器为什么装不上 gcc
很多刚接触离线安装的人,第一反应是找 gcc 的 rpm 包拷进去,然后 rpm -ivh gcc.rpm 就行。这个思路在绝大多数情况下是走不通的。CentOS 7 的 gcc 包本身不大,但它依赖的 cpp、glibc-devel、libmpc、mpfr、libgcc、binutils 等都是独立的 rpm,任何一个缺失或者版本不匹配,rpm 安装时都会直接报依赖错误,而且是连环报错。
我遇到的这台服务器就是典型场景:系统只装了最小化基础包,没有安装 Development Tools 组,gcc、make、gcc-c++ 全都没有。网络又受限,安全策略只放行特定端口,连 yum 镜像都访问不了。这种环境在政企内网、涉密机房、生产隔离区非常常见,并不是个例。
还有个隐藏问题容易被忽略:即使你有外网下载权限,从网上随手找的 gcc rpm 很可能跟当前系统的 glibc、kernel-headers 版本对不上。CentOS 7 的 gcc 是 4.8.5,它编译时依赖的 glibc-devel 版本必须和系统已安装的 glibc 2.17 配套,如果拿一个从 CentOS 8 或者其它发行版拷贝的包,轻则装不上,重则把系统基础库搞坏,连 ssh 都起不来。
1.2 两条主流思路:yumdownloader 与 createrepo
离线安装 gcc 实操中有两条主流路线,我两条都跑通了,分别适用于不同场景。
思路 A:在有网且系统版本一致的机器上,用 yumdownloader --resolve 把 gcc 及其全部依赖下载到本地目录,然后打包传进内网,直接 rpm -Uvh *.rpm 批量安装。这个思路简单粗暴,适合只装一台机器,或者内网机器数量很少的情况。
思路 B:同样先在有网机器上下载全部依赖,然后在内网机器上执行 createrepo 把 rpm 目录做成一个本地 yum 源,再通过自定义 repo 文件,让 yum 像正常联网安装一样把 gcc 装好。这个思路适合内网有大量同版本机器,后续还要装其他软件的场景,因为做好的本地源可以反复用。
两条思路的前半段准备工作几乎一样,区别只在最后一步用 rpm 直接装,还是先建本地源再用 yum 装。我个人的建议是,只要内网机器超过一台,就老老实实走思路 B,把本地源建起来,后面装 vim、gdb、make 等工具时能少折腾好几轮。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 依赖关系拆解:gcc 到底要带哪些包
2.1 gcc 的核心依赖链
CentOS 7 的 gcc 4.8.5 完整依赖链,可以用一句话概括:gcc 依赖 cpp、glibc-devel、libmpc,libmpc 依赖 mpfr,glibc-devel 依赖 glibc-headers,glibc-headers 又依赖 kernel-headers。这还没算上 binutils、libgcc、libstdc++ 这些基础组件,以及 gcc-c++ 额外需要的 libstdc++-devel。
也就是说,想装一个能编译 C/C++ 的环境,至少需要带上下面这一批包:
- gcc-4.8.5-44.el7.x86_64.rpm
- gcc-c++-4.8.5-44.el7.x86_64.rpm
- cpp-4.8.5-44.el7.x86_64.rpm
- libgcc-4.8.5-44.el7.x86_64.rpm
- libstdc++-4.8.5-44.el7.x86_64.rpm
- libstdc++-devel-4.8.5-44.el7.x86_64.rpm
- glibc-2.17-317.el7.x86_64.rpm
- glibc-common-2.17-317.el7.x86_64.rpm
- glibc-devel-2.17-317.el7.x86_64.rpm
- glibc-headers-2.17-317.el7.x86_64.rpm
- kernel-headers-3.10.0-1160.el7.x86_64.rpm
- libmpc-1.0.1-3.el7.x86_64.rpm
- mpfr-3.1.1-4.el7.x86_64.rpm
- binutils-2.27-44.base.el7.x86_64.rpm
这里必须提醒一下:具体版本号会随着你下载时使用的 yum 源快照变化,比如 glibc 可能是 2.17-317,也可能是 2.17-326,这很正常。真正要匹配的是大版本代次,CentOS 7 就是 glibc 2.17 + gcc 4.8.5 这个组合,不要跨大版本混搭。
2.2 用 yum deplist 精确查看依赖
不用凭记忆硬背依赖列表,yum 自带两个命令可以帮你查清楚。比如在一台已经联网的 CentOS 7 机器上,先确保 yum 源可用,然后执行:
bash复制yum deplist gcc
这个命令会列出 gcc 的所有依赖项,包括 provider。实际输出会很长,重点看 "dependency: libmpc.so.2()(64bit)" 这类信息,它表示 gcc 运行和编译时需要一个叫 libmpc.so.2 的共享库,而这个库由 libmpc 包提供。
如果嫌 yum deplist 输出太杂,可以用 repoquery 来查,它更简洁:
bash复制yum install -y yum-utils
repoquery --requires --resolve gcc
--requires 表示列出依赖,--resolve 会自动把依赖关系递归展开,输出结果是所有涉及到的 rpm 包名。这个命令是我用得最多的,信息密度高,一眼就能知道 yumdownloader 大概会拉下来多少包。
2.3 gcc-c++ 与 glibc-devel 的隐藏关系
很多人的目标是装能编译 C++ 的环境,于是只记得下载 gcc-c++,却忽略了 gcc-c++ 依赖 libstdc++-devel,而 libstdc++-devel 又依赖 glibc-devel。如果下载时用了 --resolve 参数,yum 会自动把这些带齐;但如果是手动从网上逐个下载 rpm,就非常容易漏掉 glibc-headers、kernel-headers 这种看着跟编译无关的包。
另外,glibc-devel 这个包比较特殊,它提供的是 C 语言标准库的头文件和静态链接库。没有它,就算 gcc 能装上,编译任何一个最简单的 hello world 都会报 fatal error: stdio.h: No such file or directory。这个问题我在后文排查部分会单独讲,因为它是离线安装后最常见的失败原因。
3. 有网机器上准备安装包
3.1 准备一台环境一致的机器,别跳版本
下载安装包这一步,必须在和离线目标机系统版本、架构都一致的环境上完成。这里的一致指两个层面:一是大版本一致,比如都是 CentOS 7.9;二是架构一致,x86_64 对 x86_64,aarch64 对 aarch64,不能混用。
原因很简单,rpm 包内的二进制代码是编译时固定的,针对某个架构和某个 glibc 版本。用 CentOS 8 的机器下载的 gcc rpm,拿到 CentOS 7 上会因为 glibc 版本太旧而无法运行。同样,x86_64 的包装在 aarch64 机器上,rpm 会直接拒绝安装,报架构不匹配。
查询目标机信息用这个命令:
bash复制cat /etc/redhat-release
uname -m
如果手头没有同版本机器,可以用虚拟机临时搭一台相同版本的系统,CentOS 7 的镜像文件很好找,装完最小化就行,不需要额外配置。这步不能省,它是整个离线安装成功的基础。
3.2 yumdownloader 一步到位下载全部依赖
在有网机器上,先安装 yum-utils 工具集,它包含 yumdownloader:
bash复制yum install -y yum-utils
然后创建存放 rpm 的目录,执行下载命令:
bash复制mkdir -p /opt/gcc-rpms
cd /opt/gcc-rpms
yumdownloader --resolve --destdir=/opt/gcc-rpms gcc gcc-c++
--resolve 参数是关键,它让 yumdownloader 自动把 gcc、gcc-c++ 运行所需的所有依赖包递归下载完整。不加这个参数的话,只会下载指定的两个 rpm,缺了依赖等于白忙。
执行完以后,检查一下目录里的文件数量:
bash复制ls -lh /opt/gcc-rpms
正常情况下你会看到上面列出的那十多个 rpm 文件。如果某些包提示已存在或跳过,不用担心,可以加 --enablerepo=base,extras,updates 确保从完整的源里拉取。下载结束后,再看一眼有没有缺漏:
bash复制rpm -qpR /opt/gcc-rpms/gcc*.rpm
这个命令会列出待装 rpm 的依赖,如果显示的依赖都能在下好的 rpm 目录里找到对应的 .so 文件或包名,说明包是完整的。
3.3 用 createrepo 制作本地软件仓库
如果内网机器不止一台,我强烈建议多做一步,把下载好的目录制作成本地 yum 源。用工具 createrepo 就可以完成,在有网机器上执行:
bash复制yum install -y createrepo
createrepo /opt/gcc-rpms
createrepo 会在 /opt/gcc-rpms 目录下生成 repodata 文件夹,里面存放元数据索引。这个目录接下来会被原封不动打包传到内网机器上。做完这一步,内网机器就不再依赖具体 rpm 文件,而是可以通过 yum 命令像在线安装一样处理依赖。
如果下载的 rpm 目录日后新增了包,需要重新执行一次 createrepo,否则新加入的包不会被识别到。这类本地源在维护内网环境时非常实用,比如后面想加装 make、ncurses-devel、openssl-devel,只要在有网机器上下载好,丢进目录,再刷新一次索引即可。
3.4 完整打包:tar 压缩与校验
最后把整个目录打包压缩,方便传输:
bash复制cd /opt
tar -czf gcc-rpms.tar.gz gcc-rpms
md5sum gcc-rpms.tar.gz > gcc-rpms.tar.gz.md5
md5 校验文件建议顺手生成,传输过程中可以快速判断文件是否损坏。打包时注意,一定要用相对路径或者进入目录后打包,避免把 /opt 这个绝对路径层级打进 tar 包,否则解压到内网机器时需要多一层目录结构。
至于怎么传到内网,常见方式有 scp、sftp、U盘拷贝,或者内网文件服务器分发。如果内网机器和外网完全没有物理连接,就用移动介质拷入,这步没什么技术难点,重点在于保证文件完整。
4. 离线安装实操步骤
4.1 解压与本地源配置
把 gcc-rpms.tar.gz 拷到内网机器后,选择一个固定目录解压,比如 /opt/gcc-rpms:
bash复制tar -xzf gcc-rpms.tar.gz -C /opt
cd /opt/gcc-rpms
确认 rpm 文件都在后,如果你的安装包目录已经用 createrepo 生成了 repodata,那就走本地 yum 源路线。在 /etc/yum.repos.d/ 下新建一个本地源配置文件,比如 gcc-local.repo:
bash复制vi /etc/yum.repos.d/gcc-local.repo
写入以下内容:
ini复制[gcc-local]
name=GCC Local Repository
baseurl=file:///opt/gcc-rpms
enabled=1
gpgcheck=0
注意 baseurl 的格式,file:// 后面跟绝对路径,不要写成 /opt/gcc-rpms 这种少前缀的写法。gpgcheck 设为 0,因为本地 rpm 包没有对应的 GPG 密钥,设 1 会导致校验失败。
配置好后,清理 yum 缓存并重建:
bash复制yum clean all
yum makecache
然后就能像联网一样安装 gcc 了:
bash复制yum install -y gcc gcc-c++
yum 会从本地仓库里自动解决依赖顺序,一次性装完所有需要的包。这是最省心的方式,强烈推荐。
4.2 不走 yum 源,直接用 rpm 批量安装
如果你只装一台机器,不想配置本地 yum 源,也可以在解压后直接用 rpm 批量安装:
bash复制cd /opt/gcc-rpms
rpm -Uvh *.rpm --nodeps --force
这里我用了 --nodeps,目的是跳过依赖检查,一次性把所有包都塞进去。但这种做法的前提是你已经通过 yumdownloader --resolve 确认了依赖完整,所有需要的包都在目录里。如果缺包,--nodeps 会让安装看似成功,实际编译时才发现头文件或库缺失,到时候排查更痛苦。
所以 my 建议是:能走 yum 本地源,就不要用 --nodeps。只有像 glibc-common、glibc 这种系统已经存在且正在被大量程序使用的包需要升级时,才考虑它。因为 rpm 直接升级 glibc 有风险,一旦中途失败或者版本不匹配,系统可能直接崩溃。用 yum 本地源方式安装时,yum 会处理好升级顺序,风险小很多。
4.3 手动指定顺序安装的方式
如果本地源没配成、手里 rpm 也齐全,但就是想手动一个一个装,那么安装顺序要严格遵循从底层到上层的规则:先装 glibc、glibc-common、libgcc、binutils、mpfr、libmpc,再装 cpp、glibc-headers、glibc-devel、kernel-headers,最后才装 gcc、libstdc++、libstdc++-devel、gcc-c++。
手动安装时,按下面这个顺序执行:
bash复制rpm -Uvh glibc-2.17-317.el7.x86_64.rpm glibc-common-2.17-317.el7.x86_64.rpm libgcc-4.8.5-44.el7.x86_64.rpm binutils-2.27-44.base.el7.x86_64.rpm
rpm -Uvh mpfr-3.1.1-4.el7.x86_64.rpm libmpc-1.0.1-3.el7.x86_64.rpm
rpm -Uvh cpp-4.8.5-44.el7.x86_64.rpm glibc-headers-2.17-317.el7.x86_64.rpm kernel-headers-3.10.0-1160.el7.x86_64.rpm glibc-devel-2.17-317.el7.x86_64.rpm
rpm -Uvh gcc-4.8.5-44.el7.x86_64.rpm libstdc++-4.8.5-44.el7.x86_64.rpm
rpm -Uvh libstdc++-devel-4.8.5-44.el7.x86_64.rpm gcc-c++-4.8.5-44.el7.x86_64.rpm
这种安装顺序的本质是遵循依赖方向:先装被依赖的包,再装依赖方。内核头文件和 C 库头文件尽量在编译工具之前就位,能避免后续编译时找不到标准头文件的尴尬。
4.4 验证 gcc 安装成功
装完别急着走,做一遍完整的验证流程。先看版本:
bash复制gcc --version
gcc -v
which gcc
which g++
gcc --version 应该输出 gcc (GCC) 4.8.5 2021 xxx 之类的内容。然后写个最简单的 C 程序测试编译:
bash复制cd /tmp
cat > hello.c << 'EOF'
#include <stdio.h>
int main(void) {
printf("hello, gcc offline ok\n");
return 0;
}
EOF
gcc hello.c -o hello
./hello
能看到 "hello, gcc offline ok" 就说明 gcc 已经能正常工作。紧接着再测 C++ 编译器:
bash复制cat > hello.cpp << 'EOF'
#include <iostream>
int main() {
std::cout << "hello, g++ offline ok" << std::endl;
return 0;
}
EOF
g++ hello.cpp -o hello_cpp
./hello_cpp
如果两个程序都能编译、运行,才真正说明这一步完成。我见过太多同事只跑到 gcc --version 就收工,结果真正编译项目时才发现缺少 make、缺少头文件,又得回头补包。
5. 常见问题与排查实录
5.1 报错:缺少 libmpc.so.2
离线安装 gcc 时最常见的一个报错是:
text复制error: Failed dependencies:
libmpc.so.2()(64bit) is needed by gcc-4.8.5-44.el7.x86_64
这个问题的根源很简单,gcc 依赖 libmpc.so.2 这个共享库,但你的安装包目录里没有 libmpc rpm,或者 libmpc 版本不对。解决办法是回到有网机器,检查 /opt/gcc-rpms 里是否存在 libmpc 包:
bash复制ls -l /opt/gcc-rpms/libmpc*
如果没有,直接补下载:
bash复制yumdownloader --resolve --destdir=/opt/gcc-rpms libmpc
然后重新打包、上传。确定能用正确版本解决的办法,是回看 3.2 节用 yumdownloader --resolve 下载时是否加了 --resolve,这个参数没加,几乎必然出现这个问题。
5.2 报错:stdio.h 不存在
gcc 装好后,编译 hello world 却报错:
text复制hello.c:1:19: fatal error: stdio.h: No such file or directory
这个错误和 gcc 本身无关,原因在于 glibc-devel 没装。stdio.h 这类标准 C 头文件不在 gcc 包里,而是在 glibc-devel 包里。检查一下:
bash复制rpm -q glibc-devel
如果提示未安装,就把 glibc-devel、glibc-headers、kernel-headers 一并补上。这个问题在离线安装中最容易踩,因为在联网机器上执行 yumdownloader 时会自动带全,但一旦手动精简了包列表,就很容易漏掉。
5.3 安装时提示文件和已装包冲突
text复制file /usr/lib/gcc/x86_64-redhat-linux/4.8.5/cc1 from install of gcc-4.8.5-44.el7.x86_64 conflicts with file from package gcc-4.8.5-39.el7.x86_64
这个报错说明系统里已经装过旧版本的 gcc 或相关包。解决办法不是一味加 --force,而是先看旧包是否需要保留。如果不是业务依赖,可以先用 yum 或 rpm 把旧包清理掉:
bash复制rpm -e gcc-4.8.5-39.el7.x86_64
然后再重新安装新包。如果确实需要覆盖安装,可以加 --replacefiles 参数:
bash复制rpm -Uvh gcc-4.8.5-44.el7.x86_64.rpm --replacefiles
但要注意,强制替换文件有风险,可能把系统已有的配置文件或库文件覆盖成不兼容版本。能用 yum 本地源装的话,yum 会采用升级策略,冲突概率小很多。
5.4 yum 报错:无法加载本地源
配置本地源后,执行 yum install 却报错找不到仓库。先检查 repo 文件路径和内容:
bash复制cat /etc/yum.repos.d/gcc-local.repo
再看 baseurl 指向的目录是否存在 repodata:
bash复制ls -l /opt/gcc-rpms/repodata
如果 repodata 不存在,说明 createrepo 那步没做或者没做对。回到有网机器重新执行 createrepo /opt/gcc-rpms,然后重新打包上传。另外还要检查 yum 是否看到这个仓库:
bash复制yum repolist
输出里应该有 gcc-local。如果没有,可能是 .repo 文件权限问题或者文件名没有以 .repo 结尾。
5.5 rpm 数据库被锁
内网机器上如果之前有别的安装进程没结束,rpm 会报:
text复制/var/lib/rpm/.rpm.lock: Permission denied
这种情况多半是 rpm 数据库被进程锁定,或者权限损坏。一般顺序是先检查是否有 rpm 进程在跑:
bash复制ps aux | grep rpm
没有进程的话,可以用 rpm --rebuilddb 重建数据库:
bash复制rpm --rebuilddb
这个命令会重新生成 rpm 数据库索引,耗时几十秒。如果连这个都报错,再考虑检查 /var/lib/rpm 目录权限。我实际遇到过一次,是因为磁盘空间满了,rpm 写数据库失败,清了 /var/tmp 下的垃圾文件后恢复正常。
5.6 为什么 gcc 装完版本还是旧的
还有一种情况:安装过程完全正常,gcc --version 输出 4.8.5,但你觉得版本太旧,想升级到新版本,结果怎么弄都是 4.8.5。这是因为 CentOS 7 的系统仓库里就固定提供 4.8.5,yum 默认版本锁定在系统源。
如果你想用更高版本的 gcc,比如 7.x、8.x,就得额外引入 SCL 软件集或者第三方源,这已经不是离线安装的范畴了。绝大多数内网环境下,能正常编译运行就是目标,版本升级反而会引入新的依赖问题,不建议在没有充分测试的情况下对编译器做跨版本升级。
6. 离线环境下的几个额外建议
这条内容放到最后说,因为它是这次实操后印象最深的部分。离线安装 gcc 不只是把包装完的问题,而是整个离线软件管理习惯的问题。
实际生产里,我现在的做法是每次有网环境下下载完 rpm 包后,不仅打包,还会顺手生成一份依赖清单,记录所有 rpm 包名和版本号,存成文本放进压缩包。这样做的好处是,下次内网机器再遇到同样需求时,可以直接对照清单核验,不需要重新分析依赖。
再有就是优先把本地 yum 源维护好,而不是每次都用 rpm -Uvh *.rpm --nodeps 硬装。rpm 硬装适合临时救急,但内网环境往往要长期维护,依赖越滚越乱,最后连自己都分不清缺什么。一条拉平的方式,就是让本地源成为内网机器的统一安装入口,所有离线软件都通过它装。
还有一点要提醒:glibc、libgcc 这类基础包升级时要格外谨慎,它们是系统底层运行库,几乎所有程序都依赖。离线环境中,如果内核或者底层库版本和之前不完全一致,可能会影响运行中的进程。除非必要,不要在内网生产环境里去动 glibc。需要装 gcc 时,尽量保证现有 glibc 满足 gcc 的依赖要求,而不是反过来强行升级系统基础库。
这次折腾虽然花了两三个小时,但跑下来发现,只要把依赖关系梳理清楚,离线安装 gcc 其实也不算难。关键步骤就三个:在有网同版本机器上把依赖下载全,把本地源建好,然后用 yum 装。把这个流程记下来,内网装 gcc 这种事儿基本就是照作业抄了。
