Edited at

QCOW2 フォーマットのイメージを chroot で編集する

More than 1 year has passed since last update.

AWS や OpenStack などで起動する仮想マシンのイメージを作る場合は Packer がよく使われますが、Packer は仮想マシンを起動し、そこにログインしてファイルなどを変更後、その仮想マシン全体を保存することでイメージを作成します。

一方 AWS を活用している Netflix は、aminator を使用してベースとなるイメージをビルド用のインスタンスにマウントし、chroot を使用してマウントしたイメージの中のファイルなどを編集後に、アンマウントするという手法をとっているようです。chroot を使った手法を採用することで、インスタンスの起動や停止の時間を省略でき、より高速に仮想マシンのイメージの作成ができるそうです。

chroot を使った手法は AWS AMI に限らず使えるのでは、と思ったため、今回は KVM や QEMU で使用される QCOW2 フォーマットのイメージを chroot でビルドする方法をまとめてみます。


事前準備

今回は Ubuntu 16.04 をインストールしたマシン上で QCOW2 フォーマットの仮想マシンイメージを変更します。QCOW2 をマウントするには Linux の Network Block Device (NBD) カーネルモジュールと qemu-nbd コマンドを使用する必要があるため、以下のコマンドでカーネルモジュールの読み込みと、qemu-nbd コマンドをインストールします。

$ sudo apt update

$ sudo apt install -y qemu-utils
$ sudo modprobe nbd


Ubuntu 16.04 イメージファイルの編集

ここからは Ubuntu Cloud Images で配布されている Ubuntu 16.04 のイメージファイルを編集する方法をまとめます。最初にイメージファイルをダウンロードします。以下は Ubuntu 16.04 のイメージファイルをダウンロードするコマンドです。

$ curl -L -O https://cloud-images.ubuntu.com/releases/xenial/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img

次に、ダウンロードしたイメージファイルを NBD デバイスに接続して、マウント可能な状態にします。NBD デバイスの接続には qemu-nbd コマンドを使用します。以下の例では /dev/nbd0 にイメージファイルを接続しています。

$ sudo qemu-nbd -c /dev/nbd0 ubuntu-16.04-server-cloudimg-amd64-disk1.img

QCOW2 フォーマットのイメージファイルには複数のパーティションを含めることができます。そのため、マウント時にパーティション情報も含める必要があるようです。パーティション情報はイメージファイルを接続した NBD デバイスに対して fdisk コマンドを使用して確認できます。以下の例では、パーティション数は1つで、/dev/nbd0p1 に接続されていることが分かります。

$ sudo fdisk -l /dev/nbd0

Disk /dev/nbd0: 2.2 GiB, 2361393152 bytes, 4612096 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x56add33d

Device Boot Start End Sectors Size Id Type
/dev/nbd0p1 * 2048 4610047 4608000 2.2G 83 Linux

イメージファイルを NBD デバイスに接続でき、パーティション情報も確認できたら、マウント先のディレクトリを作成し、そこにイメージファイルを接続した NBD デバイスをマウントします。以下のコマンドでは mount コマンドのデバイスに先ほど確認した1番目のパーティションが接続されている /dev/nbd0p1 を指定しています。

$ sudo mkdir /mnt/ubuntu-image

$ sudo mount /dev/nbd0p1 /mnt/ubuntu-image

イメージのマウントができたら、chroot コマンドでイメージ内のファイルを直接編集できます。

$ sudo chroot /mnt/ubuntu-image

イメージ内のファイルの編集が完了したら、忘れずにアンマウントを実行します。

$ sudo umount /mnt/ubuntu-image

アンマウント後は、NBD デバイスに接続していたイメージファイルを以下のように切断することも忘れないようにします。

$ sudo qemu-nbd -d /dev/nbd0 

これで Ubuntu 16.04 のイメージファイルの編集は完了です。


Container Linux イメージファイルの編集

Container Linux のイメージファイルも Ubuntu 16.04 と同様の手順で chroot を使用して編集できます。しかしながら、Container Linux のパーティション情報は Ubuntu 16.04 のイメージとは異なり少し複雑で、それを正しく理解しておく必要があったので、ここにまとめておきます。

最初に Container Linux のイメージファイルを以下のようにダウンロードします。

$ curl -L -O https://stable.release.core-os.net/amd64-usr/current/coreos_production_qemu_image.img.bz2

$ bunzip2 coreos_production_qemu_image.img.bz2

ダウンロードしたイメージを NBD デバイスに接続します。

$ sudo qemu-nbd -c /dev/nbd0 coreos_production_qemu_image.img

パーティション情報を確認すると、以下のようになっています。Container Linux のドキュメントに記載されている情報と一致しています。

$ sudo fdisk -l /dev/nbd0

Disk /dev/nbd0: 8.5 GiB, 9116319744 bytes, 17805312 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 00000000-0000-0000-0000-000000000001

Device Start End Sectors Size Type
/dev/nbd0p1 4096 266239 262144 128M EFI System
/dev/nbd0p2 266240 270335 4096 2M BIOS boot
/dev/nbd0p3 270336 2367487 2097152 1G unknown
/dev/nbd0p4 2367488 4464639 2097152 1G unknown
/dev/nbd0p6 4464640 4726783 262144 128M Linux filesystem
/dev/nbd0p7 4726784 4857855 131072 64M unknown
/dev/nbd0p9 4857856 17801215 12943360 6.2G unknown

マウント先のディレクトリを作成し、そこにイメージファイルを接続した NBD デバイスをマウントします。Container Linux のルートファイルシステムが含まれているのは9番目のパーティションのようなので、デバイスは /dev/nbd0p9 を指定します。

$ sudo mkdir /mnt/container-linux-image

$ sudo mount /dev/nbd0p9 /mnt/container-linux-image

Container Linux のイメージファイルは、この状態ではまだ chroot できません。シェルなどのコマンドを含む /usr がルートファイルシステムに含まれていないためです。コマンドなどが含まれるのは3番目のパーティションなので、追加でこれをマウントします。

$ sudo mount -t ext4 -o ro /dev/nbd0p3 /mnt/container-linux-image/usr

/usr のマウントができれば、chroot コマンドでイメージ内のファイルを直接編集できます。

$ sudo chroot /mnt/container-linux-image

イメージ内のファイルの編集が完了したら、忘れずにアンマウントを実行します。

$ sudo umount /mnt/container-linux-image/usr

$ sudo umount /mnt/container-linux-image

アンマウント後は、NBD デバイスに接続していたイメージファイルを以下のように切断することも忘れないようにします。

$ sudo qemu-nbd -d /dev/nbd0 

これで Container Linux のイメージファイルの編集は完了です。


Docker コンテナ内でのイメージファイルの編集

Docker コンテナ内でも上記の手順のようにイメージファイルの編集ができます。--privileged オプション付きで ubuntu:16.04 コンテナを起動します。起動時にホストの /dev もマウントしてください。なおコンテナの起動前にホスト側で Network Block Device (NBD) カーネルモジュールは読み込んでおく必要があります。

$ docker run -it -v /dev:/dev --privileged ubuntu:16.04