PK
パーネルカニック研究所
今日もどこかでkernel panic。
root@pk-lab:~#

chrootを理解する

連載ナビ

この連載は「Linuxを使えるようになる」ではなく、「Linuxがどう動いているのか」を理解するための実践シリーズです。

今回は Linuxを理解する 〜 LFSで学ぶLinux内部構造 〜 の Part 4 / 第5回として、仕組みのつながりを意識しながら学びます。

—

chrootは「プロセスから見えるルートディレクトリ」を切り替える仕組み

Part 4の締めくくりは chroot です。結論から言うと、chrootとは特定のプロセスとその子プロセスにとっての「ルートディレクトリ /」を、指定したディレクトリに切り替えるシステムコール(および同名のコマンド)です。

たとえば chroot /mnt/lfs /bin/bash を実行すると、起動されたbashにとっては /mnt/lfs が / になります。そのbashが /usr/bin/gcc を開こうとすると、カーネルはパス解決の起点を切り替えているので、実際にはホストの /mnt/lfs/usr/bin/gcc が開かれます。ディスクの中身は1バイトも動いていません。変わるのは「そのプロセスのパス解決の起点」だけ。この一点を押さえれば、chrootの動作はすべて説明がつきます。

作りかけの新システムに、ホストの部品を混ぜないため

なぜLFSでchrootが必須なのでしょうか。理由は、ビルド中の新システムを、ホスト環境から論理的に切り離す必要があるからです。

LFSの作業は「ホストのUbuntu上で、/mnt/lfs 以下に新しいLinuxを組み立てる」という構図で進みます。このときchrootなしでビルドを続けると、次の問題が起きます。

  • configureがホスト側の /usr/lib にあるライブラリを検出してしまい、新システムのバイナリがホストのglibcに依存して作られてしまう
  • ビルドスクリプトが参照する /usr/include などのパスが、新システムのものかホストのものか曖昧になる
  • つまり「自分で全部ビルドしたはずのOS」に、ホスト由来の部品が静かに混入する

そこでLFSでは、一時ツール群を /mnt/lfs に揃えた段階で chroot /mnt/lfs に入ります。以降、プロセスからはホストのファイルが原理的に見えないので、configureが検出するライブラリもヘッダも必ず新システム側のものになります。前回までに学んだ ./configure(環境検査)と動的リンク(.so依存)の話が、ここで1本につながります。

なお、chroot直後の環境には /dev や /proc(Part 2で学んだ、カーネル情報を見せる仮想ファイルシステム)が空なので、LFSでは入る前にホストのものを mount --bind などで新ルート内に見えるようにしてから入ります。「ファイル階層は隔離するが、カーネルはホストと共有している」ことがよく分かる手順です。

最小のchroot環境を、自分の手で組んで入ってみる

chrootは今この場で実験できます。ポイントは前回の知識です。chroot先の世界には、ホストの /lib が見えません。動的リンクされたシェルを持ち込んでも、依存する .so がなければ起動できない。だから静的リンクの busybox(多数のコマンドを1つに固めた自己完結バイナリ)を使うのが最小構成です。Ubuntu系の仮想環境で試してください。


sudo apt install -y busybox-static
mkdir -p /tmp/newroot/bin
cp /bin/busybox /tmp/newroot/bin/
sudo chroot /tmp/newroot /bin/busybox sh

プロンプトが変わったら、その中で世界を眺めてみます。


/bin/busybox ls /     # 見えるのは bin だけ。ホストの /home も /etc も存在しない
/bin/busybox pwd      # / と表示される。ここが「新しいルート」
exit                  # ホストの世界に戻る

ls / の結果が bin 1つだけ——ホストでは /tmp/newroot にすぎなかった場所が、中のプロセスにとっては世界のすべてになっています。exit して ls /tmp/newroot を見れば、ディスク上では何も動いていないことも確認できます。

失敗パターンと限界を知ると、コンテナとの違いも見えてくる

chrootで最も遭遇するエラーがこれです。


chroot: failed to run command '/bin/sh': No such file or directory

紛らわしいのは、このエラーが2つの別原因で出ることです。

1. 新ルート内に /bin/sh が本当にない(コピーし忘れ)

2. /bin/sh はあるが動的リンクされていて、依存する .so や動的リンカが新ルート内にない(「見つからないファイル」はshではなく ld-linux の方)

2のケースは、前回の ldd /bin/sh で依存一覧を調べ、.so と動的リンカを新ルート内の同じパスに全部コピーすれば解決できます。静的busyboxを使ったのは、この手間を避けるためでした。

もう1つ知っておくべきはchrootの限界です。chrootが切り替えるのはファイル階層の見え方だけで、プロセス・ネットワーク・メモリなどは隔離しません。root権限のプロセスには既知の脱出手法もあるため、chrootはセキュリティ境界ではありません。Dockerなどのコンテナは、同系統の仕組み(ファイル階層の隔離)に加えて、namespaces(プロセスやネットワークの隔離)やcgroups(資源制限)をカーネルの機能として重ねたものです。「コンテナは仮想マシンではなく、隔離を何層も重ねたプロセスである」という説明の最初の1層を、今日自分の手で作ったことになります。

ルートの切り替えを制して、いよいよ本丸へ

chrootはプロセスのパス解決の起点を切り替える仕組みであり、LFSでは新システムをホストから汚染なく組み上げるために使う。中に持ち込むバイナリは静的リンクにするか、依存 .so ごと持ち込む必要がある——これが今回の結論であり、Part 4で学んだconfigure・make・make install・リンクの知識の合流点です。

これでビルドの道具立てはすべて揃いました。次回からはPart 5、いよいよLinuxカーネルそのものを自分の手でビルドします。

図解テキスト


host root /
  -> chroot /mnt/lfs /
      -> processからは /mnt/lfs が /

パス解決の変化:
  chroot後のプロセスが /usr/bin/gcc を開く
    -> カーネルが実際に開くのは (ホストの) /mnt/lfs/usr/bin/gcc
    -> ホストの /usr/bin/gcc は原理的に見えない

隔離の範囲:
  chroot     = ファイル階層の見え方のみ
  コンテナ    = ファイル階層 + namespaces(プロセス/ネットワーク) + cgroups(資源)

この回で実行するコマンド

上から順番に実行すれば、この回の内容を体験できます。

まずはそのままコピペして実行し、次に1行ずつ意味を確認してください。


sudo apt install -y busybox-static
mkdir -p /tmp/newroot/bin
cp /bin/busybox /tmp/newroot/bin/
sudo chroot /tmp/newroot /bin/busybox sh
# --- ここから新ルートの中 ---
/bin/busybox ls /
/bin/busybox pwd
exit
# --- ホストに戻ったら確認 ---
ls /tmp/newroot

ハンズオン手順

1. ターミナルを開いて、上のコマンドを1行ずつ実行する

2. chroot内の ls / とホストの ls / の出力を見比べ、何が「見えなくなった」かメモする

3. あえて sudo chroot /tmp/newroot /bin/sh を実行し、No such file or directory エラーを一度自分の目で見る(/bin/sh を置いていないため)

4. 前回の ldd を使い、なぜ静的リンクのbusyboxを選んだのかを自分の言葉で説明してみる

5. エラーが出たら、そのエラーメッセージをそのまま残す

チェックポイント

  • コマンドが最後まで実行できた
  • chrootが「何を切り替え、何を切り替えないか」を説明できる
  • failed to run command: No such file or directory の2つの原因を言える
  • 次の回に進む前に、分からない単語を1つ調べた

今回理解できたこと

  • chrootは見えるファイル階層(パス解決の起点)を切り替える技術で、ディスク上のデータは動かない
  • LFSではホスト部品の混入を防ぐ隔離作業として必須の工程
  • 持ち込むバイナリは静的リンクにするか、依存.soと動的リンカごと持ち込む
  • chrootはセキュリティ境界ではなく、コンテナはこれにnamespaces/cgroupsを重ねたもの