先に、このマシンで何をしているか
Linux ディストロの比較記事を読むと、「結局どれが一番いいのか」という話になりがちだけど、あまり意味がないと思っている。
何をするマシンなのかで答えが変わるからだ。
私の環境はこんな感じ。
- Rust の重いビルドを日常的に回す
- Android SDK / NDK を使う
- Android Emulator をヘッドレスで起動して QA を回す
- Forgejo をセルフホストしている
- 同じ構成の Arch を 2 つ、デュアルブートで持っている
Rust のフルビルドは普通に重い。target も巨大になるし、sccache やキャッシュの置き場所を雑にすると、それだけでかなり時間を食う。
ローカル LLM は数十 GB の GGUF を置くし、Android Emulator も動く。つまりこの PC は、軽量な Linux デスクトップを楽しむための箱ではない。
最新の開発ツールと、外から持ってきた大量のバイナリを、なるべく余計なことを考えずに動かしたい。
これが一番大きい。
その条件で Gentoo、NixOS、Ubuntu、Fedora を考えた結果、今は Arch に落ち着いている。
Arch が一番優れた Linux だから、ではない。
私の用途では一番シンプルであっただけだ。
Gentoo — 面白い。でも私にはほとんど意味がなかった
Gentoo を単に「全部コンパイルする面倒な Linux」と言うのは違う。
Gentoo にしかない良さはちゃんとある。
USE フラグで余計な機能を落とせるし、-march=native で自分の CPU 向けにビルドできる。パッチを当てたパッケージを自分で持ち続けるのもやりやすい。musl を使ったり、普通のディストロではあまりやらない構成を作る自由度も高い。
この辺は Arch より明らかに強い。
ただ、自分の環境を見たときに、そのメリットがほぼ効かなかった。
まず、多少システムを軽くしてもあまり意味がない。
2TB の NVMe で容量でOS を数 GB 削ったところで誤差になる。
CPU 最適化も同じだった。
この PC で CPU を食っているのは、自分のアプリのコンパイルと、自分のアプリそのものだ。
glibc やシステムライブラリが多少速くなったとしても、そこを頑張るより自分のビルド設定を詰めたほうが効く。
実際、sccache の使い方を見直したり、CARGO_TARGET_DIR の置き場所を変えたりしたほうが、Gentoo に移るよりずっと大きな差が出る。
それなのに OS の更新でもコンパイル時間を払うことになる。
自分のアプリのビルド時間を減らすためにいろいろやっているのに、その横で OS をコンパイルするのは、どうしても無駄に感じた。
今の Gentoo はバイナリパッケージもかなり使えるので、「Gentoo = 何でも全部コンパイル」というわけではない。
ただ、バイナリ主体で使うなら、今度は「じゃあ自分は何のために Gentoo を選ぶんだろう」という話になる。
自分でパッチを持ちたいとか、USE フラグで細かく構成を削りたいとか、そういう理由がある人にはかなり良いと思う。
私には、その理由がなかった。
NixOS — 一番迷った
NixOS はかなり迷った。
というのも、今の私の運用が NixOS に向いている部分もあるからだ。
私は Arch を 2 つ入れていて、なるべく同じ構成にしている。
何を入れたか、何を同期するかを台帳に書いて、片方で設定を変えたらもう片方にも反映する。
正直、ここだけ見ると NixOS のほうがきれいだ。
設定をコードにして、それを正とする。
2 つの環境を同じ定義から作ればいい。
これはかなり魅力的だった。
それでも NixOS にしなかった最大の理由は、自分の環境には Nix の外からやってくるバイナリが多すぎたことだった。
普通の Linux を想定したバイナリをそのまま動かしたい
NixOS は普通の Linux とファイルの置き方がかなり違う。
ライブラリは /usr/lib に並ぶのではなく、Nix Store のハッシュ付きパスに入る。
Nix の中で完結するなら、それでいい。
問題は、私がそういう使い方をしていないことだ。
rustup で入るツールチェーン、Android SDK、NDK、Emulator 関係の配布バイナリ。
こういう「Linux 用バイナリです。そのまま実行してください」というものがかなり多い。
普通のディストロなら大抵そのまま動く。
NixOS だと、そうならないものがある。
buildFHSEnv を使ったり、patchelf したり、Nix 用のパッケージを探したりすれば動かせる。
でも、私が気になったのは「動かせるか」ではなくて、毎回そこを考えたくないということだった。
Android Emulator が特に嫌だった
ここがかなり大きい。
Android Emulator は Google が配布している巨大なプレビルドバイナリで、QEMU やグラフィックス周りまで抱えている。
しかもホストの GPU ドライバや Vulkan / OpenGL とつながる。
こういうものは、NixOS の思想とあまり相性が良くない。
もちろん NixOS でも Android Studio は使えるし、Android SDK を扱う仕組みもある。
調べれば動かしている人もたくさんいる。
ただ、私は Emulator をたまに手で起動するだけではなくて、ヘッドレスで QA に使っている。
つまり開発環境の一部ではなく、テスト基盤の一部になっている。
そこで何か更新が入るたびに、
「今回はライブラリの見え方が変わったのか」
「GPU 周りか」
「Nix 側のパッケージの問題か」
みたいな切り分けをしたくなかった。
Arch なら Google が想定している普通の Linux にかなり近い。
SDK を入れて、Emulator を起動する。
それで済む。
私にはその普通さのほうが価値があった。
Nix の再現性は魅力的だけど、すでに別の方法でかなり解けていた
もう一つ考えたのが、Nix の一番大きな売りである再現性だった。
これは間違いなく強い。
ただ、自分の環境を見返してみると、かなりの部分はすでに別の方法で固定できていた。
Rust はツールチェーンを固定できる。
依存関係は Cargo.lock がある。
Android には Gradle Wrapper がある。
dotfiles は Git に入れている。
サービスは systemd unit にできる。
必要なら Docker も使える。
つまり、「環境が違うせいでビルドできない」という問題のかなりの部分は、OS より上のレイヤーですでに潰せていた。
Nix には flake で macOS と Linux の開発環境を丸ごと揃えられる、という強みもある。
私が作っているのは iOS・Mac・Windows・Android のアプリだ。
iOS と Mac のビルドには Xcode が要る。
そして Xcode は Nix では管理できない。
Windows に至っては、Nix はネイティブ対応していない。WSL 越しだけだ。
Android の SDK、NDK、Emulator は、さっき書いたとおり Nix が一番苦手な種類のバイナリ。
つまり 4 つのプラットフォーム全部で、核になるツールチェーンが Nix の外にある。
そして実際のクロスプラットフォームの再現性は、rustup や Gradle Wrapper や lockfile みたいな、OS を選ばない言語側の固定がすでに担っていた。
もちろん NixOS ほど徹底した再現性ではない。
システム全体を世代管理できるわけでもないし、ロールバックの仕組みも NixOS のほうがきれいだ。
でも、そこまで含めて Nix を覚える価値が、自分にはあるのか。
そこを考えたとき、合理的ではないと判断した。
Nix を勉強する時間があるなら Rust やアプリ本体に使いたい。
NixOS はかなり良いと思う。
ただ、私の環境では「NixOS だから楽になる部分」より、「NixOS だから考えることになる部分」のほうが多そうだった。
Ubuntu — 悪くない。でも結局、外から足していくことになる
Ubuntu は安定している。
特に LTS は大きく環境が変わらない。
サーバーなら、それはかなり大きな利点だと思う。
ただ、開発機として使うと話が変わる。
Rust 自体は rustup を使えばいいので、Ubuntu の Rust が古いことはあまり問題にならない。
問題はその周辺だ。
新しい開発ツール、新しいシステムライブラリ、新しいデスクトップ周りのソフトを使いたくなると、
PPA を追加したり、
ベンダーのリポジトリを足したり、
tarball を直接置いたり、
AppImage を使ったり、
ということが増える。
もちろん普通に使える。
ただ、自分の場合は「安定した Ubuntu の上に新しいものを継ぎ足す」構成がだんだん増えていく。
それなら最初から全体が新しい Arch のほうが、自分には分かりやすかった。
Arch なら pacman で入る。
なければ AUR を見る。
大抵そこで終わる。
自分にとっては、Ubuntu の安定性より、この単純さのほうがありがたかった。
Fedora — 期待していたほど楽ではなかった
Fedora もかなり有力だった。
Ubuntu ほど古くなく、Arch ほど全部がローリングでもない。
開発機にはちょうど良さそうに見える。
実際、しばらく Arch と Fedora をデュアルブートで使っていた。
でも、結局 Fedora はやめた。
理由は単純で、自分の環境では細かい問題を何度も踏んだからだ。
一つ一つは致命的ではなかったし、調べれば直せる。
ただ、それをやっている途中で思った。
Fedora の問題を調べている時間で、Arch 側を整備したほうがよくないか。
Fedora が不安定なディストロだと言いたいわけではない。
一般的にはかなり評価が高いし、実際に安定して使っている人も多い。
でも「世間で安定している」と「自分の PC で面倒が起きない」は別の話だ。
さらに面倒だったのが、Arch と Fedora を両方維持することだった。
同じことをするにもパッケージ名が違う。
設定の場所が違うこともある。
アップデートの考え方も少し違う。
Arch 側で解決した方法を、そのまま Fedora 側には持っていけない。
OS が 2 つあることより、流儀が 2 つあることのほうが面倒だった。
そこで Fedora を消して、Arch を 2 コピーにした。
同じ Arch なら片方で作った手順をそのままもう片方に使える。
私にはそのほうが圧倒的に楽だった。
Arch はそのままだと特別安定しているわけではない
ここは大事なところ。
Arch にしたから全部安定したわけではない。
むしろ安定させたのは、その後にやった設定のほうだと思っている。
そして、その設定のほとんどは Arch 専用でもない。
カーネルは普通の linux、ファイルシステムは ext4
カーネルは標準の linux。
zen も hardened も使っていない。
ファイルシステムは ext4。
スワップは 32GB の普通のスワップファイル。
かなり地味な構成だ。
Arch を使っているからといって、全部を尖らせる必要はないと思っている。
ユーザーランドは新しくていい。
でも、下のほうまで実験的にする必要はない。
btrfs にしなかった理由
btrfs は最後まで迷った。
スナップショットがあるし、圧縮もある。
Arch なら btrfs + Snapper でロールバック環境を作っている人も多い。
でも、この PC では Rust のビルドがかなり多い。
target ディレクトリに大量のファイルを書いて、消して、また書く。
こういう用途で CoW の挙動まで気にし始めると面倒だった。
ディレクトリ単位で CoW を切る方法もある。
ビルドだけ別の場所に逃がす方法もある。
でも、「そこまでして btrfs にする必要が自分にあるか」と考えたら、なかった。
欲しいのは主にアップデート前の復旧手段だった。
それなら Timeshift の rsync モードでも足りる。
コードは Git にある。
だったら ext4 でいい。
それだけの判断だった。
zram ではなく実スワップ
これも似た理由。
ローカル LLM、Android Emulator、Rust のビルドを一緒に動かすと、RAM をかなり使う。
zram は便利だけど、結局 RAM を圧縮して使っている。
私は「本当に RAM の外へ逃がせる場所」が欲しかった。
なので 32GB のスワップファイルを置いている。
zram と併用する選択肢もあるけど、今は挙動が分かりやすい実スワップだけにしている。
更新する前に戻れるようにしておく
Arch で怖いのは更新そのものより、
更新後に問題が出たとき戻れないこと
だと思っている。
なので更新前に Timeshift を取る。
ext4 なので rsync ベース。
さらに pacman の古いパッケージも数世代残している。
何か一つのパッケージだけ戻したいなら、Timeshift より pacman cache のほうが早いこともある。
reflector、paccache、fstrim みたいな定期整備は systemd timer に投げている。
「たまに思い出して人間がやる」という運用は、そのうち忘れる。
だからなるべく自動化する。
AUR はあまり入れない
AUR は便利。
でも便利だからといって大量に入れると、それはそれで面倒になる。
私は常時 10 個前後。
基本は公式リポジトリ。
どうしてもないものだけ AUR。
AUR が特別危険だとは思っていない。
PKGBUILD はただのシェルスクリプトで、入れる前に読めば何をするか全部見えるし、yay は更新のたびに diff を見せてくれる。
そもそも「公式にないものを外から持ち込む」リスク自体は、PPA でも COPR でも curl | bash でも大差ない。
中身を読んでから入れられるぶん、AUR はまだ透明なほうだと思う。
AUR のパッケージが壊れたとき、Arch が壊れたように感じることがあるけど、実際には自分で持ち込んだ外部パッケージが原因だった、ということもある。
なので少ないほうがいい。
/tmp で Rust をビルドして痛い目を見た
この PC の /tmp は tmpfs。
さらに容量制限をかけている。
tmpfs は中身が RAM に乗るので、暴走したプロセスひとつに RAM ごと食い尽くされないための保険だ。
あるとき /tmp に作業用の worktree を置いて、そのまま cargo build した。
Rust の target がどんどん膨らんで、制限まで食い切った。
すると、そのユーザーで動いていた別の処理まで一気におかしくなった。
一時ファイルが作れない。
書き込みが失敗する。
並行していたシェルまで変な落ち方をする。
最初は何が起きたのか分からなかった。
原因は単純で、/tmp を使い切っていただけだった。
それ以来、使い捨て worktree でも target は必ずディスク側に出している。
CARGO_TARGET_DIR を ~/.cache 以下に置く。
容量制限を作るなら、その外側に大きなデータの逃げ道も作る。
当たり前だけど、一度壊すまでちゃんと分かっていなかった。
sccache はどこでも使えばいいわけではなかった
sccache も最初は全部に入れれば速くなると思っていた。
実際にはそうでもなかった。
使い捨て worktree ではかなり効く。
別の worktree を作るたびに依存クレートを最初からビルドするような状況では、キャッシュがそのまま使えるからだ。
なので、そういう場所では
RUSTC_WRAPPER=sccache CARGO_INCREMENTAL=0
で回している。
逆にメインの作業ディレクトリでは Cargo の incremental compilation のほうを優先している。
少しコードを直して、すぐもう一度ビルドする。
普段の開発ではこっちのほうが多い。
sccache のキャッシュも 10GiB では足りなくて、50GB に増やした。
あと一度ハマったのが、設定を変えても既に起動している sccache server には反映されないものがあること。
環境変数を変えたのに容量が増えない。
何度見直しても設定は合っている。
結局 server を再起動したら反映された。
こういうところを一つずつ潰した効果のほうが、Arch と Gentoo の差なんかよりずっと大きかった。
壊れないようにするより、壊れても戻れるようにする
コードは Forgejo と外部の Git リモートに push している。
Forgejo 自体もバックアップを取る。
Arch は 2 コピーある。
片方がダメになったら、もう片方から作業できる。
同期するもの、しないものも台帳に書いている。
要するに、
「この OS は壊れない」と信用しているわけではない。
壊れても戻れるようにしている。
ちなみにTTYで修正する程度の問題はあったが、壊れたことはない。
Arch をローリングリリースのまま普通に使えている理由は、たぶんこっちのほうが大きい。
結局、この設定は Arch じゃなくてもできる
ここまで書いておいてなんだけど、このチューニングの大半は Ubuntu でも Fedora でもできる。
ext4 は使える。
普通の swap も使える。
sccache も Cargo の機能。
CARGO_TARGET_DIR も Arch とは関係ない。
Forgejo も systemd timer も Arch 専用ではない。
バックアップも同じ。
つまり、今の環境が安定している理由を全部 Arch の手柄にするのは違うと思っている。
Arch を選んだ理由はもっと単純だ。
余計な摩擦が少なかった。
新しいソフトが欲しい。
大抵 pacman か AUR にある。
普通の Linux 用バイナリを落としてきた。
大抵そのまま動く。
問題が起きた。
Arch Wiki にかなりの確率で情報がある。
自分が欲しかったのは、結局これだった。
まとめ
Gentoo は面白いし、できることも多い。
でも私には、その自由度を使う理由がなかった。
NixOS は再現性という意味では一番魅力的だった。
ただ、Android Emulator を含む大量の外部バイナリを扱う自分の環境では、そのために増える摩擦のほうが気になった。
Ubuntu は安定している。
でも新しいものを求めるほど、外から足すものが増える。
Fedora は期待して使ったけれど、私の環境では細かい問題が続いた。それに Arch と Fedora の二つの流儀を覚えて維持するのが面倒だった。
そして Arch。
別に完璧ではない。
更新で問題が出ることもある。
AUR だって雑に使えば壊れる。
でも今の私の仕事では、一番余計なことを考えずに済んだ。
だから使っている。
ディストロの信頼性よりも、結局効いたのは自分で作った復旧手段やビルド環境だった。
ディストロは、自分の仕事を一番邪魔しないものを選べばいい。
その上の環境は、自分で作る。