mount / umount —— 挂载文件系统
一句话定义
mount 把一个已格式化的设备(或目录、或网络存储)"接入"到目录树的某个挂载点。Linux 不像 Windows 用盘符——所有东西都通过 mount 出现在统一的文件树里。
典型场景
- 装机时把新盘挂到
/data - K8s PV:底层 PV controller 帮你 mount 到节点上的
/var/lib/kubelet/... - 网络存储:NFS / iSCSI mount
- 排查
df/lsblk显示不一致:mount | grep ... - bind mount:把目录"映射"成另一个路径
看当前挂载
mount # 列出所有挂载(输出有点啰嗦)
mount | column -t # 对齐显示
mount | grep ext4 # 过滤
findmnt # 树形结构(**更易读**)
findmnt /var # 看某挂载点详情
cat /proc/mounts # 内核视角的真实状态
findmnt 是看挂载的现代首选:
$ findmnt
TARGET SOURCE FSTYPE OPTIONS
/ /dev/sda3 ext4 rw,relatime
├─/sys sysfs sysfs rw,nosuid,nodev,noexec
├─/proc proc proc rw,nosuid,nodev,noexec
├─/dev devtmpfs devtmpfs rw,nosuid
├─/boot /dev/sda2 ext4 rw,relatime
├─/var/lib/docker overlay overlay rw,...
└─/data /dev/sdb1 ext4 rw,relatime
/proc/mounts 是内核的真相(mount 命令的输出可能滞后)。
手动挂载
mount /dev/sdb1 /data # 普通挂载
mount -t ext4 /dev/sdb1 /data # 指定文件系统类型(多数情况自动检测)
mount -o ro /dev/sdb1 /mnt/readonly # 只读
mount -o noatime,nodiratime /dev/sdb1 /data # 加挂载选项
常用 -o 选项
| 选项 | 含义 |
|---|---|
ro / rw | 只读 / 读写 |
noatime | 不更新文件 atime(生产推荐:减少写放大) |
nodiratime | 不更新目录 atime(noatime 通常已包含) |
nodev | 不识别设备文件(安全) |
nosuid | 忽略 setuid(安全) |
noexec | 不允许执行二进制(安全) |
discard | TRIM 自动通知 SSD(SSD 推荐) |
defaults | 等价 rw,suid,dev,exec,auto,nouser,async |
remount | 重新挂载(改选项不卸载) |
nofail | 挂载失败也不让系统启动失败(fstab 必加) |
remount —— 不卸载就改选项
mount -o remount,ro / # 把 / 变只读
mount -o remount,rw / # 再改回来
只读模式是应急 fsck / 紧急调试的常见做法。
bind mount —— 把目录映射成另一个路径
mount --bind /data/foo /mnt/foo
# 之后 /mnt/foo 和 /data/foo 是同一个数据
# 修改一个,另一个也变
不是软链接 —— bind mount 在内核层做映射,对程序透明。
K8s 里 hostPath、emptyDir 都靠 bind mount 实现:
# kubelet 把 hostPath /etc/myapp bind 到容器内的 /config
mount --bind /etc/myapp /var/lib/kubelet/pods/.../volumes/.../config
bind mount 只读:
mount --bind /etc/cfg /mnt/cfg
mount -o remount,ro,bind /mnt/cfg
注意:
mount --bind不能直接mount -o ro,bind ...—— 必须 bind 之后再 remount ro。这是 mount 历史遗留坑。
看 / 排查"挂载到哪里了"
findmnt /dev/sdb1 # 这个盘挂在哪?
findmnt -t ext4 # 所有 ext4
findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS # 自定义列
findmnt -R /var # /var 下所有 mount 树
# 反查:某路径属于哪个 mount
df /path/to/file
# 或
findmnt --target /path/to/file
网络存储挂载
NFS
mount -t nfs server.example.com:/data /mnt/nfs
mount -t nfs -o vers=4,rsize=1048576,wsize=1048576 server:/data /mnt/nfs
iSCSI(先 login,后 mount)
iscsiadm -m discovery -t st -p 192.168.1.100
iscsiadm -m node -T iqn.2024.... -p 192.168.1.100 --login
# 之后 /dev/sdc 等设备出现
mount /dev/sdc1 /mnt/iscsi
Ceph RBD(K8s 常见)
rbd map mypool/myimage
mount /dev/rbd0 /mnt/ceph
K8s StorageClass 把这些操作自动化了,你通常不需要手动 mount。
umount —— 卸载
umount /data # 按挂载点
umount /dev/sdb1 # 按设备
umount -l /data # lazy(**umount 卡住时用**)
umount -f /data # 强制(NFS 卡死时)
target is busy 怎么处理
$ umount /data
umount: /data: target is busy.
# 第 1 步:找谁占着
lsof +D /data
fuser -vm /data
# 第 2 步:杀掉 / 让它退出
kill <PID>
# 第 3 步:再 umount
umount /data
或者懒卸载(lazy):
umount -l /data
# 立刻断开 mount 入口,新进程访问不到
# 但旧持有 fd 的进程可以继续工作,最后一个 close 时真正释放
-l 是常用救命招——不破坏现有进程。
-f 强制(仅适合 NFS)
umount -f /mnt/nfs
NFS server 挂了,client 卡在 D 状态、umount 卡住。-f 让 NFS client 放弃。
挂载流程总结
新盘上线全流程:
# 1. 看设备
lsblk
# /dev/sdb 是新盘
# 2. 分区(如果需要)
parted /dev/sdb mklabel gpt
parted -a optimal /dev/sdb mkpart primary ext4 0% 100%
# 3. 格式化
mkfs.ext4 -L data /dev/sdb1
# 4. 挂载
mkdir -p /data
mount /dev/sdb1 /data
# 5. 验证
df -h /data
findmnt /data
# 6. 持久化(写 fstab —— 见 fstab.md)
echo "UUID=$(blkid -s UUID -o value /dev/sdb1) /data ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
# 7. 测一遍 fstab 没写错
umount /data
mount -a
df -h /data # 还要在
K8s 视角
K8s 节点上 findmnt 输出有上百行,因为每个 pod 至少 1-2 个 bind mount:
findmnt | grep kubelet | head
每个 ConfigMap / Secret / hostPath 都是一次 bind mount。Pod 终止时 kubelet 负责 umount——但偶尔会卡(容器 runtime 错乱、CSI driver bug)。
排查"pod 卡 Terminating"
kubectl get pod my-pod
# Terminating 很久
# 节点上
ssh m4
findmnt | grep <pod-uid>
# 如果还有挂载,kubelet 在等 umount
# 看进程
lsof +D /var/lib/kubelet/pods/<pod-uid>
# 强制 unmount 残留(小心)
findmnt | grep <pod-uid> | awk '{print $1}' | xargs -r umount -l
挂载选项的性能影响
K8s 节点上 etcd / 大数据 / 数据库的挂载选项不一样:
| 场景 | 推荐选项 |
|---|---|
| 通用 | defaults,noatime |
| etcd 数据盘 | defaults,noatime,nodiratime,discard |
| 数据库(PG/MySQL) | defaults,noatime,nobarrier(SSD/电池 controller) |
| 容器 overlay | defaults(containerd / docker 自己管) |
| 临时数据 | defaults,noatime,nodiratime |
noatime 的意义
默认每次 read 文件 Linux 也会更新 atime(访问时间) → 每个 read 触发一次 write。noatime 关掉这个,大幅减少元数据写入。
生产挂载几乎一定加
noatime。
discard vs fstrim
SSD 需要 TRIM 通知"这些块不再用"。两种方式:
discard—— 实时(每次删文件都通知)。有时影响性能fstrim—— 定期批量(cron / systemd timer 跑fstrim /data)
推荐用 fstrim(定期)而不是实时 discard。Ubuntu 22+ 自带 fstrim.timer。
常见踩坑
坑 1:挂载到已有内容的目录
ls /data
# file1 file2 file3 ← 原有内容
mount /dev/sdb1 /data
ls /data
# (空 / 或 sdb1 的内容) ← 原有内容被"挡住"
挂载会把原有内容遮蔽(不是删,umount 之后还回来)。但程序在挂载时正在写 /data 的话 → 数据写到了已被遮蔽的旧空间、肉眼看不到。
养成挂载前 ls + 备份目录的习惯。
坑 2:mount 看到挂了、df 看不到
mount | grep sdb1 # 看到
df -h | grep sdb1 # 没看到
通常是挂载点路径不一致 / 类型不识别。findmnt / cat /proc/mounts 是更可靠的真相来源。
坑 3:fstab 写错导致系统启不来
下面这段写错 → 重启 → 卡 emergency mode:
/dev/wrong-device /data ext4 defaults 0 2
修:必须加 nofail:
/dev/sdb1 /data ext4 defaults,nofail 0 2
详见 fstab.md。
坑 4:用 /dev/sdX 而不是 UUID
/dev/sdb1 /data ext4 defaults 0 2
加新盘 / 重启之后 sdb 可能变成 sdc —— 挂载失败。
必须用 UUID:
blkid /dev/sdb1
# /dev/sdb1: UUID="abc-def-123..." TYPE="ext4"
# fstab:
UUID=abc-def-123... /data ext4 defaults,noatime 0 2
详见 fstab.md。
坑 5:bind mount 套娃
mount --bind /a /b
mount --bind /b /c
# /a /b /c 都是同一份数据
umount /b
# /c 还是访问 /a 的内容(bind 不传递)
这通常是好事——不互相依赖。但 K8s mount 卡死时 lazy umount 顺序错误可能让 /c 一直占用,得手动一层层处理。
坑 6:umount 之后磁盘还能写
umount /data
echo "test" > /data/file
ls /data
# 看到 file,但盘上没有!
/data 是个普通目录,umount 之后写入到了根分区的 /data 目录(被 mount 遮蔽的那个)。
K8s 里 pod 终止时如果 mount 残留,应用可能误写"假"位置。
防御:umount 后 ls -la /data 看是否预期。
坑 7:挂载点不存在
mount /dev/sdb1 /data
# mount: /data: mount point does not exist.
mkdir -p /data 先。
坑 8:FAT / NTFS 文件名编码问题
mount -t vfat /dev/sdc1 /mnt/usb
# 中文文件名乱码
加编码:
mount -t vfat -o iocharset=utf8,codepage=936 /dev/sdc1 /mnt/usb
服务器场景一般不挂 FAT,K8s 节点用不到。
坑 9:mount 卡住(NFS / iSCSI 卡死)
mount -t nfs server:/data /mnt
# 卡 30 秒以上
NFS server 不响应。
mount -t nfs -o timeo=5,retrans=2 server:/data /mnt # 短超时
K8s 用 NFS PV 时,节点 down 会让所有挂这个 NFS 的 pod 卡在 Terminating。生产慎用 NFS。