Ubuntu Weekly Recipe

第764回UbuntuサーバーのLive用ISOイメージをカスタマイズする

UbuntuサーバーのインストールISOイメージは、デスクトップ版と同じようにLive環境が備わっています。たとえば何かトラブルがあった時、デスクトップを起動するほどのスペックがないマシンに対してLive環境でリカバリーを試みる際に有効です。起動後は普通のUbuntuであるため、メモリの容量が許せば、aptコマンドなどで追加のインストールも可能になります。ただ、ネットワークの都合で、起動の時点でインストールされていたほうが良い場合も多々あります。そこで今回はこのLive環境をカスタマイズしたISOイメージの作成方法を紹介しましょう。

図1 UbuntuサーバーのLive環境
図1

UbuntuサーバーのISOイメージ

Ubuntuは18.04ぐらいから、本格的にサーバー版のインストーラーが刷新されました。第495回でも紹介しているように、これは「Subiquity」というソフトウェアを採用しています。Subiquityは多言語対応に難があるものの、cloud-init等の各種ツールと連携させることで、Ubuntuのインストーラーに対して高いカスタマイズ性をもたらしています。実際、Ubuntu 23.04ではデスクトップ版のインストーラーも、バックエンドとしてSubiquityを採用するようになりましたし、WSLg対応環境向けのWSL版Ubuntuのインストール時も同等の機能を利用しています。

また、Subiquityはsnapパッケージとして提供されていますが、その副産物として「インストーラーを起動したあとに、最新のインストーラーに更新できる」仕組みも備わるようになりました。これによりインストール機能に問題があっても、その後のアップデートによってISOイメージを更新することなく、対応できるようになったのです。

直接的な利便性という点では、Subiquityの利点はcloud-initベースの自動インストール機能とLive環境でしょう。自動インストール機能については第615回のサーバー版インストーラーに導入された自動インストール機能でも紹介してますが、YAMLファイルを利用したインストールの自動化です。完全な自動化だけでなく部分的な自動化も対応しているため、ちょっとしたテンプレートイメージの作成にも使えます。

Live環境は、デスクトップ版のUbuntuを利用している人にはおなじみの「インストールイメージで起動したら、ストレージを変更することなくUbuntuを使える」モードです。サーバー版の場合は、次のようなステップでLive環境が起動します。

図2 インストーラが起動したら、カーソルキーやタブキーで右上の「Help」にフォーカスを合わせる
図2
図3 Enterキーを押してメニューを表示し、⁠Enter Shell」を選択
図3
図4 Live環境にログインできた
図4

Live環境は管理者(root)権限で動いています。普通のUbuntuサーバーと同じように、ネットワーク設定やパッケージのインストールが可能です。exitするとインストーラーの画面に戻れます。一般ユーザーとしてubuntu-serverinstallerも存在します。そのうち後者は、リモートからSSHログインするためのアカウントです。具体的には先ほどの「Enter Shell」を選ぶ画面と同じ場所で、⁠Help on SSH access」を選択することでIPアドレスやパスワードを確認できます。

図5 Live環境にSSHログインするための情報
図5

ルートファイルシステムそのものは、squashfsで構築されています。ただし、lowerディレクトリは複数のsquashfsの組み合わせをoverlayfsで重ね合わせることによって実現しているようです[1]。よって、ISOイメージを分解してカスタマイズするにしても、このあたりの状態を把握しておかなくてはいけません。

ただし、リカバリーにも使うならよく使うツールを入れておきたいところです。また、たとえば小さなUSBメモリーにして常に接続しておくことで、ストレージ故障によりうまく起動できなくなった時に、自動的にUSBメモリー側にフォールバックして、そこから状況の確認を行う、といった用途にも使えるようになります。

今回は、そんなUbuntuサーバーのLiveインストールイメージをカスタマイズできる「livefs-editor」を紹介します。

Liveイメージのカスタマイズに特化した、livefs-editor

livefs-editorは主にUbuntuサーバーのインストールイメージのデバッグ用途として作られた、ちょっとしたPythonスクリプトです。既存のISOイメージに対して、次のような変更を加えたISOイメージを構築できます。開発者はCanonicalでSubiquityの開発等を担当しているMichael Hudson-Doyleです。

  • PPA等のリポジトリの追加
  • 自動インストール用のYAMLファイルの同梱
  • カーネルの起動パラメーターの変更
  • インストール用のリポジトリに任意のdebファイルやパッケージを追加
  • インストール用リポジトリのリポジトリ鍵の変更
  • Live環境に指定のパッケージやdebファイルをインストール
  • Live環境に指定のsnapパッケージをインストール
  • ISOイメージやLive環境に対する任意のファイルのコピーや削除
  • 指定したsquashfsのカスタマイズ
  • 指定したPython文字列の実行
  • 起動・インストール時のカーネルフレーバーの変更
  • initramsのカスタマイズ
  • カスタマイズ用のインタラクティブシェルの立ち上げ

また、上記の設定はすべてコマンドラインオプションから指定するのですが、YAMLでまとめて指定してしまうことも可能です。各オプションの指定方法や使い方が少し特殊であるため、習熟するのには少し手間かもしれませんが、うまくやればイメージの作成を自動化できます。

とりあえず使ってみるのが良いでしょう。そこでまずはlivefs-editorをインストールします。

$ sudo apt install xorriso liblz4-tool pipx
$ pipx install git+https://github.com/mwhudson/livefs-editor.git
  installed package livefs-edit 0.0.4, installed using Python 3.10.6
  These apps are now globally available
    - livefs-edit
done! ✨ 🌟 ✨

ちなみにUbuntu 23.04からPEP 668に従う形で、システム領域にpip3コマンドで(Debianパッケージではない)Pythonパッケージをインストールしようとすると、エラーを表示するようになりました。Pythonのvenvを使うようアドバイスされます。pipxを使えば、このvenvの環境構築等も一緒にやってくれるので、カジュアルにツールをインストールしたいだけであれば、今後はpipxがおすすめです。

また、pipxは~/.local/bin/以下にラッパースクリプトをインストールします。Ubuntuの場合、⁠ログイン時」にこの~/.local/bin/ディレクトリが存在すれば自動的に環境変数$PATHに追加してくれますので、インストールしたコマンドもすぐに使えるようになります。ただし、ログインの時点でディレクトリがなく、その後作られた場合は$PATHには設定されませんので、パスを指定するか一度ログインし直してください。

試しにヘルプを表示してみましょう。注意しなくてはいけない点として、live-editorのリポジトリ名は「livefs-editor」ですが、コマンド名は「livefs-edit」です。

$ livefs-edit -h
# livefs-edit source.{iso,img} dest.{iso,img} [actions]

livefs-edit makes modifications to Ubuntu live ISOs and images.

Actions include:

 * --add-apt-repository
 * --add-autoinstall-config
 * --add-cmdline-arg
 * --add-debs-to-pool
 * --add-packages-to-pool
 * --add-snap-from-store
 * --cp
 * --edit-squashfs
 * --inject-snap
 * --install-debs
 * --install-packages
 * --python
 * --replace-kernel
 * --resign-pool
 * --rm
 * --setup-rootfs
 * --shell
 * --unpack-initrd

これらの「アクション」は、引数を持つものと持たないものがあります。詳しいフォーマットはREADME.mdを参照してください。

livefs-editコマンドの使い方

ヘルプメッセージにもあるように、livefs-editは次のように複数のアクションを繋げて使うことになります。

$ sudo ~/.local/bin/livefs-edit src.iso dst.iso --add-apt-repository REPO --install-packages PKG

まず最初に、chrootコマンド等を使うために、管理者権限で実行する必要があります。さらにsudoを使おうとすると$PATHが初期化されてしまい、pipxでインストールした場所にパスが通っていないため、上記のようにフルパスで指定しなくてはいけないのです。

src.isoが元になるISOイメージで、dst.isoが生成ファイルです。そのあとにカスタマイズしたい内容を順番に指定していきます。上記の例だと「REPO」というリポジトリをadd-apt-repositoryコマンドで追加し、その上で「PKG」というパッケージを追加インストールします。

もうひとつの方法として、上記コマンド列をYAMLファイルに記述して指定する方法があります。たとえば上記のアクション列は次のように変換できるのです。

- name: add-apt-repository
  repo: REPO
- name: install-packages
  packages:
    - PKG

name: アクション名(オプションのうち先頭の「--」を除いたもの)の後ろに、引数名: 引数を追加して1アクションです。⁠引数名」は前述のREADME.mdに記載があるので、そちらを参照しましょう。引数は基本的に0個ないし1個ですが、install-packagesを含むパッケージ関連だけは、リスト形式で指定します。

作成したYAMLファイルの指定方法は次のとおりです。

$ sudo ~/.local/bin/livefs-edit src.iso dst.iso --action-yaml install.yaml

アクション列が複雑になってきたら、YAMLファイルに移行することも検討しましょう。

install-packagesとadd-packages-to-poolの違い

livefs-editには次の4種類のパッケージ関連のアクションが存在します。

  • install-packages
  • add-packages-to-pool
  • install-debs
  • add-debs-to-pool

install-packages「Live環境にパッケージをインストールする」オプションです。ここで指定したパッケージは、ISOイメージからLive環境を起動した時点で使えるようになります。

それに対してadd-packages-to-poolは、⁠インストーラーの中にあるパッケージリポジトリにパッケージを追加する」オプションです。たとえば自動インストールで、外部の追加パッケージをインストールしたいけれども、インストール中にインターネットアクセスはしたくない、なんて場合にこれを指定しておけば、必要なパッケージがISOイメージの中に同梱されます。

debがついている方のアクションは、パッケージ名の代わりにパッケージファイルを指定するオプションです。

強力なshellアクション

livefs-editにはよくある使い方に対するアクションが一通り揃っています。ただ、それですべて賄えるかというと難しいところです。そこで便利なのが「shellアクション」です。これは要するにISOファイルに中に入って、実際に手作業で処理をするアクションになります。さらに、標準入力にコマンド文字列を渡してやれば、シェルの実行も自動化できます。

実際に試してみましょう。

$ sudo ~/.local/bin/livefs-edit ubuntu-22.04.2-live-server-amd64.iso test.iso --shell
set up loop device /dev/loop31 backing ubuntu-22.04.2-live-server-amd64.iso
found live iso9660 filesystem on /dev/loop31p1
running shell with arguments {}
root@meet:/tmp/tmpv922azo1#

上記はサーバー版のUbuntu 22.04 LTSのISOイメージを使ってshellに入った様子です。/tmp以下に展開されていることがわかります。まずはディレクトリを見てみましょう。

root@meet:/tmp/tmpv922azo1# ls
new  old
root@meet:/tmp/tmpv922azo1# ls new/iso/
EFI  boot  boot.catalog  casper  dists  install  md5sum.txt  pool  ubuntu

大本のISOイメージはold/iso以下にマウントされており、new/iso以下が最終的に生成されるISOイメージになる領域です。このうちdistspoolがパッケージリポジトリで、casper以下がLive環境になります。

実際にcasper以下を見てみると、カーネル(vmlinuz)とinitramfs(initrd⁠⁠、さらに各種ルートファイルシステム(squashfs)が存在します。これらがLive環境で使われるシステムファイルです。

# ls new/iso/casper/
filesystem.manifest             ubuntu-server-minimal.squashfs.gpg                                      ubuntu-server-minimal.ubuntu-server.installer.manifest
filesystem.size                 ubuntu-server-minimal.ubuntu-server.installer.generic-hwe.manifest      ubuntu-server-minimal.ubuntu-server.installer.size
hwe-initrd                      ubuntu-server-minimal.ubuntu-server.installer.generic-hwe.size          ubuntu-server-minimal.ubuntu-server.installer.squashfs
hwe-vmlinuz                     ubuntu-server-minimal.ubuntu-server.installer.generic-hwe.squashfs      ubuntu-server-minimal.ubuntu-server.installer.squashfs.gpg
initrd                          ubuntu-server-minimal.ubuntu-server.installer.generic-hwe.squashfs.gpg  ubuntu-server-minimal.ubuntu-server.manifest
install-sources.yaml            ubuntu-server-minimal.ubuntu-server.installer.generic.manifest          ubuntu-server-minimal.ubuntu-server.size
ubuntu-server-minimal.manifest  ubuntu-server-minimal.ubuntu-server.installer.generic.size              ubuntu-server-minimal.ubuntu-server.squashfs
ubuntu-server-minimal.size      ubuntu-server-minimal.ubuntu-server.installer.generic.squashfs          ubuntu-server-minimal.ubuntu-server.squashfs.gpg
ubuntu-server-minimal.squashfs  ubuntu-server-minimal.ubuntu-server.installer.generic.squashfs.gpg      vmlinuz

squashfsが複数存在するのは、環境に応じて重ね合わせるレイヤーを変更するためのようです。ubuntu-server-minimal.squashfsがベースシステムで、さらに状況に応じて異なるsquashfsを重ねていくことになります。

shellアクションの終了はexitコマンドの実行です。このとき戻り値が0なら次のアクションを実行し、1ならその時点で終了してクリーンナップ処理が走ります。

root@meet:/tmp/tmpv922azo1# exit
exit
running repack hooks
no changes!

上記の例だと、old/isonew/isoで変化がなかったため、ISOファイルの生成は行わずに完了しています。これが何か変更があると、ISOイメージの作成フェーズに入るのです。本記事の後半ではこのshellアクションを使って、Live環境をカスタマイズする例を紹介します。

edit-squashfsアクションでsquashfsをカスタマイズ

edit-squashを使うと、squashfsをカスタマイズできるようになります。引数として、⁠squashfsのファイル名」を指定する必要があります。たとえば、先ほどのubuntu-server-minimalubuntu-server-minimal.ubuntu-server.installerをマウントしてみましょう。

$ sudo ~/.local/bin/livefs-edit ubuntu-22.04.2-live-server-amd64.iso test.iso \
  --edit-squashfs ubuntu-server-minimal \
  --edit-squashfs ubuntu-server-minimal.ubuntu-server.installer \
  --shell
set up loop device /dev/loop31 backing ubuntu-22.04.2-live-server-amd64.iso
found live iso9660 filesystem on /dev/loop31p1
running edit-squashfs with arguments {'squash_name': 'ubuntu-server-minimal'}
  squashfs 'ubuntu-server-minimal' now mounted at '/tmp/tmp6av769vs/new/ubuntu-server-minimal'
running edit-squashfs with arguments {'squash_name': 'ubuntu-server-minimal.ubuntu-server.installer'}
  squashfs 'ubuntu-server-minimal.ubuntu-server.installer' now mounted at '/tmp/tmp6av769vs/new/ubuntu-server-minimal.ubuntu-server.installer'
running shell with arguments {}
root@meet:/tmp/tmp6av769vs#

edit-squashは複数の指定が可能です。また最後に--shellを付けることで、squashfsをマウントした状態で環境に入れます。マウント先はコマンド実行結果に書かれているように、new/squashfs名です。

root@meet:/tmp/tmp6av769vs# ls new/ubuntu-server-minimal
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var
root@meet:/tmp/tmp6av769vs# ls new/ubuntu-server-minimal.ubuntu-server.installer
dev  etc  proc  root  run  snap  sys  tmp  usr  var

このように内容がだいぶ異なるようです。実際に中身を比較してみると、new/ubuntu-server-minimal.ubuntu-server.installerにはSubiquityのsnapパッケージとそれを動かすための仕組みが入っていることがわかります。しかしながら/etc/passwdといった、一般的なUnix環境にあるファイルはこちらには入っていません。つまりLive環境は複数のsquashfsを重ね合わせることでできていることがわかります。

このsquashfsディレクトリに変更を加えると、shellを抜けて、ISOイメージを構築する際に自動的に再アーカイブしてくれます。つまりLive環境のルートファイルシステムを柔軟にカスタマイズしたいのであれば、edit-squashの利用は必須となります。

標準入力のリダイレクトを活用して設定を自動化

shellとedit-squashfsを組み合わせれば大抵のことは自動化できます。まずは次のような、YAMLファイルを作ってみましょう。

- name: edit-squashfs
  squash_name: ubuntu-server-minimal
- name : shell
- name: install-packages
  packages:
    - docker-ce

ubuntu-server-minimal展開しておき、shellを起動し、docker-ceパッケージをインストールする設定に見えます。ただし次の疑問点が出てくるでしょう。

  • YAMLをshellで指定した場合はどうなるのか
  • Ubuntuリポジトリにはdocker-ceパッケージは存在しないがどうなるのか

実際に実行してみた結果は次のとおりです。

$ sudo ~/.local/bin/livefs-edit ubuntu-22.04.2-live-server-amd64.iso test.iso --action-yaml docker.yaml
(中略)
running edit-squashfs with arguments {'squash_name': 'ubuntu-server-minimal'}
  squashfs 'ubuntu-server-minimal' now mounted at '/tmp/tmpr8b131tq/new/ubuntu-server-minimal'
running shell with arguments {}
root@meet:/tmp/tmpr8b131tq# exit
exit
running install-packages with arguments {'packages': ['docker-ce']}
  running unpack-initrd with arguments {}
Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
(中略)
E: Package 'docker-ce' has no installation candidate
(後略)

まず最初の疑問の「YAMLでshellを指定した場合」ですが、単にそこでシェルが起動して処理が止まります。よってここで手入力で追加の処理を入れ込むことが可能ではありますが、自動化されるわけではありません。また、exitで抜けると、その次の処理から継続して行われます。

次の疑問である「docker-ceパッケージのインストール」ですが、これは単純に失敗します。失敗した結果、ISOイメージは作られません。

そこでさらに次のようなファイルを作ってみることにしましょう。

cd new/ubuntu-server-minimal/
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o etc/apt/keyrings/docker.gpg
chmod a+r etc/apt/keyrings/docker.gpg
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee etc/apt/sources.list.d/docker.list > /dev/null
chroot . addgroup --system docker
exit

これはnew/ubuntu-server-minimal/にディレクトリを移動して、いわゆるDockerのリポジトリを追加するコマンドを実行しています。注意点は各種ファイルパスの先頭のスラッシュを省く必要があることです。そうしないとホストのファイルを変更してしまうためです。

さらに最後から2行目ではchrootコマンドを使っています。これにより、指定したsquashfsをルートファイルシステムと仮定して、コマンドを実行できます。

これをsample.txtという名前で保存して、標準入力にリダイレクトしてみましょう。

$ sudo ~/.local/bin/livefs-edit ubuntu-22.04.2-live-server-amd64.iso test.iso --action-yaml docker.yaml < sample.txt
(中略)
Adding group `docker' (GID 112) ...
(中略)
Get:1 https://download.docker.com/linux/ubuntu jammy InRelease [48.9 kB]
(中略)
Setting up docker-ce (5:24.0.1-1~ubuntu.22.04~jammy) ...
(中略)
Writing to 'stdio:test.iso' completed successfully.

今度は処理が止まらずに動いているようです。さらにdocker-ceパッケージも無事にインストールできました。このように標準入力のリダイレクトと組み合わせれば、かなり柔軟にLive環境をカスタマイズできます。

ちなみに、この方法で作ったLiveイメージでは、dockerサービスは起動していません。どうもaddgroupはubuntu-server-minimalとは別のレイヤーのものが使われているようです。もちろんLive環境を起動したあとにaddgroup --system dockerを実行して、systemctl start dockerすると無事にdockerコマンドが使えるようになります。

ぜひ、各自でUbuntuサーバーのインストールISOをカスタマイズしてみてください。

おすすめ記事

記事・ニュース一覧