
1. 为什么 CentOS 7 离不开 SCL先搞清楚它到底解决什么问题1.1 系统自带仓库最让人头疼的一件事用 CentOS 7 的人应该都有过这种经历装完系统后想装个新一点的开发工具结果yum install gcc一敲装出来的还是 GCC 4.8.5。这版本老到什么程度C11 支持得磕磕绊绊C14 基本别想稍微新一点的项目一编译就是一堆报错。不是说 CentOS 7 不想给你新版本而是它的定位决定了它必须“稳”。服务器系统讲究的是长时间不重启、不折腾软件版本冻结反而是一种特性。Red Hat 官方只对系统自带的那套工具链做安全补丁和 bug 修复绝不给你升大版本因为升了大版本可能引入新的兼容性问题这在生产环境是致命的。问题来了我既想要 CentOS 7 的稳定又想要新版本软件的功能怎么办两个办法。第一个是去第三方源找 rpm 包比如 EPEL、IUS、Remi这些源的维护者会把新版软件重新打包成适合 CentOS 7 的 rpm。第二个就是今天要聊的 SCLSoftware Collections软件集合。SCL 的出现时间其实挺早Red Hat 从 RHEL 6 时代就开始推了但真正被国内运维圈大规模使用还是因为 CentOS 7 时代大家对 DevToolsetGCC 高版本套件和 Python 高版本的需求激增。移动互联网那几年Python 3 开始普及而 CentOS 7 自带的 Python 只有 2.7这逼着大量运维和开发去折腾 SCL。1.2 SCL 的核心机制平行安装按需启用SCL 的设计思路很有意思它不跟系统默认软件硬碰硬而是采用“平行安装”的路线。普通软件升级是覆盖式的比如系统里有一个 Python 2.7你从源码编译一个 Python 3.8 装到/usr/local/bin那 PATH 里哪个在前面就用哪个这是一种“替换式”方案。SCL 的做法不一样它把所有软件都装到一个独立目录比如/opt/rh/rh-python38/root/usr/或者/opt/rh/devtoolset-9/root/usr/然后通过一个enable脚本来临时修改环境变量让你在“当前这个 shell 会话”里优先使用 SCL 版本的命令。也就是说系统自带的/usr/bin/python还在你随时可以用回旧版本SCL 装的 Python 3.8 在/opt/rh/rh-python38/root/usr/bin/python你只有执行scl enable rh-python38 bash或者source /opt/rh/rh-python38/enable之后当前 shell 的 PATH 才会把 SCL 目录提到最前面。这种设计的最大好处就是互不干扰。你用 SCL 的 Python 3.8 跑应用跑坏了系统自带的 Python 2.7 还是完好无损连依赖都不会互相污染。我见过不止一个同事因为贪图省事直接往/usr下编译装新版 Python结果搞得整个系统的 yum 脚本全部报错因为 yum 本身也是用 Python 2.7 写的把系统的默认 Python 替换掉之后一堆系统工具直接瘫痪。1.3 和 EPEL、AppStream 有什么不一样很多人会把 SCL 和 EPEL 弄混毕竟都是“第三方源”但两者定位完全不同。EPEL 的全称是 Extra Packages for Enterprise Linux它给 RHEL/CentOS 提供的是“系统标准仓库里没有的软件包”但它不会刻意去升级已经存在的软件版本。你可以在 EPEL 里装到 Redis、Nginx、MySQL 这种 CentOS 自带仓库里不存在的软件但如果 CentOS 自带 GCC 4.8EPEL 不会给你提供一个 GCC 9。SCL 则恰恰相反它专门干“升级既有软件”的事同时又能保证这些升级不干扰系统默认版本。打个不严谨但好理解的比方EPEL 是给你的菜谱里加新菜SCL 是把一道老菜用新的做法重新做一份但老菜你还能继续吃。CentOS 8 时代的 AppStream 模块化机制其实很多设计思路上跟 SCL 有相似之处但实现方式更丰富支持了模块流切换。不过 CentOS 7 上没有 AppStreamSCL 就是最正牌的选择。2. 安装 SCL 源官方仓库与国内镜像源两手准备2.1 先装基础包centos-release-scl 全家桶安装 SCL 源的第一步并不复杂本质上是装一个centos-release-scl包这个包的作用就是把 SCL 相关的 repo 文件放到/etc/yum.repos.d/目录下。在 CentOS 7 上执行yum install -y centos-release-scl装完之后你会发现/etc/yum.repos.d/下多出两个文件CentOS-SCLo-scl.repo对应sclo仓库这里面是社区打包的软件集合比如新版 Git、PHP、Ruby、Node.js、MariaDB 等。CentOS-SCLo-scl-rh.repo对应rh仓库这是 Red Hat 官方维护的软件集合比如 devtoolset、rh-python36、rh-nodejs10 等。两个仓库的定位区别直接看名字就行rh开头的软件包通常更保守、更偏向企业级sclo开头的软件包相对新一些、社区味道更浓。如果你不确定装哪个优先选rh仓库里的版本稳定性更好。我用yum list available验证过装完之后能看到类似这样的软件包yum list available | grep scl输出结果里会出现rh-python38.x86_64、devtoolset-9、rh-nodejs12之类的东西说明源已经生效了。如果啥都搜不到多半是 repo 文件的enabled字段有问题后面排查部分会细说。2.2 国内镜像源替换阿里云、清华源怎么配这里必须泼一盆冷水如果你在服务器上直接yum install centos-release-scl然后立刻去装软件大概率会卡在下载环节。SCL 仓库的文件目录在国内访问速度很不稳定特别是sclo仓库里面动不动就是几百 MB 的大包下载超时是家常便饭。所以务实的做法是装完centos-release-scl之后马上把 repo 文件里的baseurl换成国内镜像。我用阿里云镜像和清华镜像都做过替换两种方式都验证过可用。先说清华镜像的替换方法。先备份原文件这是个好习惯cp /etc/yum.repos.d/CentOS-SCLo-scl.repo /etc/yum.repos.d/CentOS-SCLo-scl.repo.bak cp /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo.bak注意不同来源的centos-release-scl包生成的 repo 文件内容不完全一样。有的版本用的是mirror.centos.org的路径有的用的是vault.centos.org。但不管哪种核心操作都是把baseurl那一行指向镜像地址。清华镜像的 SCL 路径格式是https://mirrors.tuna.tsinghua.edu.cn/centos/7/sclo/x86_64/sclo/ https://mirrors.tuna.tsinghua.edu.cn/centos/7/sclo/x86_64/rh/如果 repo 文件里是mirrorlist开头的写法建议直接把整个文件改写成如下内容是最干净的办法。我贴一个我常用的CentOS-SCLo-scl-rh.repo完整配置[centos-sclo-rh] nameCentOS-7 - SCLo rh baseurlhttps://mirrors.tuna.tsinghua.edu.cn/centos/7/sclo/x86_64/rh/ gpgcheck1 enabled1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo照葫芦画瓢把sclo仓库也改一下路径换成.../x86_64/sclo/其他不动。然后清理缓存重新加载yum clean all yum makecache阿里云镜像的路径格式是https://mirrors.aliyun.com/centos/7/sclo/x86_64/rh/ https://mirrors.aliyun.com/centos/7/sclo/x86_64/sclo/配置文件写法完全一样就是把baseurl的域名换成mirrors.aliyun.com。还有一点值得注意gpgkey的路径。有些版本的 repo 文件写的 key 路径是file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo但这个文件不一定存在。如果yum install报错说 GPG key 找不到把gpgcheck先临时改成0装包之后再改回来或者直接去/etc/pki/rpm-gpg/目录看看有没有对应的 key 文件没有就把 gpgcheck 关掉SCL 仓库本身的安全性还好主要看你用它的环境是不是内网隔离。2.3 验证源是否生效清楚看到能装什么配置完镜像源之后别急着装软件先做一轮验证确认源真的通了并且能拉取到软件列表。yum repolist enabled | grep sclo如果能看到两个仓库并且状态是 enabled说明 repo 文件没问题。再执行yum list available | grep devtoolset能列出devtoolset-9、devtoolset-10、devtoolset-11这些包说明 rh 仓库拉取正常。搜 Pythonyum list available | grep rh-python能看到rh-python36、rh-python38等说明 python 相关的集合也都在。这里我建议多等一会儿让yum makecache完全跑完因为 SCL 仓库的 metadata 很大第一次拉取可能要几十秒。有一点很多人会踩坑SCL 仓库的包名非常长比如rh-python38-python-devel它跟系统默认的python-devel不是同一个包不能混着装。SCL 里几乎每个软件都有自己独立的一套依赖不会去复用系统的库。你在 SCL 的 Python 里装 pip 包pip 的路径也是独立在/opt/rh/rh-python38/root/usr/bin/pip跟系统的 pip 互不干扰。3. 实战通过 SCL 装出“新版本”软件并正确启用3.1 用 SCL 安装 GCC 9 并启用编译器环境光有源不装软件等于白搭我用开发者工具链来演示一套完整流程这个场景也是 SCL 最常见的用途。yum install -y devtoolset-9这个包是个元包会把 GCC、G、GDB、Make 等一整套开发工具都拉进来。装完之后系统里会多出/opt/rh/devtoolset-9/目录。你直接敲gcc --version看到的还是系统自带的 4.8.5别慌SCL 就是这么个“你不叫我我不出来”的脾气。要启用 SCL 的 GCC 9有两种方式。第一种是用scl命令scl enable devtoolset-9 bash这条命令的意思是启动一个新的 bash 会话并且在这个会话里把所有 devtoolset-9 的环境变量都注入进去。执行完之后你再敲gcc --version就会看到gcc (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)第二种方式是直接 source enable 脚本source /opt/rh/devtoolset-9/enable这两条命令的效果基本一样都是修改当前 shell 的环境变量让gcc指向/opt/rh/devtoolset-9/root/usr/bin/gcc。3.2 scl enable 背后到底做了什么好这里有个关键问题得讲清楚scl enable devtoolset-9 bash到底干了什么SCL 的 enable 机制本质上就是环境变量注入。每个软件集合的目录下都会有一个enable脚本里面写了一系列export命令把 PATH、LD_LIBRARY_PATH、MANPATH、PKG_CONFIG_PATH 这些变量指向 SCL 的根目录。比如 devtoolset-9 的 enable 脚本里大致就是这样的逻辑export PATH/opt/rh/devtoolset-9/root/usr/bin:$PATH export LD_LIBRARY_PATH/opt/rh/devtoolset-9/root/usr/lib64:$LD_LIBRARY_PATH export MANPATH/opt/rh/devtoolset-9/root/usr/share/man:$MANPATH注意scl enable devtoolset-9 bash是在一个新的子 shell里执行的。你在这个子 shell 里设置的所有环境变量一旦退出这个 shell 就全部失效。这个设计是故意的就是为了让你临时切换环境而不会影响系统全局。正因为这个机制很多人刚接触 SCL 时会觉得“这也太麻烦了每次登录都要敲一遍”。解决办法也不难看下一节。3.3 让 SCL 环境在登录时自动生效持久化如果你希望某个用户一登录就能直接用 SCL 的 GCC 9可以把 enable 脚本的执行写进~/.bashrcecho source /opt/rh/devtoolset-9/enable ~/.bashrc source ~/.bashrc这样每次打开终端devtoolset-9 的环境变量就会自动注入。注意这只是对当前用户生效不影响系统其他用户。如果你想让所有用户都默认启用可以写到/etc/profile.d/目录下比如新建一个/etc/profile.d/devtoolset-9.sh内容也是一行 source。我个人的建议是除非你真的需要全局默认否则别这么干。SCL 最大的价值就是“隔离”你把它变成全局默认那跟直接覆盖系统 GCC 有什么区别分分钟又会踩回 Python 替换那种坑。我在生产环境里的习惯是项目目录里放一个env.sh里面写好需要的 SCL 环境变量要用的时候source env.sh。这样不同项目可以平滑切换不同的 SCL 集合互不打架。3.4 systemd 服务如何吃到 SCL 环境还有一个高频场景你写了一个服务想让它跑在 SCL 的软件环境里但 systemd 启动的进程是不会读~/.bashrc的。这个坑我踩得记忆犹新。有个 Python 3.8 写的服务通过 SCL 装好了 rh-python38手动在终端里跑好好的一写成 systemd service 启动就报错说找不到 Python 模块因为它用的还是系统的 Python 2.7 环境。解决方案是在 service 文件里手动指定 SCL 的可执行文件路径或者带上完整的环境变量。比如[Service] EnvironmentPATH/opt/rh/rh-python38/root/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin EnvironmentLD_LIBRARY_PATH/opt/rh/rh-python38/root/usr/lib64 ExecStart/opt/rh/rh-python38/root/usr/bin/python3 /opt/myapp/app.py这种写法虽然繁琐但最可靠。另一种做法是用 bash 包装一层ExecStart/bin/bash -lc source /opt/rh/rh-python38/enable exec /opt/myapp/run.sh用exec替换 bash 进程保证信号能正确传给应用。我记得有一次没加exec服务 stop 的时候 systemd 把 bash 杀了但 Python 进程成了孤儿进程继续跑最后端口一直被占用排查了半天才找到原因。4. 常见问题与排查技巧实录4.1 官方源 404、下载慢、镜像过期CentOS 7 已经进入 EOL 之后的维护期官方源里的很多路径都挪到了 vault 归档目录下所以如果你装的是某个旧版centos-release-scl包里面的baseurl可能指向一个已经不存在的路径一执行yum makecache就报 404。报错长这样http://mirror.centos.org/centos/7/sclo/x86_64/rh/repodata/repomd.xml: [Errno 14] HTTP Error 404 - Not Found解决办法就是之前说的那套干脆别管原文件写的是什么直接手动改写成清华或阿里云的镜像地址。国内公司一般都会有内网 yum 源如果有的话优先配内网源速度最快还不受外网波动影响。还有一个小细节yum clean all yum makecache之后如果还是报错可以检查一下/etc/yum.repos.d/里是否有别的 repo 文件里也包含了 scl 的 url两个 repo 的定义冲突了也会导致拉取异常。我之前遇到过装了centos-release-scl之后又装了 epel结果 epel 里也有 scl 相关的包定义导致 yum 解析包依赖时二选一出问题。4.2 版本没变化PATH 没生效有一次我在服务器上执行scl enable devtoolset-9 bash之后敲gcc --version显示的居然还是 4.8.5。查了半天最后发现问题出在环境变量顺序上。enable脚本是把 SCL 的路径加到 PATH最前面但如果你的~/.bashrc或者/etc/profile里有其他地方把/usr/bin又加到了 PATH 前面或者执行了hash -r重置了命令缓存就会导致gcc还是命中系统的旧版本。解决办法很简单先which gcc看看到底指向哪个路径。如果确实指向/usr/bin/gcc手动执行hash -r这是 bash 的命令哈希缓存它会把之前解析过的gcc路径缓存起来。你换了 PATH 之后如果缓存没刷新它还是用旧路径。另外提醒一下SSH 远程登录时scl enable之后的环境只在当前连接里有效断开重连就会消失这是正常现象不是配置出错了。4.3 想要更多软件常用 SCL 集合代号速查不同版本的 SCL 集合命名有规律掌握这个规律能省很多时间。我整理了一份常用对照表软件类型SCL 集合名称安装后的启用命令GCC 编译器套件devtoolset-7scl enable devtoolset-7 bashGCC 编译器套件devtoolset-9scl enable devtoolset-9 bashGCC 编译器套件devtoolset-11scl enable devtoolset-11 bashPython 3.6rh-python36scl enable rh-python36 bashPython 3.8rh-python38scl enable rh-python38 bashPython 3.9rh-python39scl enable rh-python39 bashNode.js 10rh-nodejs10scl enable rh-nodejs10 bashNode.js 12rh-nodejs12scl enable rh-nodejs12 bashPHP 7.2rh-php72scl enable rh-php72 bashPHP 7.3rh-php73scl enable rh-php73 bashRuby 2.5rh-ruby25scl enable rh-ruby25 bashNginx 1.14rh-nginx114scl enable rh-nginx114 bashRedis 5rh-redis5scl enable rh-redis5 bashMariaDB 10.3rh-mariadb103scl enable rh-mariadb103 bashGit 2.xsclo-git212scl enable sclo-git212 bash注意几点第一rh-php72安装之后php命令在/opt/rh/rh-php72/root/usr/bin/phpphp-fpm单独叫rh-php72-php-fpm跟系统的 php-fpm 完全不同。第二有些软件集合的 service 脚本名也会带前缀比如systemctl start rh-php72-php-fpm。第三如果你看到devtoolset-11搜不到那很正常因为 SCL 里 devtoolset 最新到 11 甚至 12但具体取决于你的 SCL 仓库版本和系统补丁级别先yum clean all yum makecache刷新一下再看。4.4 和“博途 SCL”混淆先确认自己找的是哪个热词里出现了“scl语言”、“博途scl和梯形图”这里我得做一个顺手的澄清免得有人搜攻略搜到我这篇文章然后一脸懵。工业自动化领域也有一个“SCL”全称是 Structured Control Language结构化控制语言是西门子 TIA Portal博途里用来写 PLC 程序的一种文本化编程语言。它的地位等同于工业界的“高级编程语言”比梯形图更接近通用语言适合实现复杂算法和数据处理。这跟 Linux 里的 Software Collections 完全是两个东西同名同缩写纯粹是巧合。如果你是想找博途 SCL 的资料那看到“CentOS 7 安装 SCL 源”这篇文章可以直接关掉了。如果你是想在 Linux 服务器上装新版编译器、Python、Node.js那这篇文章就是为你写的。我的判断标准很简单你在搜索框里打“SCL”之前脑子里想的是“服务器”“yum”“编译器环境”那你来对地方了你脑子里想的是“PLC”“梯形图”“WinCC”“TIA Portal”那你应该去搜“博途 SCL 编程”。这两条技术路线的工具链、方法论、社区都完全不同混在一起只会浪费时间。4.5 依赖冲突、GPG key 报错一锅端处理法yum install devtoolset-9时报依赖冲突或者 GPG key 找不到这种问题也不少见我总结几个高效的处理思路。GPG key 报错的典型提示是GPG key retrieval failed: [Errno 14] Could not open key file: /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo原因就是 repo 文件里写的 key 路径对不上。最快的处理办法是验证一下 key 文件是否存在ls /etc/pki/rpm-gpg/ | grep SCLo如果没有相关的 key 文件把 repo 配置里的gpgcheck1改成gpgcheck0在保证网络环境可信的前提下这个操作风险很低。毕竟 SCL 仓库里的包不是系统核心包而且你用的是国内镜像源来源相对可控。依赖冲突一般出现在同时启用了多个第三方源比如 EPEL、IUS、SCL 同时开着一些包的版本交叉了。解决思路也简单yum install的时候显式指定 SCL 的包名不带通配符让 yum 知道你到底要装哪个仓库的版本。如果实在冲突得厉害可以临时禁用掉其他仓库yum install --disablerepoepel,ius --enablerepocentos-sclo-rh devtoolset-9这种“开着飞机换引擎”的操作能不动就尽量别动生产环境里少一个变量就少一分风险。我个人在实际操作中最深刻的体会是SCL 这套机制最大的价值不是“能装新版本”而是“装新版本的同时不破坏旧环境”。CentOS 7 的老旧让你心烦但它的稳定也不该轻易舍弃。SCL 给了你一个两全的路子代价是学习一点环境变量的概念以及习惯于“按需启用”这种在我看来相当优雅的哲学。配置好镜像源之后以后每个 SCL 集合的安装就是一趟顺畅的yum install加一行 source整个过程熟悉之后十分钟内就能搞定。最后再分享一个小技巧如果你经常在几个不同的 SCL 环境之间切换写个小函数放到~/.bashrc里比如启用 devtoolset 就敲一个ds9启用 rh-python38 就敲一个py38省下的时间虽然不多但每次切换时少打那串命令心情会好很多。