本文完整阅读约需 19 分钟,如时间较长请考虑收藏后慢慢阅读~

在使用yum的过程中,我们经常会遇到冲突的情况。有时冲突来源于同时安装了多个版本/架构的软件包,但更多时候冲突来源于当前安装的软件与依赖的软件来自不同软件源。由于yum基于rpm,而rpm包名的规则为包名-版本号-发布次数-发行商-Linux平台-适合的硬件平台-包扩展名,这就造成了不同软件源中包名上的差别会直接导致冲突。本文将以一个简单的小例子,来为读者讲解如何解决软件源重复导致的yum冲突。

0x01 环境说明

由于工作中的特殊需求,笔者最近使用了腾讯云基于CentOS二次开发的tlinux操作系统,但在使用过程中却发现tlinux自带软件源经常找不到自己想要的软件。

图1. tlinux软件源中并不包含完整的Perl依赖

由于tlinux在包管理方面只是简单地将CentOS-Base等源更换为腾讯软件源的tlinux源,因此笔者直接禁用了tlinux.repo,并从干净的CentOS镜像中拷贝了CentOS-Base等软件源到tlinux中。

这里由于tlinux修改了yum的$releasever(CentOS 7或tlinux2.2可以通过python -c 'import yum, json; yb = yum.YumBase(); print json.dumps(yb.conf.yumvar, indent=2)'查看,如下图所示),因此笔者将新软件源配置文件中的$releasever批量修改为了7(否则该值为2.2,会因为找不到对应的目录而报错)

图2. tlinux的$releasever与其他相关字段

图3. CentOS的$releasever与其他相关字段

这时候再运行yum install perl就不再报错,能从CentOS-Base中顺利找到perl的所有依赖并正常安装。

看起来万事大吉了?但让笔者没想到的是,『噩梦』才刚刚开始。

0x02 问题说明

某天当笔者尝试在这台服务器上安装Git时,遇到了这样的报错:

图4. 由于软件源不同导致包名不同,进而导致冲突

从报错信息中可以看出,Git的安装需要OpenSSH的7.4p1-21版本,然而当前安装的版本正是这一版本,两个版本的唯一差别就是el7tl2.2

这里笔者尝试卸载tl2.2版本的OpenSSH后再使用el7版本替换之,然而当笔者执行yum remove openssh后,却发现有多个软件依赖该OpenSSH,卸载OpenSSH意味着这些软件也将被连带卸载。为了一个错误安装的软件而去卸载其他数十个软件,很显然是不划算的。于是笔者开始分析这一问题出现的根源。

0x03 问题分析

在着手分析此问题前,我们需要首先了解yum背后的rpm对包名的表示规则。

一个合法的rpm包应该由包名-版本号-发布次数-发行商-Linux平台-适合的硬件平台-包扩展名所组成,且是唯一对应的关系,也就是说openssh-7.4p1-21.tl2.2.x86_64openssh-7.4p1-21.el7.x86_64尽管只是Linux平台的差别,但在rpm和yum看来,这就是一个软件的两个版本,而由于当前安装的OpenSSH与它所需求的『版本』不同,故显示此报错。

了解到这里,可能有些读者已经想出了一种办法:既然新旧软件包的版本一致,能不能使用rpm --nodeps强制卸载OpenSSH,再使用rpm i将正确的OpenSSH安装回来呢?这种方案放在这里当然是有一定可取性的,但使用这种方法势必会遇到以下问题:

  1. 使用tlinux源的底层库可能不止OpenSSH,这意味着我们需要强制卸载大量的库。
  2. 如果卸载了glibc这种库(往往glibc还经常会造成冲突),我们连yum/rpm都用不了,因为Linux下绝大多数使用C/C++开发的软件都依赖glibc(GNU的C库,使用GCC几乎无法避免依赖之)。

强制卸载OpenSSH的方法不可取,那么能不能强制安装Git呢?答案同样是否定的:

  1. 这里我们已经卸载了tlinux源,意味着未来如果需要升级Git版本,OpenSSH的版本是无法跟随之更新的,届时又会遇到新的冲突。
  2. 强制安装Git(即使用rpm)意味着需要手动安装Git的所有依赖,这里包含opensshgroff-basersyncperl等数十个依赖,如果我们以后需要强制安装其他依赖OpenSSH的软件,可能会遇到更多需要手动安装的依赖,不是长久之计。

将以上两种否决原因逆转过来,我们就可以看出解决该问题的三个核心思路:

  1. 必须使用el7的软件包替换tl2.2的软件包
  2. 不能分别卸载旧软件和安装新软件,否则会造成意料之外的问题
  3. 新旧软件版本需要保持一致

0x04 问题解决

明确了核心思路,接下来就是祭出解决问题的『偷梁换柱三板斧』:

偷梁换柱第一式——六脉神剑:yum shell

yum为了方便复杂的批量操作,设计了一个shell便于我们进行这种『先xxx,后xxx』的操作,而且这一操作是连贯的,这就为我们替换库提供了方便:

$ yum shell
> remove openssh-7.4p1-21.tl2.2.x86_64
> install openssh-7.4p1-21.el7.x86_64
> run

图5. 使用yum shell实现连贯的旧软件卸载与新软件安装,不涉及任何依赖问题

这样依赖我们就能将tl2.2的OpenSSH替换为el7的OpenSSH。这里称之为『六脉神剑』是因为yum shell提供了一种沉浸式的体验,让用户以较少的击键次数实现更多的功能,这对于回滚操作、搜索软件包、或是批量安装软件而言有着极大的帮助。

偷梁换柱第二式——乾坤大挪移:yum swap

实际上yum在设计的时候就考虑到了更换软件的情况,并将yum shell替换软件的繁杂操作包装成了一个命令:yum swap

yum官方文档中可以了解到,yum swap设计的目的就是为了便捷yum shell操作:

swap   At it's simplest this is just a simpler way to remove one set of package(s) and install another set of package(s) without having to
          use  the "shell" command.  However you can specify different commands to call than just remove or install, and you can list multiple
          packages (it splits using the "--" marker).  Note that option parsing will remove the first "--" in an argument list on the  command
          line.

其使用方法相对于yum shell而言也是非常简单的,一条命令即可完成:

yum swap -- remove openssh-7.4p1-21.tl2.2.x86_64 -- install openssh-7.4p1-21.el7.x86_64

图6. 使用yum swap实现和yum shell类似的操作,但更加简明

偷梁换柱第三式——黯然销魂掌:dnf –allowerasing

上面所说的两种方法都需要我们进行繁杂的手动操作,如果有大量的软件包需要替换,依旧免不了重复操作。

如何才能做到兵来将挡,水来土掩,化『解冲突』于无形中呢?作为『下一代yum』,dnf贴心的为我们带来了--allowerasing参数。

官方文档同样可以了解到,--allowerasing参数的设计目的就是为了自动化yum swap,做到真正的自动解冲突,即在遇到冲突时优先自动卸载产生冲突的软件包,并安装依赖所需的软件包。

dnf的基本操作和yum基本一致,甚至在Fedora 22和RHEL/CentOS 8之后,将yum作为其软链接,如下图所示。

图7. CentOS 8已默认将yum作为dnf的软链接

如果你还在使用yum,不妨使用alias将包管理器升级为dnf,并享受--allowerasing的快感:

yum install dnf
# 将这一行写入你所使用Shell的启动文件里,如~/.zshrc或~/.bashrc或/etc/profile
alias yum="dnf"

接下来我们需要做的只是执行dnf install --allowerasing install git,然后见证奇迹:

这里我们可以看到,yum(或者称之为dnf更合适)自动解决了冲突,并自动纠正了OpenSSH的版本,无需我们做任何操作!

如果你还是嫌麻烦,可以像笔者一样使用『懒人专属alias』,只是要注意不要与你安装的命令冲突了哟:

alias y="dnf"
alias yi="dnf install --allowerasing"
alias yr="dnf remove"
alias ys="dnf search"
alias ysh="dnf shell"
alias yii="dnf info"
alias yd="dnf list installed"

0x05 问题总结

本文写到这里就差不多结束了,经过笔者多年的实际验证,巧用以上『三板斧』可以帮助读者解决绝大多数由于软件/软件源重复导致的冲突问题,大家可以根据自己的喜好或实际情况灵活使用。而如果是不同软件需要不同版本基础库引起的冲突,笔者还是推荐使用Docker、Ansible或Linuxbrew(强烈推荐Linuxbrew),大部分系统自带包管理器对于多版本并存的处理都非常糟糕,很难找到一个通用的解决方案。

其实包冲突并不可怕,真正可怕的是遇到这类问题的时候没有清晰的思路去解决问题,瞎折腾一通,和电脑硬碰硬,一番混乱的操作之后,搞乱了环境、搞乱了思路、也搞乱了心绪,才算是前功尽弃。

尽管重复折腾的确能起到巩固知识的作用,但相比较而言,发现问题——分析问题——解决问题——总结问题的『心流体验』明显更加高效,也能帮助我们少走许多弯路,在前进的路上走得更快乐,也更踏实。衷心希望这篇文章在普及yum技巧的同时,为读者带来些许启示。