4-4. 特殊マウントを処理する
(見かけ上の) ルートディレクトリを変更した後、procfsやsysfsなどの特殊なファイルシステムを再度マウントしないと、いくつかのコマンドがうまく動かなくなります。
ここでは代表的な特殊ファイルシステム、procfsを正しく処理して、psコマンドが正しく動くようにしてみましょう。
psコマンドの挙動を確認する
ひとまず、今の状態のコンテナ内でpsコマンドを実行してみましょう。
$ sudo su
# make run
go build -o main *.go
./main run bash
# ps
Error, do this: mount -t proc proc /procエラーが出ましたね。mount -t proc proc /proc、すなわち/procにprocfsを再度マウントして下さい、と言われています。
procfsを再マウントする
mount -t proc proc /procと同じ内容をコードで書いて、procfsを再マウントしてみましょう。unix.Mount()を使ってマウント処理を行います。
ヒント
unix.Mount()でprocfsをマウントするには、sourceに空文字列、fstypeにproc、flagsに0を指定します。
想定解答
chrootの場合の想定解答
func SetupRootfs(c RootfsConfig) error {
// procディレクトリをマウント
if err := os.MkdirAll(filepath.Join(c.RootDirPath, "proc"), 0o755); err != nil {
return errors.WithStack(err)
}
if err := unix.Mount("", filepath.Join(c.RootDirPath, "proc"), "proc", 0, ""); err != nil {
return errors.WithStack(err)
}
// 見かけ上のルートディレクトリを変更
if err := unix.Chroot(c.RootDirPath); err != nil {
return errors.WithStack(err)
}
// カレントディレクトリをルートに
if err := os.Chdir("/"); err != nil {
return errors.WithStack(err)
}
return nil
}pivot_rootの場合の想定解答
func SetupRootfs(c RootfsConfig) error {
// ルートディレクトリから再帰的にマウントのプロパゲーションを無効にする
// これをやらないと、pivot_root時にホストマシン側の/devや/sysなどの特殊ファイルの
// マウントが壊れ、新しいシェルセッションが開けなくなるなどの支障が出る
if err := unix.Mount("", "/", "", unix.MS_REC|unix.MS_SLAVE, ""); err != nil {
return errors.WithStack(err)
}
// 既存のrootfsを移動させるディレクトリを作成
if err := os.MkdirAll(filepath.Join(c.RootDirPath, "/.old_root"), 0o755); err != nil {
return errors.WithStack(err)
}
// RootDirPathをバインドマウントし、rootfsの管轄外とする
if err := unix.Mount(c.RootDirPath, c.RootDirPath, "", unix.MS_BIND, ""); err != nil {
return errors.WithStack(err)
}
// procディレクトリをマウント
if err := os.MkdirAll(filepath.Join(c.RootDirPath, "proc"), 0o755); err != nil {
return errors.WithStack(err)
}
if err := unix.Mount("", filepath.Join(c.RootDirPath, "proc"), "proc", 0, ""); err != nil {
return errors.WithStack(err)
}
// rootfsをRootDirPathにマウントし直す
if err := unix.PivotRoot(c.RootDirPath, filepath.Join(c.RootDirPath, ".old_root")); err != nil {
return errors.WithStack(err)
}
// 古いrootfsはアンマウント・削除し、不可視にする
// 注: MNT_DETACHを付けてlazy unmountにしないとアンマウントできない
if err := unix.Unmount("/.old_root", unix.MNT_DETACH); err != nil {
return errors.WithStack(err)
}
if err := os.Remove("/.old_root"); err != nil {
return errors.WithStack(err)
}
// カレントディレクトリをルートに
if err := os.Chdir("/"); err != nil {
return errors.WithStack(err)
}
return nil
}psコマンドが正しく動くことを確かめる
この状態のコンテナ内でpsコマンドを実行し、正しく動くことを確かめましょう。
$ make run
go build -o main *.go
./main run bash
# ps
PID TTY TIME CMD
2242 ? 00:00:00 sudo
2243 ? 00:00:00 su
2244 ? 00:00:00 bash
27273 ? 00:00:00 make
27338 ? 00:00:00 main
27345 ? 00:00:00 bash
27388 ? 00:00:00 ps
#しっかりとpsコマンドが動いていれば成功です!
これと同様に、sysfsやdevtmpfsなどの特殊ファイルシステムもマウントすることで、より多くのコマンドが正しく動くようになります。
runcなどOCI Runtime Specに則ったコンテナランタイムでは、全てのマウント情報は外からconfig.jsonという設定ファイルで渡されます。runc specというコマンドでruncのデフォルトのconfig.jsonが生成できるのですが、この設定ファイルの中では必要な特殊ファイルシステムをほぼ全てマウントするようになっています。
ぜひ一度確認してみてください。
TTYが?になっているのは、ルートディレクトリの移動によってttyを参照できなくなったためです。
これの解決にはかなり複雑な手順を要しますが、余力がある方は是非挑戦してみて下さい。