如何避免销毁 zfs 镜像后出现 Unable to automount 错误提示

简要说明

在正常创建 zfs 镜像后, 如果需要访问镜像的文件, 可以直接访问当前 zfs 系统下的 .zfs 隐藏文件, 在访问镜像目录内容的时候会触发 zfs 的 automount 自动挂载, 这点有点像 Linux 内核提供的 automount 属性, 即默认不挂载镜像设备文件, 仅在访问的时候自动挂载, 如下所示:

# zfs snapshot datapool/db@db_20191025
# ls /datapool/db/.zfs/snapshot/db_2020007
db_data

# 访问镜像目录后则触发自动挂载
# df -hlT
...
datapool/db@db_20191025 zfs       346G   44G  302G  13% /datapool/db/.zfs/snapshot/db_20191025

Mar  7 16:37:53 cz snoopy[91304]: [time_ms:614 login:root uid:0 pid:91304 filename:/usr/bin/ls username:root]: ls /export/mysql/.zfs/snapshot/mysql_20191025/
Mar  7 16:37:53 cz snoopy[91307]: [time_ms:636 login:(unknown) uid:0 pid:91307 filename:/sbin/mount.zfs username:root]: /sbin/mount.zfs datapool/db@db_20191025 /datapool/db/.zfs/snapshot/db_20203007 -n -o rw

snoopy 的日志来看, 可以看到 ls xxx 访问操作触发了 mount 操作:

/sbin/mount.zfs datapool/db@db_20191025 /datapool/db/.zfs/snapshot/db_20191025 -n -o rw

zfsonlinux 0.7.11 ~ 0.7.13 版本目前默认挂载 300s, 如果没有操作则自动卸载. 其它版本未做测试. freebsd 系统的 zfs 则没有自动挂载的行为.

如何销毁 zfs 镜像

从这方面来看, 如果需要删除不用的镜像, 最好先将挂载的镜像卸载掉, 这点 zfs destroy 会自动进行. 不过在实际执行后出现了 issue-4068 的错误. 如下所示, 看起来 zfs 并未清理干净镜像目录的内容, 导致只要访问目录的时候就触发 automount 特性进行挂载, 但是又挂载不上所以出现 kernel 的警告信息:

运行环境

os:      Centos7 
kernel: 3.10.0-862.14.4.el7.x86_64

zfs version:
  zfs-0.7.13-1.el7_6.x86_64
  zfs-dkms-0.7.13-1.el7_6.noarch
  libzfs2-0.7.13-1.el7_6.x86_64
  zfs-release-1-6.el7.noarch

执行后出现 kernel 错误:

# zfs destroy datapool/db@db_20191025

# less /var/log/messages
Mar 09 09:23:57 cz snoopy[26557]: [uid:0 sid:0 tty:ERROR(ttyname_r->EBADF) cwd:/ filename:/sbin/mount.zfs]: /sbin/mount.zfs datapool/db@db_20191025 /data/
Mar 09 09:23:57 cz kernel: WARNING: Unable to automount /data/db/.zfs/snapshot/db_20191025/datapool/db@db_20191025: 256
Mar 09 09:23:58 cz snoopy[26561]: [uid:0 sid:0 tty:ERROR(ttyname_r->EBADF) cwd:/ filename:/sbin/mount.zfs]: /sbin/mount.zfs datapool/db@db_20191025 /data/
Mar 09 09:23:58 cz kernel: WARNING: Unable to automount /data/db/.zfs/snapshot/db_20191025/datapool/db@db_20191025: 256

# ls -al /data/db/.zfs/snapshot/              # 看不到镜像目录

# ls -al /data/db/.zfs/snapshot/db_20191025   # 却可以访问
ls: cannot access /data/db/.zfs/snapshot/db_20191025/.: Object is remote
ls: cannot access /data/db/.zfs/snapshot/db_20191025/..: Object is remote
total 0
d????????? ? ? ? ?            ? .
d????????? ? ? ? ?            ? ..

从上述的操作来看, 已经看不到镜像目录, 不过可以指定目录进行访问, 和 issue-4068 描述的一致. 再从 snoopy 的日志来看, 系统里有程序访问镜像目录, 进而导致 mount.zfs 操作. 不过系统日志中每隔一段时间便出现几次 kernel 消息, 我们在排除一些人为操作的情况下, 通过 sysdig 跟踪发现 zabbix_agent 监控访问了该目录进而引起 kernel 提示, 如下所示

# sysdig | grep -A 4 db_20191025
......
124281351 16:49:42.398237936 5 zabbix_agentd (30319) < stat res=0 path=/data/db/.zfs/snapshot/db_20191025 
124281352 16:49:42.398245095 0 <NA> (0) > switch next=20055(grep) pgft_maj=0 pgft_min=0 vm_size=0 vm_rss=0 vm_swap=0 
124281353 16:49:42.398246031 0 grep (20055) < read res=118 data=123900364 16:49:40.426280573 2 <NA> (0) > switch next=20055(grep) pgft_maj=0 pgf 
124281354 16:49:42.398246928 0 grep (20055) > read fd=0(<p>pipe:[416411014]) size=4096 
124281355 16:49:42.398247662 0 grep (20055) > switch next=0 pgft_maj=0 pgft_min=760 vm_size=116880 vm_rss=1212 vm_swap=0 
--
124543338 16:49:44.403356709 10 zabbix_agentd (2342) < read res=60 data=s.size[/data/db/.zfs/snapshot/db_20191025,used]. 
124543339 16:49:44.403358235 10 zabbix_agentd (2342) > alarm 
124543340 16:49:44.403358989 10 zabbix_agentd (2342) < alarm 
124543341 16:49:44.403381908 10 zabbix_agentd (2342) > pipe 
124543342 16:49:44.403388140 10 zabbix_agentd (2342) < pipe res=0 fd1=7(<p>) fd2=8(<p>) ino=416414414 

备注: 该问题本身不影响系统及备份的数据, 只是额外的出现 kernel 提示.

如何处理

从官方的 issue-4068, issue-4672, issue-4772, issue-8166 来看, 并未完全解决此问题, 只要访问已销毁的镜像目录就会出现自动挂载失败的情况. 从已测的信息来看, 以下版本均有上述的问题:

0.6.x
0.7.x

如果需要避免出现上述的 kernel 消息, 可以通过以下两种方式避免, 可以选择一种方式或者两种都执行. 这两种方式的机制类似, 均为避免访问镜像目录:

手动清理镜像目录

可参考 issue-4068 中的提示, 删除目录后, 所有的访问返回不存在错误, 避免了挂载的问题:

rmdir /data/db/.zfs/snapshot/db_20191025

避免人工或者监控访问镜像目录

如果没有手动清理镜像目录, 我们应该尽量避免访问目录, 监控程序也最好避免访问镜像的目录, 比如可以将 zabbix 磁盘监控的自动发现属性 Keep lost resources period (in days) 设置为 0, 避免继续监控过期不用的挂载点.