2022-07-24 追記
最後の実際のコードで、イメージを /target/cdrom 以下に mount する際、実際のデバイスを mount する代わりに mount --bind
でパスを複製することにしました。
このほうが簡単ですし、デバイスを二回 mount するのは動かない場合があるためです。
主題
Ubuntu 20.04 をオフラインでセットアップできるイメージを作りました。 必要なパッケージ(ソフトウェア)はイメージを作る際に指定することで、イメージの中に取り込み、セットアップの際に自動でインストールされます。 ICPC アジア予選横浜大会で使用するイメージはこの形式をもとにしています。 Ubuntu 18.04 までの方法が使えなくなったので、代わりの方法を調べました。
背景
Ubuntuのインストールメディアにはパッケージはあまり付属していなくて、セットアップの際に必要に応じて追加パッケージをインターネットからダウンロードしてインストールする仕組みになっています。 インターネットに繋がっていないコンピュータに Ubuntu をインストールする場合はこれでは困ります。
ICPC アジア予選横浜大会 では選手が使うコンピュータを大会で提供しています。 会場のコンピュータはインターネットには繋がっていないので、オフラインでセットアップできるUbuntuのインストールメディアを作って使用しています。
Ubuntu 18.04 までは Community Help Wiki: InstallCDCustomization の記述に従って、追加パッケージを含んだ extras コンポーネントをイメージ内の apt archive に作り、 kickseed で自動インストールスクリプトを与えていました。 kickseed でパッケージを指定すると、追加した extras コンポーネントからもちゃんとパッケージをインストールできました。 Ubuntu 20.04 では Ubuntu Server で Debian Installer が廃止され、代わりに Subiquity というインストーラーが提供されるようになりました。 Subiquity 上で自動セットアップスクリプトを書く autoinstall が提供されるようになったので、スクリプトとイメージの書き方をこれに移行することになりました。
要求仕様
以下のような Ubuntu インストーライメージを作成します。
- 指定したパッケージがインストールされる。
- インターネットのない環境でも動作する。
- インターネットのある環境でも動作する。
最後のものは追加仕様です。コンテストに参加する選手向けに、作成したイメージを配布しています。その際インストールに手間取らないよう、インターネットがない環境とある環境両方で動くことを確認します。
autoinstall の基本
説明は他のドキュメントに譲ります。公式ドキュメントはチュートリアルを含め読みやすいですが、説明外のことをしたいときに何もわからないと思います。 Qiita の記事にはとてもお世話になりました。
正解
私が知る限りの正解は2通りです。
正解1 イメージにapt archive を配置し、 late-commands でそれを使うよう設定する
イメージの中の適当なディレクトリに apt archive を作成し、late-commands の中でそれを /target/ から読めるようにします。その後 /etc/apt/sources.list を書き換えてそれを指定します。
これを採用しました。我々はイメージに /extra/pool/ ディレクトリを作り、そこに必要な *.deb パッケージを置き、 /extra/dists/focal/main/binary-amd64/ あたりに apt ftparchive を作成しました。その後、late-commands で以下のことを行いました。
/target/cdrom にイメージを再マウント。最後に説明。
echo ‘deb file:///cdrom/extra/ focal main’ > /etc/apt/sources.list
apt-get update
apt-get install $packages
とりあえず動きます。難点として、user-data での指定が全然できないことが挙げられます。 late-commands でなんでもできるので、それをしているだけになります。
正解2 filesystem.squashfs の中に apt archive を配置し、 user-data ファイルの apt 節で指定
filesystem.squashfs の中に apt archive を作成し、それを参照します。
この場合 path が読めるのでそのまま user-data で apt.primary.uri: file:///path-to-archive/ の指定ができます。追加で packages 節でインストールするパッケージを選べると思います。
インストール先ディスクに数 GB がりがり書き込むことになるため避けました。しかし user-data で指定ができることを考えるとアリかも知れません。
不正解1 イメージに apt archive を配置し、user-data ファイルの apt 節で設定
イメージの中の適当なディレクトリに apt archive を作成し、user-data で apt.primary.uri: file:///cdrom/extra/ などと指定します。late-commands で /target/cdrom/ 以下に配置。
これをした場合、インターネットがある環境でのインストールに失敗します。インターネットが存在する場合、 apt-get update が late-commands 以前に走り、そのタイミングで上の file://cdrom/extra/ が読めずにエラーを吐きます。 インターネットがない環境でのインストールは成功します。
微かに正解 イメージに存在する apt archive に追加コンポーネントを配置
イメージの中に存在する apt archive に追加コンポーネントを作成します。上の 18.04 までの方法です。この場合以下の二つに分けられます
不正解 user-data ファイルの apt 節で指定
user-data で apt.primary.uri: file:///cdrom/ などと指定。
この場合、不正解1と同様にインターネットがある環境でのインストールに失敗すると思います。 さらに、 user-data で apt を指定する場合、任意に追加したコンポーネントを読むように指定できません。 autoinstall の user-data での apt の記述でできることは、disable_components を指定してコンポーネントを除外することだけです。任意のコンポーネントを追加できません。
正解 late-commands で指定
late-commands で /target/cdrom/ にイメージを mount して、 /etc/apt/sources.list を書き換え。
正解1とほぼ同様で、イメージに初めから配置されている archive を利用できるようになります。しかし配置されている archive がそんなに多くないのでこの道を通ることはやめました。
よくある質問
もう Ubuntu 22.04 が出ますよ。 Ubuntu 20.04 の情報は古くないですか?
古いです。
方針として Ubuntu LTS リリースをリリースから1年経ってから使うことにしています。以前リリース直後のものを使ったところネットワークドライバ周りのバグでトラブルが起きたことがあるので。 この作業は 2021 年に行う予定でしたが、 2021 年の ICPC アジア予選横浜大会はオンラインで行うことになったためインストールイメージの作業は中断されました。 つまり 22.04 への移行は来年にやります。
手作業でやるの大変じゃないですか
大変です。今回も d-i から autoinstall に書き換えるのに数ヶ月分の週末を使いました。必要なことはそんなに多くないので、何か他の方法をご存知の方がいたら教えていただけると助かります。 とはいえ、毎回トラブルを起こす箇所が異なるので、ここだけが完璧に直ったところで他で苦労しそうです。今までは例えば以下のようなところで詰まっていました。
- upstart が systemd になる
- デフォルトの WM/DM が変わる
- ネットワーク管理方法が変わる
全然違う方法で似たようなことをしています
ぜひやり方を知りたいです。
実際のコード
user-data
# (apt line なし)
late-commands:
- mkdir /target/cdrom
- mount --bind /cdrom /target/cdrom
- curtin in-target –target=/target – bash /cdrom/late-commands.sh
- umount /target/cdrom
late-commands.sh
#/bin/bash
set -eux
echo 'deb file:///cdrom/extra/ focal main' > /etc/apt/sources.list
apt-get update
apt-get -y install $(cat /cdrom/packages | sed '/^[#]/d;/^ *$/d')
# (以降設定が続く)