Docker コンテナが起動中に新たなデバイスを bind mount 先にマウントしてもコンテナは認識しない

はじめに

お疲れ様です。
Acompany の R&D チーム所属の qweoaiu です。

本記事では、Docker の mount 機能に関する豆知識を紹介します。

本記事では、ホストで行うマウントをカタカナで「マウント」、Docker のマウント機能を英字で「mount」と表記します。

まとめ

内容を簡潔にまとめると、次のとおりです。

Docker コンテナが起動中のときに、mount したディレクトリにホスト側で新たなデバイスをマウントすると、コンテナはそのマウントポイントを、

  • bind mount のときは認識しない

  • volume mount のときは認識する

ディスク容量不足が予期されるときの対処

Bind mount は便利ですが、ディスク容量が足りなくなる可能性がある場合は volume mount を使用しましょう。
Docker 公式は volume mount の使用を推奨しています。

Volumes are the preferred mechanism for persisting data generated by and used by Docker containers. While bind mounts are dependent on the directory structure and OS of the host machine, volumes are completely managed by Docker. Volumes have several advantages over bind mounts:

https://docs.docker.com/storage/volumes/

エラーから復帰できる環境であれば、次のエラーが出てきても、ディスクを追加してマウントすればなんとかなる可能性があります。

OSError: [Errno 28] No space left on device

そのような状況に陥る前に、容量に余裕のあるディスクを最初から使いましょう。

検証

検証環境を用意して、実際に確認します。
コマンドの出力は一部加工しています。
OS は Debian 11 bullseye です。

デバイスの準備

root ファイルシステムが /dev/sdaX にあるホスト環境に、新たなデバイス /dev/sdb を追加し、/dev/sdb1 に ext4 パーティションを用意します。
/dev/sdb1 が Docker コンテナの mount 先にマウントするパーティションです。

まず、fdisk コマンドで、/dev/sdb が存在するか確認します。

$ sudo fdisk -l
Disk /dev/sda: 30 GiB, 32212254720 bytes, 62914560 sectors
Disk model: PersistentDisk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 
Device      Start      End  Sectors  Size Type

...

Disk /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk model: PersistentDisk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

/dev/sdb が認識されているので、GPT パーティションを作成し、ext4 でフォーマットします。

$ sudo fdisk /dev/sdb

Welcome to fdisk (util-linux 2.36.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x0a2696a7.

Command (m for help): g
Created a new GPT disklabel (GUID: ).

Command (m for help): n
Partition number (1-128, default 1):
First sector (2048-20971486, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-20971486, default 20971486):

Created a new partition 1 of type 'Linux filesystem' and of size 10 GiB.

Command (m for help): p
Disk /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk model: PersistentDisk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 674D4DE8-37A0-5444-9E02-C26287EA2A3C

Device     Start      End  Sectors Size Type
/dev/sdb1   2048 20971486 20969439  10G Linux filesystem

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

$ sudo mkfs.ext4 /dev/sdb1
mke2fs 1.46.2 (28-Feb-2021)
Discarding device blocks: done
Creating filesystem with 2621179 4k blocks and 655360 inodes
Filesystem UUID: 5bc9e73e-58d2-4731-b02a-c7c8dea250fa
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

これでデバイスの準備は完了です。

Bind mount で実験

Docker コンテナ起動中に bind mount 先に /dev/sdb1 をマウントして、コンテナがマウントポイントを認識できないことを確認します。

次の docker-compose.yaml を使用します。
ホストの ./data がコンテナの /data に bind mount されます。

version: '3.8'

services:
  srv:
    container_name: cont
    image: debian:bookworm
    restart: always
    volumes:
      - ./data:/data

コンテナの中に入り、/data/mount_point を作成し、/data/mount_point/a.txt を作成します。

$ sudo docker compose run srv bash
[+] Creating 1/0
 ✔ Network bind_mount_test_default  Created                                                                                      0.1s
$ ls data
$ mkdir data/mount_point
$ ls data
mount_point
$ echo 'a' > data/mount_point/a.txt

ホストで、./data/a.txt が存在することを確認します。

$ ls data/mount_point
a.txt
$ cat data/mount_point/a.txt
a

確認作業に入ります。
ホストで、/dev/sdb1 を ./data/mount_point にマウントし、a.txt が見えないことを確認します。

$ sudo mount -t ext4 /dev/sdb1 data/mount_point
$ ls data
mount_point
$ ls data/mount_point/
lost+found

コンテナが新たな /data/mount_point を認識していれば、a.txt は存在しないはずです。
実際に確認すると、a.txt が存在します。
Bind mount だと、コンテナは新たなマウントポイントを認識してくれないことがわかりました。

$ ls data
mount_point
$ ls data/mount_point/
a.txt

Volume mount

同様の実験を volume mount で行います。
次の docker-compose yaml を使用します。
/data はボリューム data_vol に volume mount されます。

version: '3.8'

services:
  srv:
    container_name: cont
    image: debian:bookworm
    restart: always
    volumes:
      - data_vol:/data
volumes:
  data_vol:

コンテナの中に入り、/data/mount_point を作成し、/data/mount_point/a.txt を作成します。

$ sudo docker compose run srv bash
[+] Creating 1/0
 ✔ Network bind_mount_test_default  Created                                                                                      0.1s
$ ls data
$ mkdir data/mount_point
$ ls data
mount_point
$ echo 'a' > data/mount_point/a.txt

ホストで、a.txt が存在することを確認します。

$ sudo ls /var/lib/docker/volumes/bind_mount_test_data_vol/_data/mount_point
a.txt
$ sudo cat /var/lib/docker/volumes/bind_mount_test_data_vol/_data/mount_point/a.txt
a

確認作業に入ります。
ホストで、/dev/sdb1 を /var/lib/docker/volumes/bind_mount_test_data_vol/_data/mount_point にマウントし、a.txt が見えないことを確認します。

$ sudo mount -t ext4 /dev/sdb1 /var/lib/docker/volumes/bind_mount_test_data_vol/_data/mount_point
$ sudo ls /var/lib/docker/volumes/bind_mount_test_data_vol/_data/mount_point
lost+found

コンテナが新たな /data/mount_point を認識していれば、a.txt は存在しません。
実際に確認すると、a.txt ではなく lost+found が表示されます。
Volume mount であれば、コンテナは新たなマウントポイントを認識してくれることがわかりました。

$ ls /data/mount_point
lost+found

おわりに

本記事では、Docker コンテナが起動中に、mount 先に新たなデバイスをマウントしたときの挙動が、bind mount と volume mount で異なることを確認しました。

最後に宣伝です。
Acompany では現在メンバを募集中です。
気になる方は以下のリンク先の採用情報をご覧ください。

No more OSError: [Errno 28] No space left on device!