运维日志:升级 Mastodon 时遇到的 Ruby 版本问题

· 630 words · 3 minute read

昨天晚上终于考完了一科的期末考试,让我有了一些时间来升级 Mastodon 实例。我的实例 从去年十二月中旬建立起就没升级过,一直是 v4.0.2,而现在都 v4.1.2 了,是时候升级 一下了。于是,我开始动身处理这件事。

升级都比较常规,而且 Mastodon 在这块儿的文档也比较通俗易懂。只需要 Fetch Tags 并 Checkout 到对应 Tag,再 Precompile assets、bundle installyarn install 最后再重启服务即可。

我见到 Mastodon 更新了建议的 Ruby 版本,建议使用 3.0.6,也提供了一个 rubyenv 命令来更新 Ruby 版本。我照做了,但在执行的过程中发现跑得非常慢,一看日志才知道 原来它在编译所有依赖!刚编译完了 OpenSSL,现在正在编译 Ruby,这如何接受得了? 我猜它是为了锁 Ruby 版本,所以建议大家都用 RubyEnv 来固定 Ruby 版本。这让我感到 十分恶心,因为我很喜欢用发行版的包管理来装任何软件。于是,我就索性把 RubyEnv 的 环境变量从 ~/.bashrc 里删去,然后用 pacman -S ruby ruby-bundler 装了 Ruby 3.0.5 和 Bundle。我看到 Ruby 还带下来了很多 ruby-* 的包,这才是应该的嘛!

然后我照例重启服务(一开始忘记 Precompile assets 了),修复了一下 Ruby 二进制的 路径,….. boom,炸了。我访问网站,提示 500,每次访问都会有如下日志:

Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c] method=GET path=/getting-started format=html controller=HomeController action=index status=500 error='ActionView::Template::Error: Unknown alias: defaults' duration=100.79 view=0.00 db=3.58
Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c]
Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c] ActionView::Template::Error (Unknown alias: defaults):
Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c]     1: - thumbnail     = @instance_presenter.thumbnail
Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c]     2: - description ||= @instance_presenter.description.presence || strip_tags(t('about.about_mastodon_html'))
Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c]     3:
Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c]     4: %meta{ name: 'description', content: description }/
Apr 20 20:36:54 mastodon bundle[27971]: [b9bb60fd-4b2b-4f39-9092-34743b8ed51c]     5:

而在我 Precompile assets 的时候也出了同样的错误。 Boom,我本来有些困的,这下完全不困了,就像早上喝咖啡的时候把咖啡洒到电脑上了一样 提神。

除了提神,我也开始头大了,因为我完全不会 Ruby,对 Bundle / Gem 什么的一窍不通, 这该怎么调试呀?

初步诊断 #

我首先上网搜了一下 Unknown alias: defaults 这个错误,找到了一个 StackOverflow 问题,和我的情况非常相似。看回答,似乎是因为一个名为 Psych 的裤升级大版本而有 breaking change 导致的。我也不知道这个裤是干嘛的,难道和心理学有关系? 于是我读了一下 Ruby 官方 Bug Tracker 的那个工单,发现这个裤改动了一个 API。然而,Mastodon 目录下的小文件非常多,在机 械盘上跑 grep 找到这个 API 的所有使用或者找到所有用这个裤的地方根本不现实。同 时,要想改也非常不现实,因为我完全不会 Ruby。在 GitHub 里搜索,也完全找不到任何 同这个裤有关的蛛丝马迹(关键词包括 psych defaults 等)。难道是什么间接依赖 坏了?

继续上网搜索,发现了 Mastodon 也有两个 Issue (1, 2 和这个问题有关,而且似乎 都和 Ruby 3.1 有关系。我又在 GitHub Repo 里搜了一下,发现和这个裤有关的东西不多, 但似乎都和 Ruby 3.1 兼容性有关。的确,我又看了看上面那个 Bug Tracker,发现这个 Psych 实际上是 Ruby 的一个标准库,而 3.1 版本的 Ruby 会自带 Psych 4,但 3.0.x Ruby 自带的是 Psych 3,造成了这个 Breaking Change。

依赖版本 #

继续浏览 GitHub,看到上面其中一个 Issue 和我有完全一致的情况:都在用 Arch,都在 用 Arch 仓库里的 Ruby,都在跑一个东西的时候产生了同样的错误。然而,那个人似乎也 没搞懂,TA 在下面评论道,重新装了一下,问题就解决了,好像是他们手动修改过 Gemfile 导致的,然后就把 Issue 关了。于是,我试图重装一下,因为我之前似乎好像确实动过 Gemfile,可能和 pam 那个 Gem 有关。然而,重装之后报错完全一致,且我对比了 Git 里的 Gemfile 和 Gemfile.lock,没有任何修改。

我又再一次地检查了 Gemfile 和 Gemfile.lock,里面同样没有任何同 Psych 有关的东西。 或许 Psych 是某个裤的依赖?又或许因为它是标准库所以就没有写进去?总之,我还试图 在 Gemfile 末尾加了一条 gem 'psych', '< 4',但一跑 bundle install 就报错,让 我关掉 Deployment mode 什么的,我不懂,怕搞坏,就删掉了。

为了验证 Psych 是否是某个 Gem 的依赖,我上网查了一下怎么列出 Gemfile 项目的依赖 树。得到的命令是 gem dep。我在 Mastodon 目录里跑了一下,发现果真有 psych-4.0.6, 且是被另外一个 Gem 依赖的(Gem rdoc-6.4.0, psych (>= 4.0.0))。然而,这个 Gem 明确写了要求 psych >= 4.0.0,那如果我装一个 3.x 的 Psych,不就一定会出问题吗? 看来这样做不行。我想起我在操作之前对着整个目录打了一个 Btrfs Snapshot,我就进去 也跑了一下这个命令,试图看看升级前的依赖树是什么样。然而结果却令我很疑惑:它同样 也有 psych-4.0.6。这就很奇怪了,升级前一切工作正常,不可能有 4.x 的 Psych。我 思考了一下后突然想到:或许这个 gem dep 命令显示的是系统 Ruby 的 stdlib 依赖树, 和项目完全无关。事实也印证了这个想法,尤其是这个命令还跑的 gem(1) 而不是 bundle(1)。 果然,就在告诉我 gem dep 那个 StackOverflow 回答 里,答主还说了一句,用 bundle exec gem dependency 来看当前 Gemfile 的依赖树。 这才是我要找的嘛。然而,里面确实有几条 Psych 相关,但都是依赖很久的版本:

Gem cocoon-1.2.15
  ...
  psych (~> 2.2, development)

Gem mime-types-data-3.2022.0105
  ...
  psych (~> 3.0, development)

看起来,它和 Psych 版本并没有直接关系。

发现端倪 #

我毫无头绪地滚动着终端,忽然一条 pacman(1) 的结果引起了我的注意:系统有一个 ruby-psych 包,它的版本是 4.0.6。上面的 Ruby bug tracker 里说过,Ruby 3.0 是自带 Psych 3 的,只有 Ruby 3.1 才会自带 Psych 4。其实这也是我一直疑惑的点:为 什么我这会有 Psych 4。看到 pacman 的结果,令我突然意识到,这个包是 pacman 装的, 而它就位于装 Ruby 时的那一大串依赖里。或许是 Arch 包的版本有了问题,3.0.5 的 Ruby 却依赖了 4.0.6 的 Psych。这或许是不对的。

于是,我抱着试一试的态度打开了 Arch Linux Archive,试图找到之前版本的 Ruby。然而 试图降级的时候 pacman 报了一堆文件冲突的错,看上去是旧版 Ruby 的很多文件被这些 ruby-* 的包占了。或许我需要一起降级 Ruby 和那些依赖的包。因此我就去搜了一下 ruby-psych 的历史版本,但发现在 Arch 仓库里它只有 4.x,完全没有 3.x。这就很迷 惑了,因为 Arch Linux Archive 大概不会删除旧包,而这个包的历史也不是很久,因此只 有一种可能,那就是 Arch 压根没打过 Psych 3.x 的包。

因此,我开始怀疑是不是 Arch 拆包拆错了。毕竟 Psych 是 Ruby 的标准库之一,Arch 单独提供 ruby-psych 包有可能是把它拆了,而拆错了版本。我就找了一个比较早期的 Ruby 包的版本,也是 3.x,而当时 ruby-psych 包还没出现或还没有成为 Ruby 的依赖。 我删掉了所有 Ruby 包,重装这个旧的,果然没有那一堆 ruby-* 的依赖了。然后再次 Precompile Assets,完美运行,网站上线。当然,还出了个小插曲,当时的 Ruby 包还在 依赖老的 OpenSSL,而老仓库(我直接把 Mirrorlist 里的仓库改成了当时的 Arch Archive 仓库,因为考虑到 Ruby 会依赖好多别的包,怕装错)里根本没有 openssl-1.1,于是我 临时把仓库换回最新的,装好 OpenSSL,再换回去。还好没有遇到什么二进制兼容问题。

于是,网站就这样上线了。整个调试过程历时 50 分钟。

真相大白 #

但网站上线了还不够,我还要搞明白到底是什么地方坏了。毕竟不能无端指责 Arch 打错了 包对吧。顺着刚才的头绪,我开始在 Arch Archive 里一点一点找,也翻了 Arch Repo 的 Commit History。找了大概二十分钟,最终发现:

  1. 2022 年 1 月 9 日:Arch Repo 建立了独立的 ruby-psych 包
  2. 2022 年 7 月 12 日:Arch Repo 将 Ruby 包(3.0.4)拆包。准确地说,这不是拆包,因为 Arch 并没有通过同一个 PKGBUILD 来编译出好几个 stdlib 包,而是粗暴地将 stdlib 文件从编译好的包中删除了。可能这是考虑到其他 stdlib 组件已经在其他包里编译了吧。
  3. 在随后的日子里,Arch Repo 继续剔除 Ruby 包,将越来越多的 stdlib 组件删除。
  4. 2022 年 7 月 15 日是最后一个 Ruby 3.0.4-1 包的日期。在 7 月 16 日,Arch 重新 编译 Ruby 包,pkgrel 改成了 2,也就是 3.0.4-2,删除了 stdlib 组件,依赖独立编 译的 stdlib 包。

因此,2022 年 7 月 15 日是最后个可用的 Ruby 版本。查到这里已经非常明确了:Arch 想拆包,但没有好好拆。他们独立建立了很多 Ruby stdlib 包,但他们忽略了一件事: Ruby 3.0.4 自带的是 Psych 3,而他们独立编译的 Psych 包是 4.x,从来就没编译过 Psych 3

至此,真相大白。对于这个问题,Psych 搞 Breaking Change 肯定不对(保留之前的 API 行为有什么不好嘛?),以及 Mastodon 锁版本也确实不好,同时我或许也不该自己折腾 换 Arch Repo 里的 Ruby。然而我觉得最直接的问题,还应该是 Arch 的打包问题,即他们 应该同步 stdlib 和 Ruby 本体的版本,或者更好地,直接让 Ruby 包拆包出来,不要再 编译一次。

我搜了一下 Arch Bug Tracker,发现有人之前就发现了这个问题,但一直没有得到解决: https://bugs.archlinux.org/task/76004。 我去下面评论了一下 Mastodon 遇到的这个问题。同时也去 GitHub Issue 下面贴了一下自己的发现。我不知道楼主是怎么弄成的,或许 TA 用了 RubyEnv 吧。

总之,前前后后花了一个多小时。对于我在完全不懂 Ruby / Gem 的情况下能一个小时 把这个问题彻底搞懂,甚至还有点沾沾自喜(