Dockerコンテナ上でイメージファイルをマウントする

## 3行まとめ

1. `parted`コマンドでバイト単位のパーティション開始位置を確認する。
2. `docker container run`コマンドに`--privileged`オプションを付加する。
3. `mount --options loop,offset=[オフセット値] [イメージファイル] [マウント先]`でマウントする。

## 1. 概要

　Dockerコンテナ内でイメージファイル（例えばOSイメージ）をマウントしたいことがあります。
毎回似たようなことを調べているので、整理も兼ねて具体例をここに記しておきます。

**2019年9月11日12時55分追記:** [mt08さんからのコメント](https://qiita.com/yuyakato/items/5dd06fb179922206044d#comment-885516a16b64e5e442a0)によると、Raspbianは公式のルートファイルシステムが別途配布されているとのことです。そのため、Raspbianを対象にこれらを行うことにはほとんど意味がありませんが、他のイメージファイルでは有用かと思います。

## 2. 環境

　今回試した環境は以下の通りです。

* OS: Ubuntu 16.04.5 LTS
* Docker: 18.09.4

```text
host$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.5 LTS"

host$ docker version
Client:
 Version:           18.09.4
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        d14af54
 Built:             Wed Mar 27 18:34:51 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.4
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       d14af54
  Built:            Wed Mar 27 18:01:48 2019
  OS/Arch:          linux/amd64
  Experimental:     false
```

## 3. 対象イメージ

　今回の対象イメージは、Raspberry Pi用のLinuxディストリビューションである「Raspbian Buster Lite」（2019-07-10版）です。
Dockerコンテナ内でダウンロードしても良いですが、試行錯誤を容易にするため、Dockerコンテナの外側でダウンロードしました。

```text
host$ wget https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip
host$ ls -l 2019-07-10-raspbian-buster-lite.zip
-rw-rw-r-- 1 yuya yuya 426250971 Jul 10 09:23 2019-07-10-raspbian-buster-lite.zip

host$ sha256sum 2019-07-10-raspbian-buster-lite.zip
9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e  2019-07-10-raspbian-buster-lite.zip

host$ unzip 2019-07-10-raspbian-buster-lite.zip
host$ sha256sum 2019-07-10-raspbian-buster-lite.img
cce3cdaa078597d0b2181f7e121a452346c72bca9095893e56e01c82e524c276  2019-07-10-raspbian-buster-lite.img
```

## 4. Dockerコンテナ内でのマウント

　今回はUbuntu 18.04のDockerコンテナ上で作業を行いました。
以下、Dockerホスト側のプロンプトを`host$`、Dockerコンテナ内のプロンプトを`docker$`で示します。

```text
host$ docker image pull ubuntu:18.04
18.04: Pulling from library/ubuntu
Digest: sha256:d1d454df0f579c6be4d8161d227462d69e163a8ff9d20a847533989cf0c94d90
Status: Image is up to date for ubuntu:18.04

host$ docker container run --interactive --tty --rm --privileged \
  --volume $(pwd):/workspace \
  ubuntu:18.04
```

　まず、イメージファイルに含まれるパーティションと、その位置（オフセット）を確認します。
今回のイメージには2つのパーティション（FAT32とext4）が含まれており、2つ目のパーティションが目的のパーティションです。このパーティションの`Start`の値（今回は`276824064`）を控えておきます。

　`parted`ではなく`fdisk`でも確認できますが、セクタ単位で表示されるためバイト単位に換算が必要です。`parted`であればバイト単位で表示できます。

```text
docker$ apt-get update && apt-get install --yes --no-install-recommends parted
docker$ parted /workspace/2019-07-10-raspbian-buster-lite.img unit B print
Model:  (file)
Disk /workspace/2019-07-10-raspbian-buster-lite.img: 2197815296B
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number  Start       End          Size         Type     File system  Flags
 1      4194304B    272630271B   268435968B   primary  fat32        lba
 2      276824064B  2197815295B  1920991232B  primary  ext4
```

　続いて、イメージファイルをマウントします。`mount`コマンドの`offset=`に、先ほど控えた値（パーティションの開始位置）を指定します。
マウントが成功すると、イメージファイルの内容を確認することができます。

```text
docker$ mkdir --parents /mnt/image
docker$ mount --options loop,offset=276824064 /workspace/2019-07-10-raspbian-buster-lite.img /mnt/image

docker$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0    7:0    0    80K  0 loop
loop1    7:1    0    80K  0 loop
loop2    7:2    0   1.8G  0 loop /mnt/image
sda      8:0    0   3.7T  0 disk
`-sda1   8:1    0   3.7T  0 part /workspace
sdb      8:16   0 232.9G  0 disk
|-sdb1   8:17   0   512M  0 part
|-sdb2   8:18   0 216.5G  0 part
`-sdb3   8:19   0  15.9G  0 part
sdc      8:32   0   2.7T  0 disk
sdd      8:48   0 489.1G  0 disk
`-sdd1   8:49   0   489G  0 part /etc/hosts
sr0     11:0    1  1024M  0 rom

docker$ ls -l /mnt/image/
total 88
drwxr-xr-x  2 root root  4096 Jul 10 00:08 bin
drwxr-xr-x  2 root root  4096 Jul 10 00:20 boot
drwxr-xr-x  4 root root  4096 Jul 10 00:03 dev
drwxr-xr-x 80 root root  4096 Jul 10 00:21 etc
drwxr-xr-x  3 root root  4096 Jul 10 00:07 home
drwxr-xr-x 16 root root  4096 Jul 10 00:09 lib
drwx------  2 root root 16384 Jul 10 00:20 lost+found
drwxr-xr-x  2 root root  4096 Jul 10 00:03 media
drwxr-xr-x  2 root root  4096 Jul 10 00:03 mnt
drwxr-xr-x  3 root root  4096 Jul 10 00:07 opt
drwxr-xr-x  2 root root  4096 Jun  6 15:42 proc
drwx------  2 root root  4096 Jul 10 00:03 root
drwxr-xr-x  4 root root  4096 Jul 10 00:03 run
drwxr-xr-x  2 root root  4096 Jul 10 00:09 sbin
drwxr-xr-x  2 root root  4096 Jul 10 00:03 srv
drwxr-xr-x  2 root root  4096 Jun  6 15:42 sys
drwxrwxrwt  2 root root  4096 Jul 10 00:21 tmp
drwxr-xr-x 10 root root  4096 Jul 10 00:03 usr
drwxr-xr-x 11 root root  4096 Jul 10 00:03 var
```

## 5. 補足: `--privileged`オプションについて

　`docker container run`コマンドに`--privileged`オプションを付けない場合、以下の通り`mount`コマンドがエラーとなります。

```text
host$ docker container run --interactive --tty --rm \
  --volume $(pwd):/workspace \
  ubuntu:18.04

docker$ mount -o loop,offset=276824064 /workspace/2019-07-10-raspbian-buster-lite.img /mnt/image
mount: /mnt/image: mount failed: Operation not permitted.
```