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

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

一方 AWS を活用している Netflix は、[aminator](https://github.com/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](https://cloud-images.ubuntu.com/releases/) で配布されている 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](https://coreos.com/why/) のイメージファイルも Ubuntu 16.04 と同様の手順で chroot を使用して編集できます。しかしながら、Container Linux の[パーティション情報](https://coreos.com/os/docs/latest/sdk-disk-partitions.html)は 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
```