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