Ubuntu Weekly Recipe

第861回systemdの開発者が作ったmkosiで⁠お手軽にルートファイルシステムを構築する

ソフトウェアの作成時における悩ましい問題のひとつが「動作確認環境の構築」です。今回はこの動作確認環境をCI等から作成・利用しやすい「mkosi」について紹介しましょう。

mkosiとは

mkosiとはさまざまなディストリビューションの起動可能なOSツリーやイメージを作成するツールです。URLからもわかるように、systemdの開発者たちがsystemdの動作確認用に作ったツールでもあります。その名前の由来はMake Operating System Imageであり、次のような機能を備えています。

  • UbuntuやFedoraを含むさまざまなディストリビューションのルートファイルシステムに対応
  • 個々のリリースやCPUアーキテクチャーを指定できる
  • 起動可能な仮想マシンイメージだけでなく、コンテナー用のルートファイルシステムをアーカイブしたものも作れる
  • コマンドラインオプションを設定ファイル化することでリポジトリでも管理しやすくなっている
  • ソフトウェアのビルドを行うスクリプトを設定ファイルに記述可能
  • 「ビルド用イメージ」⁠動作確認用イメージ」に分けることで、それぞれを独立して運用できる
  • SecureBootやTPMに対応したイメージも作れる
  • 作成するイメージそのものを暗号化することも可能

systemdと同様に、ひとつのコマンドに様々な機能を備えています。使い方を覚えるのはなかなか骨が折れる作業になりますが、一度設定ファイルを作り込んでしまえばあとはそれを流用するだけで済みます。

仕組み上はクロスディストリビューションにも対応しています。つまりUbuntu上でFedoraのイメージを作ったり、Fedora上でopenSUSEのイメージを作ることが可能です。ただしそのためには、それぞれのディストリビューションで必要なソフトウェアをインストールする必要があります。その手間を考えると、実際に運用する際は、基本的にホストと同じディストリビューションの作成だけを考えたほうが良いでしょう[1]

mkosiのインストール方法

実際にmkosiをインストールしてみましょう。今回はUbuntu 24.04 LTS上で、Ubuntuの公式リポジトリにある「mkosi 20.2」を使うことにします。ちなみにmkosiそのものはUbuntu 18.04 LTSでも使えますがバージョンがそれなりに古いです。特にv14あたりから設定ファイルの名前なども変えている状態です。今から使い始めるなら、Ubuntu 24.04 LTS以降をおすすめします。もちろんコンテナーや仮想マシン上、WSLの上でも実行可能です。

$ sudo apt install mkosi systemd-ukify
(中略)
The following NEW packages will be installed:
  archlinux-keyring augeas-lenses binutils binutils-common binutils-x86-64-linux-gnu bubblewrap build-essential bzip2 cpp cpp-13 cpp-13-x86-64-linux-gnu cpp-x86-64-linux-gnu debian-archive-keyring
  debugedit deltarpm dnf dnf-data dpkg-dev erofs-utils fakeroot g++ g++-13 g++-13-x86-64-linux-gnu g++-x86-64-linux-gnu gcc gcc-13 gcc-13-base gcc-13-x86-64-linux-gnu gcc-x86-64-linux-gnu
  libalgorithm-diff-perl libalgorithm-diff-xs-perl libalgorithm-merge-perl libalpm13t64 libasan8 libatomic1 libaugeas0 libauthen-sasl-perl libbinutils libboost-thread1.83.0 libcc1-0 libclone-perl libcomps0
  libctf-nobfd0 libctf0 libdata-dump-perl libdnf2-common libdnf2t64 libdpkg-perl libencode-locale-perl libevent-2.1-7t64 libfakeroot libfile-fcntllock-perl libfile-listing-perl libfont-afm-perl
  libfsverity0 libgcc-13-dev libgomp1 libgprofng0 libhtml-form-perl libhtml-format-perl libhtml-parser-perl libhtml-tagset-perl libhtml-tree-perl libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl
  libhttp-message-perl libhttp-negotiate-perl libhwasan0 libio-html-perl libio-socket-ssl-perl libisl23 libitm1 liblsan0 liblua5.3-0 liblwp-mediatypes-perl liblwp-protocol-https-perl libmailtools-perl
  libmodulemd2 libmpc3 libnet-http-perl libnet-smtp-ssl-perl libnet-ssleay-perl libnss-mymachines libpkgconf3 libprotobuf-lite32t64 libproxy1v5 libquadmath0 librepo0 librpm9t64 librpmbuild9t64 librpmio9t64
  librpmsign9t64 libsframe1 libsigc++-2.0-0v5 libsolv-tools libsolv1 libsolvext1 libstdc++-13-dev libtext-unidecode-perl libtimedate-perl libtry-tiny-perl libtsan2 libubsan1 libunbound8 liburi-perl
  libwww-perl libwww-robotrules-perl libxml-libxml-perl libxml-namespacesupport-perl libxml-parser-perl libxml-sax-base-perl libxml-sax-expat-perl libxml-sax-perl libyaml-cpp0.8 libzypp-bin libzypp-common
  libzypp-config libzypp1722 lto-disabled-list make makepkg mkosi ovmf pacman-package-manager perl-openssl-defaults pkgconf pkgconf-bin python3-dnf python3-gpg python3-hawkey python3-libcomps
  python3-libdnf python3-pefile python3-rpm python3-unbound rpm rpm-common rpm2cpio sqlite3 systemd-container tex-common texinfo texinfo-lib zypper zypper-common
(後略)

「systemd-ukify」は起動可能なイメージを作るために必要なパッケージです。これはmkosiの依存関係には含まれていないため、明示的に指定しています。

mkosiそのものはPythonスクリプトとして作られています。よって最低限必要なものは、Pythonの実行環境と、systemd-container(systemd-nspawnコマンド)とbublewrapのみです[2]

ただし様々なディストリビューションのルートファイルシステムを作るという特性上、Ubuntu上でインストールできるたくさんのパッケージ管理システムにも入れようとします。具体的にはdnfやpacman、zypperなどのコマンド類は、実はUbuntuでもインストールできるのです。

さらにUEFI/QEMUに対応したルートファイルシステムの作成や起動、システムの暗号化や署名、さまざまなファイルシステムへの対応も必要です。その結果「Recommends」として多種多様なソフトウェアがインストールされてしまいます。もしシンプルな環境を構築したければ、aptコマンドに--no-install-recommendsオプションを指定しましょう。その上で、必要なパッケージを別途取捨選択すると良いでしょう。

mkosi本体はPythonソフトウェアであるため、pipxコマンドを使えば最新版をインストールできます。

$ pipx install git+https://github.com/systemd/mkosi.git

Ubuntuのリポジトリにあるバージョンでは古くて必要な機能が足りない場合は、こちらの方法でインストールしてください。ただし、pipxでインストールする場合、必要なコマンド類は別途手動でインストールしなくてはなりません。それが面倒であれば、mkosiパッケージをインストールした上で、pipxでインストールすれば大体のものは揃います[3]

mkosiの簡単な使い方

mkosiを実際に使うには、さまざまなオプションを指定しなくてはなりません。何も指定せずに実行すると、ホストのイメージを作ろうとします。ちなみにこのあたりの初期設定値は「summary」コマンドで確認できます。

$ mkosi summary
IMAGE: default

    CONFIG:
                            Profile: none
                            Include: none
                     Initrd Include: none
                             Images: none
                       Dependencies: none
                    Minimum Version: none

    DISTRIBUTION:
                       Distribution: ubuntu
                            Release: noble
                       Architecture: x86-64
                             Mirror: default
               Local Mirror (build): none
           Repo Signature/Key check: yes
                       Repositories: none
             Use Only Package Cache: no
              Package Manager Trees: none


    OUTPUT:
                      Output Format: disk
                   Manifest Formats: (none)
                             Output: image.raw
                        Compression: none
(後略)

空のイメージを作る

何もオプションを指定せずに実行すると、上記の設定に合わせたイメージが作られます。なお、管理者権限が必要です。

$ sudo mkosi
‣ Building default image
‣  Installing Ubuntu
(中略)
All done.
‣  Formatting ESP/XBOOTLDR partitions
Automatically determined minimal disk image size as 131.2M, current image size is 131.2M.
File '/var/tmp/mkosi-workspacenhe5nruo/staging/image.raw' already is of requested size or larger, not growing. (131.2M >= 131.2M)
No changes.
‣  /home/ubuntu/image.raw size is 131.3M, consumes 130.3M.

$ ls -lh
total 131M
lrwxrwxrwx 1 ubuntu ubuntu    9 Feb 24 08:46 image -> image.raw
-rw-r--r-- 1 ubuntu ubuntu 132M Feb 24 08:46 image.raw

$ file image.raw
image.raw: DOS/MBR boot sector; partition 1 : ID=0xee, start-CHS (0x0,0,2), end-CHS (0x3ff,255,63), startsector 1, 268815 sectors, extended partition table (last)

具体的には次のようなフォーマットで作られます。

  • ext4でフォーマットされた単一パーティションのイメージファイル
  • Ubuntuの最小限のルートファイルシステム
  • アカウントはrootだけ
  • カーネルやsystemdがインストールされないため、QEMUによる起動はできない

このイメージにログインするにはshellサブコマンドを実行します。

$ sudo mkosi shell
root@default:~# dpkg -l | wc -l
65
root@default:~# apt
-bash: apt: command not found

これは何をしているのかと言うと、単にbwrapでコンテナー環境を構築し、そこからsystemd-nspawnで作成したイメージを指定してログイン(bashを起動)しているだけです。

このイメージの中に、パッケージは最低限のものしか入っていません。カーネルやsystemdはおろか、aptコマンドも入っていません。よって起動イメージとしては使えませんし、パッケージの追加もできません。これはmkosiに-p パッケージ名を指定することで回避できます。

パッケージを追加して作り直す

そこで試しにUbuntuのベースシステムには基本的に入っている「ubuntu-mimimal」「ubuntu-standard」パッケージをインストールしたイメージを作ってみましょう。今回は-o ubuntu1を指定して、別のイメージ名として作成します。

$ sudo mkosi -p ubuntu-minimal -p ubuntu-standard -o ubuntu1 shell
(中略)
‣  /home/ubuntu/test4/ubuntu1.raw size is 505.3M, consumes 504.3M.

root@default:~# dpkg -l | wc -l
241

root@default:~# apt --version
apt 2.7.14 (amd64)

root@default:~# systemctl --version
systemd 255 (255.4-1ubuntu8.5)
(中略)

ここではいきなりshellコマンドを指定しています。mkosiはサブコマンドを指定しなければ暗黙で「buildコマンドを実行した」ことになり、-oオプションで指定したイメージ名(未指定なら「image⁠⁠)でイメージを作ります。shellコマンドは「イメージを作成済みならそのイメージにログイン⁠⁠、⁠イメージが未作成ならbuildした上でログイン」します。今回は-o ubuntu1で指定したubuntu1は未作成だったために、ビルドした上でログインする処理となりました[4]

インストールされるパッケージとサイズがだいぶ増えました。Ubuntuのルートファイルシステムとしてインタラクティブに使う予定がなければ、ubuntu-standardはインストールしなくても良いでしょう。

rootパスワードを設定する

shellサブコマンドはコンテナーの中で指定したコマンド(未指定ならbash)を起動します。つまりDocker等と同じくプロセスコンテナーとして起動するため、systemd等は動作しません。もしPID=1としてinit(Ubuntuならsystemd)を起動し、各種デーモンを動かしたいのであれば、shellコマンドの代わりにbootコマンドを実行します。

ただしbootコマンドを実行する場合はログインできなくてはなりません。mkosiで作ったイメージに一般ユーザーは存在せず、rootもパスワードが無効化されているためログインできません。そこで今回はrootのパスワードを設定してしまいましょう。

root@default:~# passwd
New password:
Retype new password:
passwd: password updated successfully
root@default:~# exit

もちろんrootパスワードを設定せずにユーザーを追加するほうが良いでしょう。

作成したイメージをシステムコンテナーとして起動する

一度shellを抜けて、今度はbootコマンドで起動します。

$ sudo mkosi -o ubuntu1 boot
(中略)
         Starting systemd-update-utmp-runlevel.service - Record Runlevel Change in UTMP...
[  OK  ] Finished systemd-update-utmp-runlevel.service - Record Runlevel Change in UTMP.

Ubuntu 24.04.2 LTS default pts/0

default login: root
Password:
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-53-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@default:~#

無事に起動できました。ただしこの方法は「カーネルを起動せずに、PID=1としてsystemdを起動する」モードになります。つまり仮想マシンというよりはLXD/Incusのようなシステムコンテナー的な使い方となります。

コンテナーを終了するにはpoweroffコマンドを実行するだけです。

root@default:~# poweroff
(中略)
Sending SIGTERM to remaining processes...
Sending SIGKILL to remaining processes...
All filesystems, swaps, loop devices, MD devices and DM devices detached.
Powering off.

ちなみにログインできないとか固まってしまってpoweroffを実行できない場合は、Ctrlキーを押しながら1秒以内に]を3回入力でsystemd-nspawnのインスタンスを終了できます。

mkosi.confを作成する

mkosiはとても複雑で多種多様なオプションを持っています。これを実行の度に指定するのは大変です。カレントディレクトリもしくは--directoryオプションで指定したディレクトリにmkosi.confがある場合、そのファイルの内容に応じてオプションを指定してくれます。普段はこれを使うと良いでしょう[5]

たとえば前項のオプションに似た内容を設定ファイルに書き下すと次のようになります。

[Distribution]
Distribution=ubuntu

[Output]
Format=disk
Output=ubuntu2

[Content]
Packages=ubuntu-minimal

これまでとはオプションをいくつか変更しています。出力ファイルを前のそれとかぶらないように「ubuntu2」に変更したことと、ビルド時間を短くするためにインストールするパッケージからubuntu-standardをなくしたことです。

この状態でmkosi summaryを実行すると、実際に指定されるオプションが変更されます。mkosi.confを更新した場合は、まず期待通りの設定になっているかをmkosi summaryで確認すると良いでしょう。

さらに情報を追加して複雑な設定にしてみましょう。

[Distribution]
Distribution=ubuntu
Release=plucky

[Output]
Format=disk
Output=ubuntu2
CacheDirectory=cache

[Content]
Packages=ubuntu-minimal
Hostname=plucky

ここでは2025年4月にリリースされたUbuntu 25.04(Plucky Puffin)ベースのイメージを作成し、ホスト名を設定してみました。またCahceDirectoryでパッケージキャッシュを作成し、再ビルドの際の高速化を試みます。

$ sudo mkosi
(中略)
‣  /home/ubuntu/test4/ubuntu2.raw size is 390.2M, consumes 389.3M.

ちなみにCacheDirectoryで指定したディレクトリにはdebファイルとリポジトリのメタデータが保存されます。

フックスクリプトを活用して任意のプログラムをビルドする

mkosiの主要な目的のひとつが「ソフトウェアのビルドおよび動作確認環境の構築」です。一般的に「何かをビルドする環境」というのは、⁠追加でソフトウェアをインストールする」必要があります。ただ、環境によって「すでにインストールされているソフトウェア」が異なるため、⁠何を追加しなくてはいけないか」の判断は実は結構難しいです。Ubuntu/Debianのパッケージはpbuilderをはじめとするミニマムなルートファイルシステムを構築して、そこでビルドすることで必要なソフトウェアを洗い出しています。最近だとDockerなどのコンテナを利用したビルドも同様にクリーンビルド環境を構築できます。

mkosiでは、これをよりクロスプラットフォームに対応させる形で実現しています。つまり、mkosi.confにビルド用の設定は追記しつつ、Contentフィールドを切り替えればかんたんに複数のディストリビューションに対応できるというわけです。

さて、mkosiにおけるビルド環境はフックスクリプトを使うことで次のように実現します。

  • パッケージインストール後など環境の準備が整ったあとの微調整(mkosi.prepare)
  • イメージの元となるシステムができたあとのソフトウェアビルド処理の実行(mkosi.build)
  • ソフトウェアのビルドとインストール後の後処理(mkosi.postinst)
  • イメージ作成直前の微調整(mkosi.finalize)

たとえばGNU Helloをmkosiで作ったイメージの中でビルドすることを考えてみましょう。まずはソースコードを取得します。

$ git clone https://git.savannah.gnu.org/git/hello.git
$ cd hello

移動したディレクトリに次のmkosi.confを作成します。

[Distribution]
Distribution=ubuntu
Release=plucky

[Output]
Format=disk
Output=ubuntu2
CacheDirectory=cache

[Content]
Packages=ubuntu-minimal
BuildPackages=build-essential,automake,git,wget,ca-certificates,autopoint,gperf,help2man,texinfo
Hostname=plucky
WithNetwork=true

これまでとの違いは「BuildPackages」「WithNetwork」が追加されたことです。

「BuildPackages」「mkosi build」時にもう一個追加のレイヤーを用意し、そこにソフトウェアのビルド専用のパッケージをインストールしてくれます。つまりこれだけでビルド環境と実行環境を分離できるのです。今回はGCCを含むビルド用にbuild-essentialを、GNU helloのconfigureなどを生成するためにautomakeをインストールしています。他にもGNU Helloのクリーンビルド時はHTTPS経由でのライブラリのダウンロードなどが発生するため、それに必要なツールをインストールしています。

とはいえ最初のうちは何をインストールすれば良いかわからないはず。その場合は、後述のmkosi.buildスクリプトのifブロックの後ろにbashを追加しておき、インタラクティブにトライアンドエラーを繰り返すと良いでしょう。

mkosiは初期設定だと安全のため、外部ネットワークは利用できません。つまり追加ライブラリのダウンロードが阻害されてしまうため、ここではWithNetwork=trueを追加して、インターネットへと接続できるようにしています。もちろんビルド時にネットワークが不要なソフトウェアであれば、この行を追加する必要はありません。

さらに次のようなmkosi.buildファイルを作成し、実行権限を付けます。

#!/bin/sh

if [ "$container" != "mkosi" ]; then
    exec mkosi-chroot "$CHROOT_SCRIPT" "$@"
fi

./bootstrap
./configure
make
make install

ポイントはifブロックの部分です。これをやっておかないと、せっかくインストールしたautomake等のコマンドが使えません。あとはビルドしたいソフトウェアに応じて実行するスクリプトを変えれば良いでしょう。

$ chmod +x mkosi.build

これで準備が完了です。mkosiは、mkosi.buildが存在するとまずビルド用のイメージを生成し、mkosi.buildを実行します。その後、ビルド用のイメージを一旦削除した上で本来のイメージを作り直す挙動になります。mkosi.buildが失敗したら、その段階でエラーになりイメージは作られません。

$ sudo mkosi
‣ Building default image
‣  Installing Ubuntu
(中略)
‣  Running build script /home/ubuntu/temp/hello/mkosi.build…
./bootstrap: Bootstrapping from checked-out hello sources...
./bootstrap: getting gnulib files...
(中略)
make[2]: Leaving directory '/work/src'
make[1]: Leaving directory '/work/src'
‣  Copying in build tree…
(中略)
No changes.
‣  /home/ubuntu/temp/hello/ubuntu2.raw size is 390.5M, consumes 389.5M.

これでビルドは完了しました。GNU helloの場合はカレントディレクトリにhelloコマンドがインストールされているはずですので実行してみましょう。

$ ./hello
Hello, world!

ちなみにmkosi.biuildではmake installも実行していますので、作成されたイメージの中にもhelloコマンドがあるはずです。そちらも試してみましょう。

$ sudo mkosi shell
root@default:~# /usr/local/bin/hello
Hello, world!

mkosiはsystemdと同じく「いろいろできる」タイプのプログラムです。manページを読んでもらうとわかるとおり、様々な用途を想定した多種多様なオプションが用意されています。最初のうちはその自由度の高さに戸惑うでしょうが、すべてを使いこなす必要はありません。必要となったときにマニュアルから使えそうなオプションを探すようにすれば良いでしょう。

おすすめ記事

記事・ニュース一覧