Nokogiri が version `GLIBC_2.29' not found で読み込めない件

# はじめに

AlmaLinux 上で動いている，とある Rails プロジェクトにて。

Nokogiri のバージョンを 1.18.0 にしたところ，動かなくなった。
たとえば

```shell
rails -T
```

しただけで

```
/lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/rbenv/versions/3.4.1/lib/ruby/gems/3.4.0/gems/nokogiri-1.18.0-x86_64-linux-gnu/lib/nokogiri/3.4/nokogiri.so) - /usr/local/rbenv/versions/3.4.1/lib/ruby/gems/3.4.0/gems/nokogiri-1.18.0-x86_64-linux-gnu/lib/nokogiri/3.4/nokogiri.so
```

とエラーが出る。

2022 年にも（各種バージョンは違うが）ほぼ同じ問題によって，あちこちで記事が書かれたようだ。

## 環境

* OS ver. AlmaLinux release 8.10 (Cerulean Leopard)  Kernel 4.18.0-553.33.1.el8_10.x86_64
* ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-linux]
* Bundler version 2.6.2

# 原因

nokogiri は C で書かれた部分を含む拡張ライブラリーだが，主要なプラットフォーム向けに，C 部分のコンパイル済みパッケージも用意されている。
（このあたり，正確な用語を知らないので，間違ってたら教えてください）

そのため，対応 OS ではインストール時にコンパイラーが走らなくてよい。

ところが，今回の問題は，そのコンパイル済みのやつがシステムの環境と合ってないために起きたようだ。

AlmaLinux 8.10 は glibc 2.28 がインストールされているらしい。
このことは `dnf info glibc` で確認できる。

一方，インストールした最新の nokogiri は

```console 
$ gem list nokogiri
*** LOCAL GEMS ***

nokogiri (1.18.0 x86_64-linux-gnu)
```

なのだが，こいつは glibc 2.29 を要求するらしい。惜しい，ぎりぎりダメじゃん。

Rails プロジェクトじゃなくても，単に

```
ruby -r nokogiri -e "puts Nokogiri::VERSION"
```

とやっただけで冒頭に掲げたエラーが出る。
（エラーメッセージの全体は長いが省略）

# 対策

エラーメッセージの中には，対処法がちゃんと書かれている。
私のように英語が苦手な人も，「あー，ゴチャゴチャなに言ってるかわかんねー，まーいーや，エラーメッセージでググっちゃえー」などと思わず，分からないなりに読もうとしてみるのがいいと思うよ。

件の「`GLIBC_2.29' not found」の直後に

```
  If that's the case, then please install Nokogiri via the `ruby` platform gem:
      gem install nokogiri --platform=ruby
  or:
      bundle config set force_ruby_platform true
```

とあった。

要するに「platform を `ruby` として nokogiri をインストールしなはれ」と言っている。

これが何を意味するかというと，C 部分がコンパイル済みのやつをインストールするんじゃなくて，手元でコンパイルするようにインストールする，と。
（それがなんで「platform が ruby」なのかよく分からんが）

やり方としては

```shell
gem install nokogiri --platform=ruby
```

と

```shell
bundle config set force_ruby_platform true
```

と二つある，と。親切に具体的なコマンドまで書いてくれている。

## まずは `--platform=ruby` で

これは今までにも使ったことがあるオプションだ。

ではまず

```shell
gem uninstall nokogiri
```

でアンインストールしておいて，書かれているとおり

```shell
gem install nokogiri --platform=ruby
```

でインストール。
コンパイラーが走るので，約 1 分かかる。たるい。

この状態で Nokogiri が読めるか確認すると……

```console
$ ruby -r nokogiri -e "puts Nokogiri::VERSION"
1.18.0
```

よっしゃー，解決！

……となれば話は簡単なのだが，Rails アプリ（に限らずなんらかのプロジェクト）でうっかり

```shell
bundle update
```

しようものなら，nokogiri 1.18.0 が入っているにもかかわらず，同バージョンのコンパイル済み版（nokogiri-1.18.0-x86_64-linux-gnu）が新たにインストールされてしまう。
ひ〜

## force_ruby_platform 設定を試す

では次に

```shell
bundle config set force_ruby_platform true
```

を検討しよう。
よく分からんが，いかにも「`platform=ruby` を強制する」感じがする。

上記のコマンドを実行すると，Bundler の設定が変わるようだ。
この「設定」というのは，プロジェクトディレクトリー（`Gemfile` を置くところ）直下の `.bundle/config` と，ユーザーのホームディレクトリー直下の `.bundle/config` に書かれている（というか，そこから読み取るようになっている）。

これを，件の Rails アプリのディレクトリーで実行すると，その直下の `.bundle/config` に

```
BUNDLE_FORCE_RUBY_PLATFORM: "true"
```

が追加された。
オプションなしでも，グローバルじゃなくてローカルの設定の変更になるのね。
（ちなみに，Gemfile の存在しないディレクトリーでやってみたら，`.bundle/config` は作られなかった。エラーも出なかった）

この状態なら

```shell
bundle update
```

しても，「コンパイル済み版」がインストールされたりはしなかった。
めでたしめでたし。

しかし，これをやると，nokogiri だけでなく全部の gem で「自前コンパイル」になるんだよな？　マジかよ。

## glibc をバージョンアップするのは？

2022 年に多数書かれた記事の情報によると，glibc のバージョンを上げよう，という方向の解決方法はダメぽい（OS にがっつり噛んでいるので）。

AlmaLinux 8 はメンテナンスサポートが 2029 年 3 月 1 日までと長く使えるので安心していたが，こういうところでトラブルがいろいろ生じるのだろうなあ。

ちなみに AlmaLinux 9.5 で確認したところ，glibc のバージョンは 2.34 なので，こちらは余裕で大丈夫。


# 感想

* 疲れた。
* gem 1 個のインストールに 1 分もかかる（nokogiri の場合）のは嫌だな。
* この設定で全ての gem についてコンパイル済みが利用できなくなるのは嫌だな。
* gem のインストール時には何も起こらず，`require` で初めて問題が起きるのは嫌だな。
* AlmaLinux のサポート期間が長いのはいいけど，こういう問題を回避するためにアップデートを検討しなければならないとは……。

# 追記　2025-01-07

Nokogiri のリポジトリーに本件の issue が立っていた（すでにクローズされている）：
[\[bug\] v1.18.x requiring glibc 2.29 (Rocky 8.10 ships with 2.28) · Issue #3399 · sparklemotion/nokogiri](https://github.com/sparklemotion/nokogiri/issues/3399)

直接の原因は Nokogiri が C のプリコンパイルに使っている [rake-compiler-dock](https://github.com/rake-compiler/rake-compiler-dock) において glibc の最小サポートバージョンが引き上げられたことであるようだ。
なんで引き上げたのかは，どうも CentOS のサポート終了が原因だったようなのだが[^cse]，ええと，AlmaLinux 8 も Rocky Linux 8 も 2029 年まで生きてますよ？

rake-compiler-dock の [History.md](https://github.com/rake-compiler/rake-compiler-dock/blob/c4e7dc390e0757891ad8c7898953f87a35c957dc/History.md) を見ると，この変更は 2024-12-13 の v1.6.0 で行われたようだ。

[^cse]: 私の英語力では，そういう解釈で正しいのかよく分からん。

それはともかく，issue 主さんの記述を見ていて，私が本記事で書いたのとは別の対策があることを知った。

つまり，Gemfile に

```rb:Gemfile
gem "nokogiri", force_ruby_platform: true
```

のように書けばよい。
`gem` メソッドの `force_ruby_platform` なんてオプション知らなかったぞ。

このやり方であれば，他の gem のインストールに影響を与えることなく，nokogiri だけ自前コンパイルにできる。
これは `.bundle/config` で Bundler の動作を変える方法に比べて大きな利点だ。

しかし，短所もある。
Gemfile に書いちゃうわけなので，「本番サーバーでは自前コンパイルになってほしいが，手元の開発環境ではコンパイル済み版で」ということができない（しにくい）。