mountコマンド周りの整理

はじめに

今まで何となく使っていたmountコマンドについて、理解している部分やよくわかっていない部分を整理したいなと思い書きました。作業環境はMac上のVirtualBoxで動かしているCentOS8です。

ファイルシステム

Unixシステムでは全てのファイルは/をルートとしたツリー構造になっています。

$ tree -L 1 /
/
├── bin -> usr/bin
├── boot
├── dev
├── etc
├── home
├── lib -> usr/lib
├── lib64 -> usr/lib64
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -> usr/sbin
├── srv
├── swapfile
├── sys
├── tmp
├── usr
├── vagrant
└── var

データが保存できるSSDやHDDなどのストレージデバイスファイルシステムを作って/にマウントしています。以下のように確認できます。

$ findmnt /
TARGET SOURCE    FSTYPE OPTIONS
/      /dev/sda1 xfs    rw,relatime,attr2,inode64,noquota

VirtualBoxではストレージデバイスの容量を増やせます。30GiBから1GiB増やしました。*1

$ lsblk
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda      8:0    0  31G  0 disk
└─sda1   8:1    0  30G  0 part /

増えた1GiBの部分にパーティションを新たに作ります。

$ sudo fdisk /dev/sda

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


Command (m for help): p
Disk /dev/sda: 31 GiB, 33285996544 bytes, 65011712 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: 0x615169d8

Device     Boot Start      End  Sectors Size Id Type
/dev/sda1  *     2048 62914526 62912479  30G 83 Linux

Command (m for help): n
Partition type
   p   primary (1 primary, 0 extended, 3 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (2-4, default 2):
First sector (62914527-65011711, default 62914560):
Last sector, +sectors or +size{K,M,G,T,P} (62914560-65011711, default 65011711):

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

Command (m for help): p
Disk /dev/sda: 31 GiB, 33285996544 bytes, 65011712 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: 0x615169d8

Device     Boot    Start      End  Sectors Size Id Type
/dev/sda1  *        2048 62914526 62912479  30G 83 Linux
/dev/sda2       62914560 65011711  2097152   1G 83 Linux

Command (m for help): w
The partition table has been altered.
Syncing disks.

パーティションが作成されました。

$ lsblk
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda      8:0    0  31G  0 disk
├─sda1   8:1    0  30G  0 part /
└─sda2   8:2    0   1G  0 part

新しく作ったパーティションファイルシステムを作ります。ファイルシステムの種類にext4を指定しました。

$ sudo mkfs -t ext4 /dev/sda2
mke2fs 1.44.3 (10-July-2018)
Creating filesystem with 262144 4k blocks and 65536 inodes
Filesystem UUID: fd1c3d2f-633c-45b6-a0b1-32b156544e8d
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376

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

直接ストレージデバイスへの書き込み

既にファイルシステムが作られていると以下のように確認されます。

$ sudo mkfs -t ext4 /dev/sda2
mke2fs 1.44.3 (10-July-2018)
/dev/sda2 contains a ext4 file system
        last mounted on Mon Mar  9 01:17:11 2020
Proceed anyway? (y,N)

mkfsコマンドがext4ディスクレイアウトを検知しているのかもしれません*2。ストレージデバイスには直接書き込むこともできるので以下のようにしてランダムな値を書き込んだらmkfs時の確認がされなくなりました。

$ sudo dd bs=1024 count=1024  if=/dev/urandom of='/dev/sda2'

仮想ファイルシステムとmount

mountコマンドはmount -t type device dirというような書式で、デバイスファイルシステム上の指定の場所にアタッチします。先程作ったファイルシステム/mntにマウントします。*3

$ sudo mount -t ext4 /dev/sda2 /mnt
$ findmnt /mnt
TARGET SOURCE    FSTYPE OPTIONS
/mnt   /dev/sda2 ext4   rw,relatime

今マウントした/mntの中にRAMをストレージとしたtmpfsを作ってみます。

$ sudo mkdir /mnt/tmp
$ sudo mount -t tmpfs tmpfs /mnt/tmp
$ findmnt /mnt/tmp
TARGET   SOURCE FSTYPE OPTIONS
/mnt/tmp tmpfs  tmpfs  rw,relatime

tmpfsは電源を落とすと消えるので、一時的なデータを保存するのに使います。

ちなみに/xfsというファイルシステムになっていました。

$ findmnt /
TARGET SOURCE    FSTYPE OPTIONS
/      /dev/sda1 xfs    rw,relatime,attr2,inode64,noquota

このように様々な種類のファイルシステム/をルートとしたツリー構造のどこかにマウントできます。そしてファイルシステムの違いを意識せずにファイルという概念でデータを扱えます。これはカーネルの仮想ファイルシステムという仕組みによるものです*4

関連コマンドなど

mountに引数がなければ、現在マウントされているものがリストされます。

$ mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
devtmpfs on /dev type devtmpfs (rw,nosuid,size=231496k,nr_inodes=57874,mode=755)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
...

devptsなどの仮想的なファイルシステム*5やBindマウントを除いたものはdfコマンドでも一覧が見れます。

$ df
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        227M     0  227M   0% /dev
tmpfs           239M     0  239M   0% /dev/shm
tmpfs           239M  6.5M  233M   3% /run
tmpfs           239M     0  239M   0% /sys/fs/cgroup
/dev/sda1        30G   24G  6.3G  80% /
tmpfs            48M     0   48M   0% /run/user/1000
/dev/sda2       976M  2.6M  907M   1% /mnt
tmpfs           239M     0  239M   0% /mnt/tmp

起動時に自動でマウントするには/etc/fstabに書き込みます。

$ cat /etc/fstab

#
# /etc/fstab
# Created by anaconda on Fri Oct 25 18:37:15 2019
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
UUID=d5f5b677-6350-416d-b1d3-47d723d94d88 /                       xfs     defaults        0 0
/swapfile none swap defaults 0 0

ストレージデバイスのUUIDなどはblkidで調べられます。

$ sudo blkid
/dev/sda1: UUID="d5f5b677-6350-416d-b1d3-47d723d94d88" TYPE="xfs" PARTUUID="615169d8-01"
/dev/sda2: UUID="fd1c3d2f-633c-45b6-a0b1-32b156544e8d" TYPE="ext4" PARTUUID="615169d8-02"

Bindマウント

mount --bind olddir newdirというような書式でディレクトリもマウントできます。

$ tree .
.
├── A
│   └── a
└── B
    └── b

2 directories, 2 files
$ sudo mount --bind A B
$ tree .
.
├── A
│   └── a
└── B
    └── a

2 directories, 2 files

バイスをマウントするのとは違い、既にマウントされているファイルシステムのツリー構造上のどこか一部(例ではAディレクトリ)を既にマウントされている別の部分(例ではBディレクトリ)に再度マウントしています。結果的にBの中身がAになっています。

rbind

--bindでは以下のように再帰的にマウントがされません。

$ tree .
.
├── A
│   └── a
├── B
│   └── b
└── C
    ├── c
    └── D
        └── d

4 directories, 4 files
$ sudo mount --bind A C/D
$ sudo mount --bind C B
$ tree .
.
├── A
│   └── a
├── B
│   ├── c
│   └── D
│       └── d
└── C
    ├── c
    └── D
        └── a

5 directories, 5 files

AC/Dにマウントして、CBにマウントしたのですが、B/Dの内容が元のDのままになっています。

--rbind再帰的にマウントされるようになります。

$ sudo umount B
$ sudo mount --rbind C B
$ tree .
.
├── A
│   └── a
├── B
│   ├── c
│   └── D
│       └── a
└── C
    ├── c
    └── D
        └── a

5 directories, 5 files

umountコマンドでマウントの取り消し(アンマウント)をしています。BCディレクトリをマウントすると、C/Dのマウントもされています。

Mount propagation

Bindマウントによってファイルシステムの一部が複数のディレクトリ間で共有された状態になったといえます。共有されたディレクトリの中でさらにマウントをするとどうなるでしょうか?その時の挙動について細かい設定ができます。

先程のままA/Fディレクトリを作ります。

$ mkdir A/F
$ tree .
.
├── A
│   ├── a
│   └── F
├── B
│   ├── c
│   └── D
│       ├── a
│       └── F
└── C
    ├── c
    └── D
        ├── a
        └── F

8 directories, 5 files

さらにGG/gを作ります。

$ mkdir G
$ touch G/g
$ tree .
.
├── A
│   ├── a
│   └── F
├── B
│   ├── c
│   └── D
│       ├── a
│       └── F
├── C
│   ├── c
│   └── D
│       ├── a
│       └── F
└── G
    └── g

9 directories, 6 files

そして、A/Fに今作ったGをマウントします。

$ sudo mount --bind G A/F
$ tree .
.
├── A
│   ├── a
│   └── F
│       └── g
├── B
│   ├── c
│   └── D
│       ├── a
│       └── F
│           └── g
├── C
│   ├── c
│   └── D
│       ├── a
│       └── F
│           └── g
└── G
    └── g

9 directories, 9 files

Aディレクトリの中身はC/DディレクトリやB/Dディレクトリに共有されている状態でした。上記のようにGディレクトリのマウントも共有されました。これは/ディレクトリがsharedというマウントの設定になっていたからです。

$ findmnt -o TARGET,PROPAGATION /
TARGET PROPAGATION
/      shared

この影響をなくすにはslaveという設定をします。

$ sudo umount A/F
$ sudo umount A
$ sudo mount --bind A A
$ sudo mount --make-slave A
$ findmnt -o TARGET,PROPAGATION A
TARGET                     PROPAGATION
/home/vagrant/mount-test/A private,slave

一度sudo mount --bind A Aによりマウントポイントを作る必要があります。sudo mount --make-slave Aによりslaveという属性が設定されました。これでAディレクトリ以下のマウントの影響が共有されないようになります。

$ sudo mount --bind G A/F
$ tree .
.
├── A
│   ├── a
│   └── F
│       └── g
├── B
│   ├── c
│   └── D
│       ├── a
│       └── F
├── C
│   ├── c
│   └── D
│       ├── a
│       └── F
└── G
    └── g

9 directories, 7 files

今回はGディレクトリのマウントがC/DディレクトリやB/Dディレクトリに反映されていません。

Aにマウントポイントを作らずにB/Dslaveの設定をし、GB/D/Fにマウントすると次のようになります。

$ sudo mount --make-slave B/D
$ findmnt -o TARGET,PROPAGATION B/D
TARGET                       PROPAGATION
/home/vagrant/mount-test/B/D private,slave
$ sudo mount --bind G B/D/F
$ tree .
.
├── A
│   ├── a
│   └── F
├── B
│   ├── c
│   └── D
│       ├── a
│       └── F
│           └── g
├── C
│   ├── c
│   └── D
│       ├── a
│       └── F
└── G
    └── g

9 directories, 7 files

上の例で、privateという設定もされていますが、これは--bindした時のデフォルトの設定のようです。sharedなマウントポイントのサブディレクトリでのマウントはslaveにしていても反映されてしまいます。反映させたくない時は上の例ならばsudo mount --make-private B/Dによりprivateにします。private,slaveではだめなようで、先程のコマンドでprivateだけにする必要があるようです。

結果privateにすればそのマウントポイント以下でのマウントも他でのマウントも反映されません。

だんだん混乱してきますね。表にすると次のようになります。Bindマウントであるディレクトリを2つのマウントポイントで共有しているとします。マウントポイントのうち1つを自分のディレクトリ、もう1つを相手のディレクトリとします。自分のディレクトリでのマウントが相手に影響する場合はセルの左側を○に、相手マウントが自分のディレクトリ影響する時はセルの右側を○に、そうでない時は✗にしています。*6

f:id:gkuga:20200316195429p:plain

おわりに

mountコマンドでは他にマウントポイントの移動やBindマウントの禁止などができます。詳しくはmount --helpman mountを参照ください。

*1:Vagrantプラグインvagrant-disksizeを使いました。

*2:調べていないです。

*3:NFSなどでなければ検知してくれるのでtypeは省略可能です。

*4:カーネルソースコードレベルの説明を誰かブログとかで書いてほしいです。探せばありますかね。

*5:仮想ファイルシステムとどう呼び分ければいいのでしょうか。使い方が間違っている...??

*6:わかりやすく説明できている気がしません...