My First Impressions of Nix

Michael Lynch

Nixを初めて使ってみた感想

原文は Michael Lynch により に公開されました。 このブログを購読する

Nixは、ソースファイルに基づいてソフトウェア環境を構成するためのツールだ。最近、Hacker NewsやTwitterでNixの話題をますます耳にするようになった。その考え方に惹かれ、ここ数週間いじってみている。

Infrastructure as Codeとのこれまでの歩み

10年前、コンピューターシステムの構成をソースコードで定義できるツールSaltに出会った。自分のPCやVMにどのサービスがインストールされているかをGitリポジトリで定義できるという発想が気に入った。マシンを更地にしても、構成ツールを再実行すれば同じ状態に戻せるのだ。

数年間Saltをいじった後、同じ発想をよりうまく実現していると感じたAnsibleに出会った。

開発はすべて自宅のホームラボサーバー上のVMで行っている。プロジェクトごとに別のVMを用意し、すべてAnsibleで管理している。

Ansibleの問題点

Ansibleの最大の問題は、とにかく遅いことだ。私のVMの1台に対して実行するだけでも、だいたい10〜15分かかる。

たとえば新しいaptパッケージfooを入れたいとする。sudo apt install --yes fooを実行して5秒で終わらせるか、それともAnsibleのroleを開いてfooをインストールするステップを追加し、playbookを実行して15分待つか。もちろん、前者を選ぶことが多くなり、結果として環境はそれを表現するはずのAnsibleファイルからどんどん乖離していく。

もう一つの問題は、Ansibleの変更に後方互換性がないことだ。新しい機能を使うために一つのplaybookを更新すると、互換性を保つためにすべてのplaybookを更新しなければならなくなる。でも、すべてのplaybookを書き直してテストし直す気にはなれず、古いバージョンに縛られてしまう。今でも3年前にリリースされたAnsible 2.9を使い続けている。

Nixの魅力

NixやNixOSについて語る人をますます見かけるようになった。自分が面白いと思う開発者の多くが、Nixを試した経験を語っている。

中でも特に印象に残ったのは、VagrantやPacker、Consul、Terraformといった広く使われているオープンソースのインフラツールを数多く生み出してきたHashicorpの共同創業者Mitchell Hashimotoによる推薦だ。彼はNixを「ここ数年で学んだ技術の中で、最も良い影響を与えてくれたナンバーワンの技術」と評していた。

これまでのNixの歩み。ここ数年で学んだ技術の中で最も良い影響を与えたという評価は今も変わらない。

NixというアイデアはAnsibleとよく似ていると感じる。コードで構成を定義し、システムをその状態にするというものだ。

NixとAnsibleの違い

NixはAnsibleといくつか重要な点で異なっており、そこが面白いと感じている。

NixはAnsibleより速い

Ansibleには、構成対象のシステムの「状態」という概念がない。私のVMでAnsibleの実行に15分かかる場合、1分後に同じplaybookを再実行しても約10分かかる。2回の実行の間にパッケージの更新はないだろうから多少は時間が短縮されるが、それでもほぼ同じ作業をやり直している。

Ansibleは「さっき構成したばかりだから、もうやることはないな」とはならない。前回実行してから何が起きたかわからないため、毎回すべての構成をやり直さなければならないのだ。

一方のNixには状態という概念がある。200行のNix構成のうち1行だけを変更した場合、残りの199行の作業をやり直す必要はない。システムの状態を構成ファイルと照らし合わせて、1行分の変更だけを適用すればいいと判断できる。その変更はたいてい数秒で終わる。

追記(2023-06-19):より詳しい読者から、Nixは自分が想定していたような意味での状態を持っているわけではないと指摘があった

Nixが速いのはステートフルだからではない。関数型的で再現可能だから速いのだ。それにより正確性を損なうことなくキャッシュが可能になっている。

私は、Nixがどのタスクを実行してどのシステム状態に至ったかを記録しているのだと思っていた。たとえばタスクXとYを実行すると状態Aになり、X、Y、Zを実行すると状態Bになるとする。状態AにあるNixシステムは、状態Bに行くにはタスクZだけを実行すればいいとわかっているのだろうと考えていた。

今の理解では、状態AにあるNixシステムに状態Bへ移行するよう求めると、NixはタスクX、Y、Zをすべて実行する。ただしXとYの結果はキャッシュされているため、ほぼ一瞬で終わるということだ。

Nixはローカルでの構成に最適化されている

Ansibleはネットワーク越しにシステムを構成するために設計されている。localhostをターゲットに指定することもできるが、それはAnsibleが最適化している使い方ではない。

Nixは今いる環境そのものを構成するために設計されている。Nixでは環境に何があるべきかを定義すると、その場でその環境を作ってくれる。NixOSではOS全体を定義し、NixOSがOSをその状態にしてくれる。ファイルシステムやLinuxカーネル、ブートローダーといった低レイヤーの部分まで変更できる。

これは、Ansibleで一つのplaybookを新しいバージョンに対応させようとするとシステム上のすべてのplaybookを更新しなければならないという問題を解決してくれる。Nixなら、異なるバージョンのNixで動くシステムを複数持っていても問題なく共存できる。Ansible 2.9が必要なファイル、2.10が必要なファイル、2.14が必要なファイルが混在していると、それらをやりくりするのはかなり面倒だ。

Nixの変更はアトミック

Ansibleでは、構成の途中で失敗してシステムが中途半端な状態で放置されることがよくある。

Nixでは変更はアトミックだ。システムを望む状態にするか、さもなければ構成変更を試す前の状態にロールバックするかのどちらかになる。

役に立ったNix関連リソース

Nixについてよく見かける不満の一つは、ドキュメントが不足していたり、間違っていたり、質が低かったりすることだ。私の実感でも、ドキュメントは経験豊富なNixユーザー向けに書かれているように感じる。

見つかるドキュメントの多くは「単にこれらの行を追加してください!」といった調子だ。

は?

どのファイルの、どこに追加すればいいんだ?

これまでに見つけた中で一番役に立ったリソースは以下の通りだ:

  • Zero to Nix:これまで見つけた中で最高のNix入門チュートリアルだ。Determinate Systemsが書いており、同社はNixの使い方について初心者向けのブログ記事も公開している。
  • NixOS for the Impatient:この記事を読む前に何度かNixOSのインストールを試していたが、この記事で思ったより簡単だと確信でき、最後までやり切る後押しになった。
  • Julia Evansによる「Some notes on using nix」:JuliaもNix初心者なので、彼女がどんな壁にぶつかり、エコシステムの多くがまだ新しく馴染みがない中でどう乗り越えたかを見るのが参考になった。

失敗その1:VMでのNixOS

ProxmoxのVMで「NixOS for the Impatient」のチュートリアル通りに進めたところ、最初はすべてうまくいった。チュートリアルでホスト名を変更する箇所まで来て、反映のために再起動した。するとVMが奇妙な状態になり、起動はするのにログインできないようになった。ログイン画面でパスワードを入力すると、画面がそのままフリーズしてしまうのだ。

NixOSはProxmox VMサーバーに正常にインストールされたが、2回目の起動ではログイン後にハングした。

失敗その2:Raspberry Pi 4でのNixOS

VMがダメだったので、次はベアメタルが妥当だと考えた。手元に予備のRaspberry Pi 4があったので、実験するには面白いハードウェアだと思った。

Raspberry Pi 4にNixOSをインストールするための、一見公式っぽいチュートリアルを2つ見つけた:

どちらのチュートリアルも、すでにNix環境が動いていることが前提になっていた。私はメインのWin10マシンからmicroSDを用意しようとしていたので、まだNixは入っていなかった。

NixOSのダウンロードページには64ビットARM用イメージが載っている。Raspberry Pi 4は64ビットARMをサポートしているので、それを試してみることにした。

microSDの書き込みにいつも使っているBalena Etcherを起動した。最初の異変は、Etcherが「おい、何考えてるんだ?これはブート可能なイメージですらないぞ」と言わんばかりの警告を出したことだった。

パーティションテーブルが見つかりません。これはブート可能なイメージではないようです。イメージにパーティションテーブルが含まれていないようで、デバイスに認識されないか、ブートできない可能性があります。

それでも強行した!しかしmicroSDからブートしようとすると、Piもこれがブート可能なイメージではないと判断したようで、そのまま固まってしまった。

『Progress: Trying boot mode USB-MSD』と表示されたPiのブート画面

同じイメージを公式のRaspberry Pi Imagerでも書き込んでみたが、結果は同じだった。

追記:最終的に動かすことができた

成功:DellのミニPCでのNixOS

仕事のテストの多くはDell Optiplex 7040で行っている。それが手元で更地にできる唯一のベアメタルマシンだったので、そこで試してみることにした。

すべてが「NixOS for the Impatient」の通りにうまくいった。インストールは開始から終了まで約10分で、7分はファイルのコピーだった。テスト用デバイスなので暗号化はスキップし、再起動ごとのパスワード入力を省くことにした。

Dell Optiplex 7040へのNixOSのインストール(ファイルコピー部分は早送り)

最終的に、完全に動作するNixOSをインストールできた!

失敗その3:Raspberry Pi 4でのNixOS(再挑戦)

動作するNixOSマシンが手に入ったので、Raspberry Piにもう一度挑戦したくなった。前回の障壁はmicroSDイメージを用意するためのNix環境がなかったことだったが、今はそれがある。

nix.devのチュートリアルに従って進めたところ、前回よりは先に進めたものの、やはりブートできなかった。Piはカラフルな画面が表示されたところで止まってしまうのだ:

NixOSシステムからNixOS Pi aarch64のmicroSDイメージを書き込み、それでPiをブートすると、カラフルな画面でハングした。

追記:最終的に動かすことができた

タスク1:SSHアクセスの確保

さて、Dell Optiplex上の動作しているNixOSの話に戻ろう。

メインのマシンからNixOSシステムにSSHで入る必要があった。そのためにはSSH鍵をシステムに置かなければならない。

NixOSでコンソールアプリを開き、sudo nano /etc/nixos/configuration.nixと入力した。そしてenvironment.systemPackagesのところに以下の行を追加した:

  environment.systemPackages = with pkgs; [
    vim
    curl
  ];

変更を反映するために、以下を実行した:

sudo nixos-rebuild switch

これでvimcurlが使えるようになったので、GitHubからSSH公開鍵を取得できた:

sudo mkdir -p /etc/nixos/ssh

GITHUB_USERNAME='mtlynch'
curl "https://github.com/${GITHUB_USERNAME}.keys" | \
  sudo tee --append /etc/nixos/ssh/authorized_keys

次にsudo vim /etc/nixos/configuration.nixを実行し、以下の行を追加した:

  # Enable the OpenSSH daemon.
  services.openssh.enable = true;
  users.users.mike.openssh.authorizedKeys.keyFiles = [
    /etc/nixos/ssh/authorized_keys
  ];

最後に再ビルドして再起動した。再起動が本当に必要だったかどうかはわからない:

sudo nixos-rebuild switch && sudo reboot

そしてうまくいった!これでメインのコンピューターからNixOSシステムにSSHで入れるようになった。

タスク2:Gnomeの不要なアプリを削除する

OSで最初に気になったのは、不要なものがたくさんあることだった。連絡先や天気アプリなど、要らないビルトインアプリがごちゃごちゃ入っていた:

削除方法を調べたところ、これらはGnome Shellのデフォルトアプリだということがわかった。/etc/nixos/configuration.nixに以下の行を追加すれば無効にできる:

services.gnome.core-utilities.enable = false;

あるいは、ユーティリティを一つずつ除外することもできる:

 environment.gnome.excludePackages = with pkgs.gnome; [
    baobab      # disk usage analyzer
    cheese      # photo booth
    eog         # image viewer
    epiphany    # web browser
    gedit       # text editor
    simple-scan # document scanner
    totem       # video player
    yelp        # help viewer
    evince      # document viewer
    file-roller # archive manager
    geary       # email client
    seahorse    # password manager

    gnome-calculator
    gnome-calendar
    gnome-characters
    gnome-clocks
    gnome-contacts
    gnome-font-viewer
    gnome-logs
    gnome-maps
    gnome-music
    gnome-screenshot
    gnome-system-monitor
    gnome-weather
    gnome-disk-utility
    pkgs.gnome-connections
  ];

私は「すべて無効にする」方を選び、再ビルドした:

sudo nixos-rebuild switch

すると見事に、ごちゃごちゃしたものがすべて消えた:

タスク3:システムモニターを戻す(失敗)

残しておきたいと思ったGnomeツールはシステムモニターだけだった。environment.systemPackagesのリストに追加してみたが、再ビルドに失敗した:

$ sudo nixos-rebuild switch
building Nix...
building the system configuration...
error: undefined variable 'gnome-system-monitor'

       at /etc/nixos/configuration.nix:130:5:

          129|     curl
          130|     gnome-system-monitor
             |     ^
          131|   ];
(use '--show-trace' to show detailed location information)

gnome-shell-system-monitorなど他の名前も試してみたが、インストール方法がわからなかった。

追記(2023-06-19):正しいパッケージ名はgnome.gnome-system-monitorだと教えてくれた読者に感謝したい。自分に欠けていたのは、search.nixos.orgでパッケージを検索できるということだった。

次に理解したいこと

NixとNixOSでの最初の数日間には満足している。聞いていた通りの印象だ。うまく使えばとてつもなく強力になりそうだが、初期投資が大きく、情報をかき集める必要がある。

まだ表面的なところしか触れていないので、次にNixについて学びたいことを挙げておく。

NixOSシステムでVS Code Remote SSHを使う

開発はすべてVS CodeのRemote SSH経由で行っている。VS CodeからNixOSシステムにリモート接続しようとすると、インストールに失敗する。VS Codeはターゲットシステムに何らかのサーバーをインストールする必要があるのだが、おそらくNixOSへのインストール方法を認識していないのだろう。

nixos-vscode-serverというGitリポジトリがあり、おそらくそれが解決策になるはずだ。まだ試してはいない。

Nixの主要な概念がどうつながっているのか

「flakes」や「derivations」といった言葉を目にするが、今のところ意味がわからない。Nixの言語構文も理解していないが、JavaScriptやPythonに似ているので今のところはなんとなくごまかせている。ただ、Nixを効果的に使うには当然言語を学ぶ必要があるだろう。

決定性はいつ担保されるのか

Nixについての議論でよく目にするトップの特徴の一つが、Nixは決定論的(deterministic)であるということだ。

今のところ、どう決定論的なのかがわからない。インストールするパッケージを指定したとき、バージョン番号はおろか整合性ハッシュすら指定しなかった。同じNix構成を1年後に実行したら、指定したvimcurlのバージョンが変わっているため、違うシステムができあがるはずだ。

もっと正確にパッケージバージョンを指定する方法があるのだろうが、まだ学んでいない。

誰を信頼しているのか

パッケージを指定したとき、それは単なるパッケージ名のリストだった:

  environment.systemPackages = with pkgs; [
    vim
    curl
  ];

上のようにパッケージを指定できるということは、Nixがデフォルトのリポジトリからパッケージを取得しているはずだ。リポジトリは複数あるのだろうか?どのリポジトリを使うかはどうやって選ぶのだろう?

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント