第590回で紹介したマルチプラットフォームな仮想マシン管理ツールである「Multipass」は、原則としてUbuntuサーバーのイメージを起動するように作られています。しかしながら実は任意の仮想マシンイメージの指定も可能です。そこでPackerで仮想マシンイメージを作成し、Multipassで管理する方法を紹介しましょう。
MultipassとPacker
第590回「Windows/macOS/Linuxで使える仮想マシン管理ツール『multipass』」で紹介したように、MultipassはWindowsやmacOS、Linuxでも使える仮想マシンを管理するツールです。DockerやLXDのようにコマンドラインから気軽に仮想マシンを起動・終了するような使い方を想定しています。
multipassは特に指定しなければサーバー版のUbuntuをベースイメージとして使います。もし自動的にカスタマイズしたいなら、cloud-initを使うか、インスタンス作成後にプロビジョニングツールを使うことを想定しています。しかしながらあらかじめ構築済みのイメージやUbuntu以外の仮想マシンを立ち上げたいこともあるはずです。
そこで今回は自分で仮想マシンイメージを作る方法とそれをmultipassで利用する方法を紹介します。Ubuntu版multipassの標準のバックエンドはQEMUなので、QEMUで任意のOSのインストーラーを使ってディスクイメージを作成し、それを利用しても良いのですが若干面倒です。よってマシンイメージの作成自動化ツールとして広く使われている「Packer」を使うことにしましょう。PackerであればWindowsのHyper-V用イメージにも対応しています。
PackerはUbuntuならパッケージ管理ツールからインストール可能です。しかしながら18.04だと1.0.4、現在開発中の20.04ですら1.3.4と古いバージョンしかインストールできません[1]。DebianでもFTBFSな状態なので、誰かがメンテナンスを引き継がなければ将来的にパッケージそのものがなくなってしまう可能性があります[2]。
よって素直にPackerのダウンロードページからバイナリをインストールしましょう。PackerはGo言語で作られたバイナリなので、ホスト側に必要なのはモダンなlibcだけです。おそらくUbuntuであれば、サポートしているどのバージョン・アーキテクチャーでも特に問題はないでしょう。該当ページの「Linux」から、使っているマシンのCPUアーキテクチャーに合わせてアーカイブファイルをダウンロードしてください。今回はAMD64版を使うことにします。
バイナリひとつ配置すればいいので簡単ですね。また、Packer自身がQEMUを使うように設定するため、QEMUパッケージをインストールしておく必要があります。multipassパッケージ同梱のQEMUを使ってもいいのですが、いろいろ調整が必要なので、パッケージ版をインストールしておきましょう。
これでインストールは完了です[3]。
Packer用ファイルとcloud-initデータの作成
PackerはJSONファイルに基づいて、仮想マシンイメージを構築します。builderがISOファイルやルートファイルシステムのアーカイブをダウンロードした上で仮想マシンを起動し、provisionerがインストール後の諸々の設定を行います。builderがOSごとのインストール方法や作成するイメージ形式の違いを吸収し、パッケージのインストールや設定ファイルの作成はprovisionerが担うと思っておけば良いでしょう。正確にはbuilderがOSのインストールを行うわけではありません。しかしながら今回はcloud-initによって、builderによる仮想マシン起動の際にインストールも事実上「自動化」します。
ベースイメージはUbuntu Cloud Imagesのデータを使うことにします。これは各種クラウドサービスで使用するUbuntuのベースイメージを配布しているサイトです。リリースごとに最新のアップデートを適用したイメージが、ほぼデイリーでビルドされるので、ベースイメージとしてはもってこいなのです。
ちなみにこのイメージにはアカウントは設定されていません。ユーザーごとに異なる設定はクラウドサービスごとに合わせたcloud-initで設定する、というスタンスです。
Packerでイメージを構築する際、Packerのprovisionerが仮想マシンインスタンス内部にSSHログインできる必要があります[4]。つまりあらかじめアカウントが必要です。そこでQEMU用のbuilderからcloud-initの設定を行うことにします。これには第561回の「ローカルインストール時もcloud-initを活用する」で紹介した知識が利用可能です。
cloud-initをサポートしたイメージは、どこかからcloud-initの設定ファイルを取得しなくてはなりません。一般的なクラウドサービスは、たとえばシリアルコンソール経由などサービスごとに取得方法を用意しており、ユーザーはそれを気にすることなくデータだけを渡せば良いようになっています。それに対してQEMUはもともとcloud-initには対応していないため、cloud-initのプログラム側で考慮する必要があります。それがNoCloudという仕組みです。
NoCloudでは次のいずれかの方法で、cloud-initの設定ファイルを取得可能です。
- ボリュームラベルが「cidata」か「CIDATA」な、vfatもしくはiso9660のストレージ
- カーネルコマンドラインで指定されたパラメーター
- SMBIOS上のシリアル番号に記載された文字列
後者のふたつに関しては、cloud-initデータが存在するファイルの位置を指定可能です。つまり任意のURLにcloud-initファイルを配置し、それをダウンロードできるのです。
Packerのbuilderは「http_directory
」というパラメーターを指定することで、インスタンス作成時にそのディレクトリを見せるHTTPサーバーを立ち上げます。よってここにcloud-initファイルを置くことで、仮想マシンイメージ構築時にcloud-initを実行できるようになります。
最後に注意が必要なのは、「multipass自身もインスタンス作成時にcloud-initを使用している」点です。つまり作成した仮想マシンイメージとmultipassのcloud-init設定がバッティングしてしまうと、その仮想マシンイメージをmultipassで立ち上げられません。そこでprovisionerで、いくつかの調整を行います[5]。
まとめるとmultipass用のイメージをPackerで構築するために、次のファイルを準備する必要があります。
- cloud-init用の設定ファイル(
meta-data
とuser-data
)
- Packer用の設定ファイル(
ubuntu.json
)
それでは順番に作っていきましょう。
cloud-init用の設定ファイルの作成
cloud-init用の設定ファイルは比較的単純です。要するにPackerのcommunicatorがインスタンス内部にログインできるようにしておけば良いのですから。
cloud-data/meta-data
はcloud-initで使うインスタンスIDやホストネームを指定するために使うファイルですが、今回は何も設定しません。そこで空ファイルのみを作っています。ちなみにインスタンスIDはPackerのほうで設定しています。
cloud-init本体の設定のポイントは次のとおりです。
ssh_pwauth
の設定でパスワードログインを許可しておく
sudo
の設定でパスワード無しのsudo
を許可しておく
passwd
の設定でパスワードを設定しておく
lock_passwd
の設定でパスワードログインを許可しておく
PackerのSSH communicatorはSSHの公開鍵ログインも利用可能です。複数のユーザーで運用するPackerファイルを作るなら公開鍵の設定にしておくべきでしょう。しかしながら今回はPackerファイルにパスワードを直接書いてパスワードログインすることにします。Packerファイルには生のパスワードを直接記述しますが、cloud-initのほうはハッシュ化したものを記述します。これはcloud-initのデータが作成後のルートファイルシステムの中に残るためです。
また上記では、Packerのprovisionerが設定を行う際にsudo
コマンドをパスワードなしで実行できるような設定も行っています。
今回はPacker用のアカウントを「ubuntu」にしましたが、これはmultipassが自動で作るアカウントと同じ名前です。Packer用のアカウントということなら、別の名前にしても良いかもしれません。
Packer用の設定ファイルの作成
次にPacker用の設定ファイルを作成しましょう。
前半はbuildersの設定です。詳細はQEMU用のbuilderを確認してもらうとして、今回重要なポイントは次のとおりです。
http_directory
でcloud-initファイルのディレクトリを指定
iso_url
とiso_checksum_XXX
でダウンロードするイメージを指定
qemuargs
でcloud-initのダウンロードもとを指定
ssh_password
とssh_username
でcommunicatorが使うパスワードとアカウントを指定
type
でQEMUを利用することを指定
iso_url
とiso_checksum_XXX
はUbuntu Cloud Imagesを参考に、使用するリリースなどにあわせて変更してください。もちろん、ローカルのファイルを指定してもかまいません。
qemuargs
はPackerがイメージ構築時に利用するQEMUコマンドの引数です。ここにSMBIOSの設定を渡すことで、provisionerが動く前にcloud-initを実行できるようにしています。
「{{ .HTTPIP }}
」と「{{ .HTTPPort }}
」は、builderが自動的に展開するIPアドレスとポート番号です。QEMUの場合、IPアドレスはユーザーネットワークのゲートウェイアドレスが割り当てられ、ポート番号はランダムな値が使われます。もちろん直接アドレスとポート番号を指定すれば、PackerのHTTPサーバー以外も利用可能です。
後半はprovisionerが実行するコマンドのリストです。Shell Provisionerによって、inline
でリストアップされたコマンドを順番にsudo
で実行しています。
主にbuilderが実行したcloud-initで生成されたファイルのクリーンナップ処理です。これによりmultipassによるcloud-initがキレイに実行されることになります。
/etc/machie-id
は初回起動時にsystemd-machine-id-setupで生成される、インストールされたマシン固有のIDを保存するファイルです。空でない場合はマシンIDを生成しません。つまりPackerで構築時にイメージを起動した時点でマシンIDが生成されるため、構築完了後にIDを削除しておかないと、同じイメージファイルを使うすべてのインスタンスが同じマシンIDを持ってしまうのです。空のファイルとして存在する必要があるため、truncate
コマンドで中身を消しています。
/etc/group
他のmv
コマンドの実行は「cloud-init前の設定に戻す」処理です。cloud-initの内部で実行されるuseradd
などのコマンドは、設定ファイルを書き換える際に「-
」を付けたオリジナルファイルのバックアップを残すようになっています。このファイルをもとに戻すことで、cloud-init時に作成したユーザーを「なかったこと」にできるわけです。
今回はPackerとmultipassで同じユーザーではありますが、multipassがユーザーを作れるように元に戻しています。またPacker用にユーザーを作るにしても、それを構築完了後に削除したいなら最後にこのようなコマンドを実行すると良いでしょう。
fstrim
はファイルの削除等によって消された領域がイメージファイルから確実に削除になるようにするツールです。builderのほうでdisk_discard
をunmap
に設定しているため、fstrim
がイメージサイズの削減にも繋がります。今回のように削除しているファイルがそうでもない場合はあまり恩恵はありませんが、シンプロビジョニングなイメージを作る際の手順として覚えておきましょう。
イメージのビルドとmultipassでの利用
作成したPackerファイルはvalidateサブコマンドでチェックできます。
またfixサブコマンドを使うと、JSONファイルそのものを整形できます。他人と共有するPackerファイルは、設定ファイルの書き方や位置を揃えるためにもfixコマンドを積極的に活用していきましょう。
イメージの作成はbuildサブコマンドです。
作成したイメージは「output-qemu/packer-qemu
」として保存されます。
あとはこのファイルをmultipassのインスタンス作成時に指定するだけです。
これで任意の「構築済み仮想マシンイメージ」を作れるようになりました。cloud-initをサポートしているOSなら、Ubuntu以外でも同様の方法でイメージの作成が可能です。それはまたの機会に。