Ubuntu Weekly Recipe

第594回mmdebstrapで最小のルートファイルシステムを作る

mmdebstrapはdebootstrapライクなインターフェースを持つ、ルートファイルシステムを作るツールです。今回はこのツールを用いて、より小さなUbuntuルートファイルシステムを作ってみましょう。

debootstrapとmmdebstrap

Linux環境を構築する上で「ルートファイルシステム」は非常に重要なコンポーネントです。ルートファイルシステムの品質によって、ユーザーの使い勝手が大きく変わります。Ubuntuをはじめとする「Linuxディストリビューション」「より良いルートファイルシステムを構築する」ためのプロジェクトとも言えるのです。

世界に遍く存在する多種多様なソフトウェアをすべてひとつのルートファイルシステムに含めることは事実上不可能であり、ムダも多いため、Linuxディストリビューションは「パッケージ管理システム」を構築する方向に進化しました。つまりユーザーが必要なソフトウェアを、必要に応じて、より簡単にインストール・活用できるようにしたのです。

よって大半のLinuxディストリビューションは、パッケージ管理システムを動かすために必要なソフトウェアに加えて、そのLinuxディストリビューションがターゲットにしている大半のユーザーがおそらく使うであろうソフトウェア一式をまとめて提供しています。このため、現在ではそのルートファイルシステムは数百MBから数GBぐらいのサイズになります。

しかしながらコンテナ仮想化の普及によって、ルートファイルシステムに対して別の要求が生まれます。曰く「Linuxシステムと必要なソフトウェアをひとつのイメージにまとめて配布する」ために「ルートファイルシステムはできるだけ小さいほうが良い」という要求です。

コンテナ時代の初期から、コンテナイメージ作成ツールとして使われたのがDebianベースのディストリビューションで使われているのdebootstrapでした。

debootstrapは何もない状態から、Debianのベースシステムを構築するためのツールです。ターゲットのシステムも存在しないため、ホストのツールを使ってパッケージを取得・展開してシステムを構築します。ユーザーはdebootstrapで構築されたベースシステムにchrootし、その中でようやくaptなどのコマンドを使えるようになるのです。

もともとdebootstrapは名前の候補にdebchrootがあったことからもわかるように、Debianベースのchroot環境を構築するツールとして作られたものですが、その後すぐにDebianパッケージ構築の基盤システムとしても広く使われるようになります。今日のDebianパッケージやそれを元にしたDebianシステムの品質は、debootstrapによって「クリーンなベースシステムをかんたんに構築できる」ことも大きな要因のひとつでしょう[1]⁠。

debootstrapは「Debianのベースシステム」を構築するツールではありますが、⁠動作には必要ではない部分をこそぎ落とした最小のシステム」を構築するツールではありません。また、あくまで起点となるベースシステムを構築するツールである以上、そのシステムを作成した時点ではセキュリティアップデートは適用されません。それらは本来debootstrapではなく、たとえばDebianのインストーラーがchrootしたあとに必要に応じて行うべき作業であるためです。

前置きが長くなりましたが、このようなdebootstrapの目的外の用途でも使えるように構築されたツールが、今回紹介するmmdebstrapです。

mmdebstrapのインストール

mmdebstrapは構築時にaptコマンドを活用することで、複数のミラーに対応し、より複雑な依存関係にも対応できるdebootstrapという位置づけになっています。

結果的にdebootstrapよりも数倍速くなったり、apt/dpkgの設定を活用することで不要なデータのインストールを抑止できます。さらに非特権でも実行できるように、Linuxの名前空間やfakerootなどにも対応しているようです。

まずはmmdebstrapをインストールしましょう。mmdebstrapは比較的新しいパッケージのため、Ubuntu 19.04以降が必要です。DebianならDebian 10以降であればmmdebstrapがパッケージ化されています。最新のリリースが0.5.1で、パッケージとの関係は次のようになっています。

  • Ubuntu 19.04、19.10、Debian 10:0.4.1
  • Ubuntu focal、Debian testing:0.5.1

今回はUbuntu 19.10とmmdebstrap 0.4.1で説明します。

Ubuntu 19.04以降の環境であれば、mmdebstrapはパッケージをインストールするだけです。

$ sudo apt install mmdebstrap

LXD上でmmdebstrapを動かすには

ただ普段使っている環境はUbuntu 18.04 LTSであることも考慮して、LXD上でUbuntu 19.10環境を構築し、そこでmmdebstrapを実行する方法もあわせて紹介しておきましょう。

LXDの初期設定については第521回「入門システムコンテナマネージャーLXD 3.0」を参照してください。今回はホストとディレクトリを共有しやすくするために、一般ユーザーとrootアカウントのIDマッピングをホストと同じにしています。共有するディレクトリはコンテナ・ホストともに~/rootfs/です。

$ mkdir $HOME/rootfs
$ lxc launch ubuntu:19.10 mmdebstrap
Creating mmdebstrap
Starting mmdebstrap
$ echo -e "both 0 0\nboth 1000 1000" | lxc config set mmdebstrap raw.idmap -
$ lxc config device add mmdebstrap share disk source=$HOME/rootfs path=/home/ubuntu/rootfs
Device share added to mmdebstrap

さらにdebootstram/mmdebstrapをコンテナ内部で実行するためには、いくつかのシステムコールを許容する必要があります。そこで今回は単に特権コンテナとして動かし、コンテナ内部で名前空間等の設定も行えるようにしておきます。これらの設定を反映させるために、コンテナを再起動してください。

$ lxc config set mmdebstrap security.privileged true
$ lxc config set mmdebstrap security.nesting true
$ lxc restart mmdebstrap

このような設定だと、コンテナとホストの管理者アカウントが隔離されません。さらに/etc/sudoers.d/90-cloud-init-usersにより、ubuntuアカウントはパスワード無しでsudoコマンドを実行できるようになっています。コンテナの扱いには十分に注意してください。

cloud-initなどを使わない場合、パスワードが無効化されたubuntuアカウントしか作られません。さらにOpenSSHサーバーはパスワードログインを無効化しているため、事実上SSHログインができない状態です。ここでは公開鍵をインポートする形で、SSHログインできるようにもしておきましょう。

LaunchpadにSSHの公開鍵を登録しているなら、次のコマンドを実行するだけです。

$ lxc exec mmdebstrap -- sudo -i -u ubuntu ssh-import-id lp:(LaunchpadのID)

GitHubに登録した公開鍵を利用したいのであれば、lp:(LaunchpadのID)の部分を「gh:(GitHubのID⁠⁠」に変更してください。これでlxc listで表示されるIPアドレスに対して、ubuntuアカウントでSSHログインできるはずです。

基本的な使い方

基本的な使い方を見ていきましょう。Ubuntu 19.10(Eoan Ermine)のルートファイルシステムは、次のように作成します。

$ mmdebstrap --components="main restricted universe multiverse" \
  eoan eoan-rootfs http://jp.archive.ubuntu.com/ubuntu
I: automatically chosen mode: unshare
I: chroot architecture amd64 is equal to the host's architecture
I: running apt-get update...
done
I: downloading packages with apt...
done
I: extracting archives...
done
I: installing packages...
done
I: downloading apt...
done
I: installing apt...
done
I: installing remaining packages inside the chroot...
done
I: cleaning package lists and apt cache...
done
done

debootstrapと異なり管理者権限でsudoコマンドを付けて)実行する必要はありません。現在のmmdebstrapは、特に指定しなければ環境に応じてunshareコマンドを用いて隔離空間を作成します。この挙動は--modeオプションを用いて変更可能です。

コマンドオプションにはコンポーネントを指定しています。Debianの場合は原則としてmainだけで良く、contrib/non-freeは必要に応じて付ける形になることが多いのですが、Ubuntuの場合はmain/restricted/universe/multiverseすべて指定することが多いためです。ちなみに0.5.1ではカンマ区切りの指定もサポートしています

残りのコマンドライン引数はそれぞれ順番に、リリース名、出力先、ミラーURIです。上記の例だとリリース名は「eoan」になります。リリース名はルートファイルシステム内部の/etc/apt/sources.listを構築する際に利用されます。

2つ目の引数である「eoan-rootfs」は出力先です。出力先は「.tar.gz」など拡張子を付けると圧縮アーカイブ形式で保存でき、サポートしている拡張子が存在しない場合はプレーンなディレクトリとなります。サポートしている拡張子はmanページのCOMPRESSIONの項目を参照してください。-を指定した場合、標準出力に出力します。パイプを経由して別のプロセスで出力結果を処理したい際に便利です。

最後の引数がミラーサーバーのURIです。0.4.1の場合、指定しないと「http://deb.debian.org/debian」が使われます。よってUbuntu用のルートファイルシステムを作る場合は、明示的にミラーサーバーを指定する必要があるのです[2]⁠。ちなみに0.5.1からは、リリース名に応じてミラーサーバーを自動的に切り替える機能が実装されました。ただしリリース名とディストリビューションの対応リストは、mmdebstrapのスクリプト内部に静的に持っているため、より新しいリリース名には対応できない可能性があります。上記のようにミラーを明示しておく方法も覚えておきましょう。

ミラーサーバーは複数指定できます。たとえば通常のリポジトリに加えて、PPAもセットで指定したい場合は、コマンドの末尾にPPAのURIを追加してください[3]⁠。

ミラーサーバーで指定できるURIは次の3種類です。

  • -⁠:標準入力の内容がそのままsources.listに追加されます
  • debもしくはdeb-src 」で始まる文字列:その行がそのままsources.listに追加されます
  • ://が含まれる文字列:deb URI リリース名 コンポーネントの形でsources.listに追加されます
  • 上記以外でファイル名の場合:ファイル名の内容がそのままsources.listに追加されます

特に-については、たとえば次のように実行すると、現在のリポジトリ設定をそのまま利用できます。

$ cat /etc/apt/sources.list /etc/apt/source.list.d/*.list | mmdebstrap eoan eoan-rootfs -

Debianのルートファイルシステムを作る際の注意点

Ubuntu上でDebianのルートファイルシステムを作ろうとするとリポジトリの鍵がないため、次のようにエラーになります。

$ mmdebstrap unstable sid
I: automatically chosen mode: unshare
I: chroot architecture amd64 is equal to the host's architecture
I: running apt-get update...
done
Get:1 http://deb.debian.org/debian unstable InRelease [139 kB]
Err:1 http://deb.debian.org/debian unstable InRelease
  The following signatures couldn't be verified because the public key
is not available: NO_PUBKEY 04EE7237B7D453EC NO_PUBKEY 648ACFD622F3D138
Reading package lists...
W: GPG error: http://deb.debian.org/debian unstable InRelease: The
following signatures couldn't be verified because the public key is
not available: NO_PUBKEY 04EE7237B7D453EC NO_PUBKEY 648ACFD622F3D138
E: The repository 'http://deb.debian.org/debian unstable InRelease' is
not signed.
E: apt-get update -oAPT::Status-Fd=<$fd> -oDpkg::Use-Pty=false failed

これはUbuntuには、Debianアーカイブのリポジトリ鍵がインストールされていないためです。

mmdebstrapは最初の段階では実行ホスト上のaptコマンドを利用します。その際、aptコマンドは実行ホスト上に登録されているリポジトリ鍵です。Ubuntuマシンなら、Ubuntuの公式リポジトリの公開鍵とユーザーがPPA登録時などに追加した公開鍵がそれに該当します。これはapt-keyコマンドで参照できます。

$ apt-key list
/etc/apt/trusted.gpg.d/ubuntu-keyring-2012-archive.gpg
------------------------------------------------------
pub   rsa4096 2012-05-11 [SC]
      790B C727 7767 219C 42C8  6F93 3B4F E6AC C0B2 1F32
uid           [ unknown] Ubuntu Archive Automatic Signing Key (2012) <ftpmaster@ubuntu.com>

/etc/apt/trusted.gpg.d/ubuntu-keyring-2012-cdimage.gpg
------------------------------------------------------
pub   rsa4096 2012-05-11 [SC]
      8439 38DF 228D 22F7 B374  2BC0 D94A A3F0 EFE2 1092
uid           [ unknown] Ubuntu CD Image Automatic Signing Key (2012) <cdimage@ubuntu.com>

/etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg
------------------------------------------------------
pub   rsa4096 2018-09-17 [SC]
      F6EC B376 2474 EDA9 D21B  7022 8719 20D1 991B C93C
uid           [ unknown] Ubuntu Archive Automatic Signing Key (2018) <ftpmaster@ubuntu.com>

しかしながらDebianのルートファイルシステムを作る際に参照するリポジトリはUbuntuではなく、Debianのリポジトリです。よってDebianのリポジトリの公開鍵が必要になるのです。

Debian系のリポジトリには、Debianアーカイブのリポジトリ鍵がパッケージ化されています。それをインストールして、mmdebstrap時に参照するようにしましょう。

$ sudo apt install debian-archive-keyring

これにより/usr/share/kerings以下に、Debianリポジトリのリリースごとの鍵ファイルがインストールされます。あとはmmdebstrapコマンド実行時に、aptコマンドのオプションとして鍵束ファイルを指定するだけです。

$ mmdebstrap unstable sid \
  --aptopt='Dir::Etc::Trusted "/usr/share/keyrings/debian-archive-keyring.gpg"'

ちなみにmmdebstrapの最新のコードにはdebootstrapコマンドと同じように、--keyringオプションが追加されています。よって、将来のリリースではもう少し直感的に鍵束ファイル・ディレクトリを指定できるようになるはずです。

ルートファイルシステムのクロスビルド

もちろんホストとは異なるCPUアーキテクチャーのルートファイルシステムの構築も可能です。あらかじめqemu-user-staticとbinfmt-supportをインストールしておきます。これによりホストと異なるアーキテクチャーのバイナリを実行する際に、自動的にqemu-user-staticを経由してくれます。

$ sudo apt install qemu-user-static binfmt-support

あとは次のコマンドを実行するだけです。

$ mmdebstrap --components="main restricted universe multiverse" \
  --architecture=arm64 \
  eoan eoan-arm64-rootfs http://ports.ubuntu.com/ubuntu-ports

--architecture=arm64でCPUアーキテクチャーを指定できます。

注意しなければならないのはミラーサーバーの部分です。Ubuntuの公式リポジトリサーバーでサポートしているアーキテクチャーはamd64とi386だけです[4]⁠。それ以外のUbuntuでサポートしているアーキテクチャーについては、⁠http://ports.ubuntu.com/ubuntu-ports」を指定する必要があります[5]⁠。具体的にはarmhf、arm64、ppc64el、s390xがこれに該当します。

残念ながら、Ubuntu 18.04 LTS上のLXDコンテナ上で実行した場合、上記コマンドは次のように失敗します。

$ mmdebstrap --components="main restricted universe multiverse" \
  --architecture=arm64 \
  eoan eoan-arm64-rootfs http://ports.ubuntu.com/ubuntu-ports
I: automatically chosen mode: unshare
I: arm64 cannot be executed, falling back to qemu-user
update-binfmts: warning: qemu-aarch64 not in database of installed binary formats.
update-binfmts: exiting due to previous errors
E: qemu-aarch64 is not a supported binfmt name

これは権限や名前空間の都合上、binfmt関連の情報をうまく読めないためです。4.15カーネルではうまくいきませんでしたが、5.3カーネルだと設定によっては、ホストにもqemu-user-staticとbinfmt-supportをインストールすればうまく動いたので、将来的にはコンテナ上でもクロスビルドができるようになるかもしれません。

ちなみに--architecturesオプションにはカンマ区切りで複数のCPUアーキテクチャーを指定可能です。その場合、2つ目以降の指定は、dpkgで言うところの--add-architectureオプションが指定された状態でルートファイルシステムが作られます。MultiArchな環境を作りたい場合に便利でしょう。

ルートファイルシステムのサイズを減らす方法

実はこの時点で、ルートファイルシステムのサイズとしては、debootstrapで作ったものと大差ありません。

$ sudo du -hs eoan-*rootfs/ | sort -nr
334M    eoan-debootstrap-rootfs/
269M    eoan-rootfs/

サイズとしてはそこそこ差が出ていますが、⁠どちらも大きい」という意味ではあまり差がない状態です。より小さいルートファイルシステムが欲しいのであれば、--variantオプションを利用します。

debootstrapの--variantはminbaseを指定するとさらに小さくなります。mmdebstrapの場合、さらに様々なvariantオプションが存在します

もっとも小さくなるのはcustomを指定して、--includeで個別にインストールするパッケージを指定する方法ですが、必要なパッケージをリストアップする設定が大変です[6]⁠。単に--variant単体で最小となるのはessentialでしょう。

$ mmdebstrap --components="main restricted universe multiverse" \
  --variant=essential \
  eoan eoan-essential-rootfs http://jp.archive.ubuntu.com/ubuntu

ちなみにessentialはaptコマンドすらインストールしないので、今後のカスタマイズを考えるならessentialよりはaptパッケージも追加でインストールするaptのほうが良いかもしれません。

$ sudo du -hs eoan-*rootfs/ | sort -nr
334M    eoan-debootstrap-rootfs/
269M    eoan-rootfs/
119M    eoan-debootstrap-minbase-rootfs/
65M     eoan-essential-rootfs/

かなり小さくなりましたね。

dpkgにはパッケージのインストール時に「特定のファイルパスを除外する」オプションが存在します。このオプションを駆使すれば「パッケージについてくるものの目的の用途では不要なもの」を除外できます。よく除外対象になるのが「manpageのファイル」「翻訳データ」です。

$ mmdebstrap --components="main restricted universe multiverse" \
  --variant=essential \
  --dpkgopt='path-exclude=/usr/share/man/*' \
  --dpkgopt='path-exclude=/usr/share/locale/*/LC_MESSAGES/*.mo' \
  --dpkgopt='path-exclude=/usr/share/doc/*' \
  --dpkgopt='path-include=/usr/share/doc/*/copyright' \
  eoan eoan-essential-min-rootfs http://jp.archive.ubuntu.com/ubuntu

--dpkgoptでdpkg固有のオプションを追加できます。追加できるのはdpkgコマンドのオプションから先頭のハイフンを取り除いたものです。

上記の例だと「/usr/share/man/」以下と翻訳データであるmoファイルをすべて削除しています。また、ドキュメント類が格納されている「/usr/share/doc/」以下は削除していますが、ルートファイルシステムの再配布も想定してコピーライトファイルだけは残しています。

$ sudo du -hs eoan-*rootfs/ | sort -nr
334M    eoan-debootstrap-rootfs/
269M    eoan-rootfs/
119M    eoan-debootstrap-minbase-rootfs/
74M     eoan-apt-min-rootfs/
65M     eoan-essential-rootfs/
57M     eoan-essential-min-rootfs/

さらに小さくなりました。⁠eoan-apt-min-rootfs」「eoan-essential-min-rootfs」--variant=essentialの代わりに--variant=aptを指定したものです。

これ以上はもう個別にファイルを削除したり、パッケージを省いたりすることになります。ただし、そこまでくるともう、aptシステムを使えるかどうかも怪しくなってきますし、あえてDebianベースで構築する必要はない気もします。

Dockerイメージとして活用する

作ったルートファイルシステムをDockerイメージにしてみましょう。

$ cd eoan-apt-min-rootfs/
$ sudo tar -c . | docker import - eoan

公式のUbuntu 19.10のイメージと比べると「ほんの少しだけ」小さくなったようです。

$ docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
eoan                     latest              603b264ef4ea        7 seconds ago       68.7MB
ubuntu                   eoan                09604a62a001        3 weeks ago         72.9MB

Ubuntu BaseやUbuntu Minimalとの比較

Ubuntuには組み込み・コンテナ向けの「小さなイメージファイル」として、Ubuntu BaseUbuntu Minimalが存在します。これらとサイズを比較してみましょう。Ubuntu 19.10のイメージは、それぞれ次のURLからダウンロードできます。

$ sudo du -hs * | sort -nr
404M    minimal
77M     base

Ubuntu Baseのほうはeoan-apt-min-rootfsと大差ありませんね。当たり前と言えば当たり前なのですが、Ubuntu Baseも次のような内容の設定ファイルを/etc/dpkg/dpkg.cfg.d/excludesに保存しています。

# Drop all man pages
path-exclude=/usr/share/man/*

# Drop all translations
path-exclude=/usr/share/locale/*/LC_MESSAGES/*.mo

# Drop all documentation ...
path-exclude=/usr/share/doc/*

# ... except copyright files ...
path-include=/usr/share/doc/*/copyright

# ... and Debian changelogs
path-include=/usr/share/doc/*/changelog.Debian.*

http://cdimage.ubuntu.com/ubuntu-base/releases/

まさにmmdebstrapで指定した内容です。その結果、同じようなサイズになっているのです。イメージ作成時にカスタムする必要がないのであれば、Ubuntu Baseも選択肢のひとつとして考えておきましょう。

なおUbuntu Baseには/usr/local/sbin/unminimizeというコマンドが存在します。これを使うと、上記設定を無効化した上で、除外されていたファイルを再インストールしてくれるのです。

ちなみにUbuntu Minimalは、Ubuntu Baseに加えてubuntu-minimalパッケージをインストールしたイメージです。よってどうしてもサイズは大きくなってしまいます。

おすすめ記事

記事・ニュース一覧