distrobuilderは、Linux Containersプロジェクトで開発されている「LXD/LXC用イメージを構築するツール」です。特にLXD用のイメージサーバーで公開している各種イメージを日々ビルドするのに使われています。今回はこのツールを使って、独自のイメージをビルドする方法を紹介しましょう。
distrobuilderのインストール
distrobuilder自体は単なるGo言語製のプログラムです。Ubuntuだけでなく、Fedora/CentOS/Springdale/Alma/Rocky、Gentoo/Arch/openSUSE/Plamoといった有名どころのイメージはもちろん、Apertis/openWRT/Alpineといった特別な用途のイメージやDocker Hubで公開されているイメージをカスタマイズできます。手順はシンプルで、カスタマイズ内容を記述したYAMLファイルを用意し、distrobuilderを実行するだけです。
インストールはUbuntuならsnapパッケージを使うのが一番確実でしょう。
ちなみに8月9日に最新の1.3がリリースされています。snap版は8月10日時点で1.3はまだcandidateチャンネルにしか存在しませんが、まもなくstableチャンネルに移行する予定です。
Ubuntu以外ならGo言語の環境を整えた上で、go get
するのが簡単です。
distrobuilder自体はシンプルなコマンドです。
helpを除くと、主に6個のサブコマンドから構築されています。これらはいずれも「distrobuilder help サブコマンド
」で詳細なオプションを確認できます。
build-dir
:指定したディレクトリにルートファイルシステムを構築します
build-lxc
:LXC向けのイメージデータを構築します
build-lxd
:LXD向けのイメージデータを構築します
pack-lxc
:build-dir
で構築したルートファイルシステムをベースにLXC向けのイメージデータを構築します
pack-lxd
:build-dir
で構築したルートファイルシステムをベースにLXD向けのイメージデータを構築します
repack-windows
:WindowsのダウンロードISOイメージを、VM版のLXDのインストールイメージに作り変えます
なお、たとえばbuild-dir
の実行にはbtrfs-progs
パッケージが必要になるなど、サブコマンドによっては追加でパッケージが必要になることもあります。
主に使うのはLXDイメージを作成できるbuild-lxd
でしょう。LXDイメージはそのままだとsquashfsとして構築します。また「--vm
」オプションを付けると仮想マシンで利用可能なqcow2フォーマットで構築します。
「LXC/LXD」向けと言う場合、単なるルートファイルシステムのアーカイブだけでなく、メタデータも必要です。これらによりLXCなら「lxc-create
」でインスタンスを生成する際のベースイメージとして利用できますし、LXDなら「lxc image
」でローカルのイメージリポジトリにインポートできます。
シンプルなUbuntuイメージを作る
まずは単にUbuntuのLXDイメージを構築してみましょう。イメージの元となる情報は、すべてYAMLファイルに記述します。distrobuilderのリポジトリにはサンプルのYAMLファイルがありますので、それをそのまま使いましょう。
distrobuilderの実行には管理者権限が必要です。
上記の設定ではdebootstrapを使っています。流れとしては「debootstrapでベースイメージを作り、YAMLに従ってルートファイルシステムをカスタマイズし、mksquashfsでアーカイブする」といった形になります。VMイメージなら最後はsqhuashfsではなくqcow2イメージファイルとなります。
構築には環境によって数分から十数分かかります。実際に作られるのは次のようなファイルです.
rootfs.squashfsがルートファイルシステムそのものです。それに対してlxd.tar.xzは、root.squashfsをLXDで利用するためのメタデータの集まりとなっています。
実際にLXDのイメージサービスにインポートしてみましょう。「--alias
」オプションで適当な名前を付けておきます。
無事にイメージを取り込めました。image info
コマンドでその内容を確認できます。
このイメージを使って新しいインスタンスを作るには次のように実行します。
これだけです。簡単でしたね。
フォーマットの解説
ここまでの説明で薄々気づいたかと思いますが、distrobuilderのキモとなるのはYAMLファイルの内容です。そこで先ほど利用したubuntu.yamlを元に、具体的なフォーマットについて説明していきましょう。
image:メタデータ情報
imageにはイメージのメタデータを記録します。
必須なのはdistributionだけです。原則として任意の文字列を指定可能ですが、値によってはYAML上の別のデータから参照されることもあります。ちなみにarchitectureは未設定だとビルドホストのアーキテクチャーが使われます。
また、上記以外にvariantというフィールドでイメージの派生版を作成し、YAMLの中で派生版だけ特殊設定を行うといった使い方も可能です。
source:ベースシステムの構築方法
sourceにはベースシステムの構築方法とイメージファイルの取得方法を記載します。
downloaderでベースイメージの取得方法を指定し、それ以外の設定はdownloaderの値に依存します。
上記の例だとdebootstrap
コマンドをベースイメージの取得・構築に使います。same_asは/usr/share/debootstrap/scripts
以下のどのファイルをビルドスクリプトとして利用するかを指定し、urlがミラーサーバーの指定、keyserverとkeysがリポジトリの鍵として使われます。
実はdebootstrapだけが特殊ケースで、他はHTTP/HTTPSからイメージを取得することになります。downloaderでサポートしているフォーマットは主に以下のような値が存在します。
- almalinux-http
- alpinelinux-http
- alt-http
- apertis-http
- archlinux-http
- busybox
- centos-http
- debootstrap
- docker-http
- fedora-http
- funtoo-http
- gentoo-http
- opensuse-http
- openwrt-http
- oraclelinux-http
- plamolinux-http
- rockylinux-http
- rootfs-http
- sabayon-http
- ubuntu-http
- voidlinux-http
おおよそLXDのイメージサーバーでインストール可能なLinuxディストリビューションは、downloaderとurlとkeysを正しく設定することで構築可能です。具体的な記述方法はLXC/LXDのCIで使われている設定ファイルが参考になるでしょう。
busyboxはBusyBoxのソースコードをダウンロード・ビルドしてイメージを構築します。できるだけ小さい環境がほしいなら、busyboxを使うと良いかもしれません。
ちなみにdocker-httpの場合は、urlにdocker pull
で渡す名前をかけば大丈夫です。独自のレジストリを利用したいなら、環境変数DOCKER_REGISTRY_BASE
を設定しておいてください。ちなみにこれはSabayon Linuxのために導入された仕組みです。普通のDockerイメージをLXDに対応させるには、/sbin/init
を適切に設定したり[1]、ネットワーク周りの設定をなんとかしたりと、いろいろな対応が必要になります。手間の割に得られるものは少ないため、よっぽど特殊な嗜好を持っている人以外はおすすめしません。
targets: 特定のターゲット向けの細かい設定
targetsでは、LXDやLXCなどイメージをデプロイするターゲット用の設定を記述します。
ubuntu.yamlの例だとLXCの設定しか記述していませんね。create-messageではイメージ構築時に表示するメッセージを、configではコンテナの設定を記述しています。
LXDの場合は、仮想マシンインスタンスを作るときに利用します。バージョン1.3の時点では、sizeでファイルシステムのサイズを、filesystemでファイルシステムをext4もしくはbtrfsのいずれにするかを選べるだけです。
files: ルートファイルシステムのカスタマイズ
filesでは、downloaderによって構築したベースファイルシステム上の各種ファイルをカスタマイズできます。ここの項目はgeneratorsとfiltersの2種類のコンポーネントで形成されています。基本的にfilesの処理が行われるのは最後にルートファイルシステムをイメージとしてアーカイブする直前です。
ubuntu.yamlのfilesは長いので一部だけ抜粋して表示します。
generatorにはhostnameやhosts、fstab、lxd-agent、systemdといった特定のファイル専用のタイプと、dump/copy/removeのような汎用的なタイプの2種類にわかれます。
このうち専用のものは主にコンテナの中で特殊扱いされるもの、もしくはlxd.tar.gzにテンプレートとして保存され、インスタンス生成時に自動生成されるべきものが大半です。基本的に公式イメージの書式に合わせておいたほうが無難でしょう。
removeはイメージ作成時に自動生成されたファイルを確実に削除しておきたい場合に便利です。copyはホストから特定のファイルをイメージ内部にコピーしたい場合に使います。またmode/uid/gidの指定も可能です。
dumpは特定の内容をファイルに書き出したい場合に利用します。
/etc/machine-id
のようにcontent未指定の場合は、ただの空ファイルとなります。
releases/types/variantsはこの設定を特定のイメージにのみ適用したい場合のフィルタリング機能です。上記の例だと、Ubuntuの18.04(bionic)以上にのみ適用する想定であり、さらにvariants未指定のコンテナイメージでのみ使われます。コンテナのみになっているのはVM版だとNICの名前がeth0ではなくenp5s0だからです。
これらはimages
以下で設定された値が参照されます。またdistrobuilder実行時の末尾に「-o image.variant=cloud
」のようにオプションを使って値を追加することも可能です。
このフィルタリング機能はfilesだけでなく、後述のpackagesやrepositories、actionsでも利用します。
これらはcloud-init用のテンプレートファイルを作成します。実際に作成されるのは、lxd.tar.gz
の中になります。
packages: パッケージのインストールと削除
packagesでは、追加でインストールするパッケージや削除しておくパッケージを指定します。こちらもフィルタリング機能を用いて、特定のアーキテクチャー向けイメージだけ変えるといった対応が可能です。
またmanagerによってdnfやyum、pacmanといったパッケージ管理システムを選択できます。またcustom-managerを使うと、任意のコマンド列を「パッケージ管理システム」として指定できます。
上記の例だとaptを使って、fuseなどをインストールし、cloud設定があるならcloud-initをインストール、VMイメージならos-proberを削除しています。
さらにrepositoriesでパッケージリポジトリを設定できます。Ubuntuならsources.listに記述する内容ですね。ここで「{{ image.release }}
」のようにメタデータフィールドの値を参照しています。このためubuntu.yamlのimage.release
はUbuntuのリリースコード名が記述されることを期待しているわけです。
actions: イメージビルドの各処理の途中で実施するコマンド設定
actionsではイメージビルドの処理の途中でフック処理として任意のコマンドを実行できるようになっています。
triggerにフックするタイミングを記述し、actionに実際に実行するシェルスクリプトを記述します。
バージョン1.3でサポートしているフックは次の4種類です。
post-unpack
:ダウンロード・作成したベースイメージを展開したあとに実施
post-update
:packages.update
がtrue
のときのみ、パッケージ情報を更新したあとに実施
post-packages
:packages
で記述したパッケージ関連の処理を実施したあと
post-files
:files
で記述されたファイルの変更処理を実施したあと
実際にフックが叩かれるのも上記の順番になります。
mappings: アーキテクチャー名のマッピング
mappingsにはCPUアーキテクチャー名のマッピングテーブルを記述します。CPUアーキテクチャー名はディストリビューションによって「x86_64」だったり「amd64」だったり「generic_64」だったりと、微妙に異なります。そこでこのマッピングテーブルで、そのあたりの差異を吸収してしまおうというわけです。
architecture_mapの後ろにディストリビューション名を書けば、そのディストリビューションに合わせた設定を行います。
また、architecturesによってアーキテクチャーごとに任意のマッピングをハッシュテーブルとしても記述できます。なお記述は「distrobuider側の名前: ディストリビューション側の名前
」となります。distrobuilder側の名前は、実質LXDのコード上の名前と一致しています。
- i686:Intel x86 32bit/IA-32
- x86_64:Intel x86 64bit/AMD64/Intel 64
- armv7l:ARMv7 32bit
- aarch64:AAarch64 64bit
- ppc:PowerPC 32bit
- ppc64:PowerPC 64bit Big Endian
- ppc64le:PowerPC 64bt Little Endian
- s390x:S/390 Big Endian
- mips:MIPS 32bit
- mips64:MIPS 64bit
- riscv32:RISC-V 32bit
- riscv64:RISC-V 64bit
実際のところ個々のディストリビューションのサポートアーキテクチャーが上記に完全に一致するわけではありませんが、おおよそ一致しているものがひとつのカテゴリにまとめられています。
ここまでいろいろ述べましたが、そこまで難しい書式ではないため、ubuntu.yamlやすべての利用可能なオプションを網羅したscheme.yamlなどで使用例を見ながら作業すれば、自分好みのイメージが作れることでしょう。