1. 先把话说清楚:为什么有人非要把 ext4 换成 xfs
1.1 一个很常见的场景:老机器上的 ext4 分区
这几年我经手过不少 CentOS 机器,有的是从 CentOS 6 时代一路升级上来的,有的是早期安装时手选文件系统,把 /data 或者某个挂载点格式化成了 ext4。CentOS 7 从 7.0 开始虽然安装器默认就用 xfs,但"默认"这两个字挡不住历史遗留——装系统时嫌麻烦直接点"我要自行分区",然后顺手选了 ext4 的人并不少。等到业务跑起来,问题就慢慢冒出来了:目录里小文件堆到几百万个,ls卡半天;日志写入量大,元数据操作频繁,iostat 里 %util 一直飘红;再往后想做在线扩容、想跑个容器,才发现这个分区有点跟不上趟了。
这时候很多人会想到把 ext4 换成 xfs。做这件事之前必须先明白一件事:ext4 转 xfs 不能原地转换。没有任何一个命令能像convert2xfs那样一键完成,也不存在"原地改个超级块标记"的骚操作。你能做的,只有一条路——备份数据、卸载分区、用mkfs.xfs重建文件系统、再把数据搬回来。这就是整件事的本质,后面所有的步骤都是围绕它展开的。
我写这篇东西,是想把这套流程拆得足够细,细到你把命令复制过去改个路径就能用。不管你是刚接手一台服务器的新手,还是已经干了几年的运维,只要碰到"CentOS 上把 ext4 改成 xfs"这件事,照着往下做基本不会翻车。
1.2 ext4 和 xfs 到底差在哪,值不值得折腾
先做个对比,把两者的差异摆到桌面上。很多文章只讲"xfs 更先进",这话太虚,得落到具体指标上才有意义。
| 对比维度 | ext4 | xfs |
|---|---|---|
| inode 分配方式 | 格式化时固定数量,用光只能重建 | 动态分配,按需生成 |
| 单文件系统容量上限 | 理论 1 EiB(实测常见到 50TB 量级) | 理论 8 EiB,生产上几百 TB 很常见 |
| 单文件大小上限 | 16 TiB | 8 EiB |
| 能否缩小 | 可以(离线 resize2fs) | 做不到,只能扩不能缩 |
| 并行写入能力 | 单块设备日志,并发一般 | 多分配组(AG)并行,多线程有明显优势 |
| 元数据性能 | 一般,小文件多时明显劣化 | 强,日志写入更高效 |
| 在线扩容 | 支持(resize2fs 在线) | 支持(xfs_growfs) |
| 在线碎片整理 | 支持(e4defrag) | 支持(xfs_fsr) |
| 默认启用场景 | CentOS 6 及更早 | CentOS 7 之后的默认选择 |
看这张表,重点其实就三条。第一,inode 动态分配是 xfs 最实在的优势。ext4 格式化时你要预估 inode 数量,估少了以后磁盘还有空间但写不进文件,只能重建;估多了又浪费 inode 表占的空间。xfs 是按需分配,小文件再多也不会出现"inode 用尽"这种尴尬。第二,多 AG 并行让 xfs 在多核、高并发写入场景下表现更好,尤其是日志型、数据库型负载。第三,xfs 不能缩小,这是它最大的"缺点",也是做容量规划时必须提前想清楚的事。
所以值不值得折腾,判断标准很清楚:如果你的分区小文件数量大、写入并发高、单分区容量需求超过几 TB、未来还要挂容器或者跑数据库,那换成 xfs 是划算的。反过来,如果只是一个 100GB 的静态文件归档盘,读写都很温和,那真没必要为它停机折腾一次。
1.3 明确一个心理预期:这不是"升级",是"重建"
我见过有人把这件事理解成"打补丁",以为风险不大。这里必须纠正:重建文件系统意味着这个分区上的所有数据在一段时间内都不在原位。整个过程里,你手里要有两份完整数据:一份在原始位置(或者备份介质上),一份在新文件系统上。任何一步侥幸省掉备份,都是拿业务开玩笑。
还有一个容易被忽视的点:文件系统换了,分区的 UUID 会变。如果你的/etc/fstab里是按 UUID 挂载的(CentOS 7 默认就是这么干的),格式化之后 UUID 变了,重启直接进不去系统,掉进 emergency shell。这个坑我在早期踩过一次,凌晨三点对着黑屏敲救援命令的滋味不好受。后面我会专门讲怎么规避。
2. 动手之前:把家底盘清楚,把退路留好
2.1 先确认现状,别凭记忆操作
老运维有个通病,觉得自己记得住磁盘布局。但服务器挂载点一多,或者经手过好几个人的机器,记忆基本靠不住。第一条命令永远是先看实际情况:
df -Th lsblk -f blkid cat /etc/fstabdf -Th里的-T是显示文件系统类型,一眼就能看出哪个挂载点是 ext4。lsblk -f会把块设备树、文件系统类型、UUID、挂载点全部列出来,比 df 更全,尤其是没挂载的设备也能看到。blkid专门输出 UUID 和 TYPE,写 fstab 时直接从这抄。最后再看一眼/etc/fstab,确认系统是按 UUID 挂载还是按设备名挂载——这决定了你后面改配置的写法。
这里有个细节:df -T显示的可能是ext4也可能是ext3或者ext2,都算 ext 家族,处理方式一样。另外注意区分挂载点和设备:你要操作的是设备(比如/dev/sdb1或/dev/mapper/vg0-data),格式化的是设备,卸载的却是挂载点。
提示:执行任何格式化命令之前,把
lsblk -f的输出截图或者复制到记事本里存着。真出问题的时候,这张图就是你的回滚地图。
2.2 数据备份:做两份,一份不够
数据备份这块,我坚持一个原则:至少两份,且至少一份不在本机。具体怎么做,看你的分区大小和允许的停机时间。
分区小于 500GB、停机窗口宽裕的情况下,直接本地双备份就够了。第一份用tar打包到另一个分区,保留权限和属性:
tar -cvpzf /backup/data-$(date +%F).tar.gz -C / data-p是保留权限,-C /是切到根目录再打包,这样解包时不会带一堆绝对路径。缺点是 tar 不保留硬链接和 ACL,如果你有这两类需求,换rsync。
第二份用rsync同步到异地,或者另一个物理磁盘:
rsync -aHAX --numeric-ids --info=progress2 /data/ /mnt/backup/data/这里的参数值得展开说:-a是归档模式,等于-rlptgoD;-H保留硬链接;-A保留 ACL;-X保留 SELinux 上下文;--numeric-ids按数字 UID/GID 同步,避免跨机器时用户映射错乱;--info=progress2给出总体进度而不是刷屏的单文件进度。
如果分区很大(几个 TB),停机时间又只有一两个小时,那就用"两次 rsync"策略:服务还在线的时候先跑一遍全量 rsync(这时候数据在变,没关系),等真到停机窗口,业务停了,再跑第二遍增量 rsync。第二遍只传变化的部分,通常几分钟到几十分钟就完了,能把停机时间压到最短。这个技巧在数据量大、业务停不起的场景里几乎是标配。
还有一种情况:你有多余的磁盘或者 LVM 里有空闲空间。那恭喜,你可以用"新建文件系统直接迁移"的方式,原始分区从头到尾不动,等新分区验证无误后再删旧的。这是最安全的方案,后面第 4 章会详细讲。
2.3 退路预案:失败了我怎么回去
做这类操作,我习惯在动手前列一个回滚清单,写清楚每一步失败后怎么退。
- 如果备份阶段失败 → 什么都不做,检查备份介质空间和权限,重来。
- 如果卸载分区失败,提示设备忙 → 查占用进程,不能强行卸载,见第 5 章排查。
- 如果格式化成功但数据回迁失败 → 原始分区(或备份)还在,数据没丢,重来即可。
- 如果改完 fstab 重启进不去系统 → 从救援模式改回 fstab 或者直接改回原 UUID。
- 如果新文件系统跑起来性能反而更差 → 这基本不可能,但要真有,数据还能回迁回 ext4 分区(前提是你还没把原分区格式化掉)。
这里面最关键的一条:在原分区数据没删干净之前,绝不要格式化原分区。如果你的方案是"备份到别的地方,原分区格式化",那备份介质必须完整可用且经过校验。校验怎么做?我一般在备份完成后做一次"反向 rsync 空跑",用rsync -avn --delete对比源和目标,只输出差异文件,没有输出才说明两边一致:
rsync -avn --delete /data/ /mnt/backup/data/-n是 dry-run,不会真的删改任何东西,只告诉你"如果真跑会发生什么"。输出为空,就说明备份是完整的。
3. 实操全流程:从卸载到数据回迁
3.1 停掉占用进程并安全卸载
假设我们要处理的是/dev/sdb1,挂在/data,当前是 ext4。第一步是卸载。很多人直接敲umount /data,然后收到一句target is busy,就卡住了。原因通常是还有进程在工作目录里、有文件被打开、或者有 NFS 导出。
先找出是谁在占用:
lsof +f -- /data | head -20 fuser -mv /datalsof +f -- /data会列出所有正在使用这个挂载点上文件的进程,fuser -mv更直观,直接把进程名、PID、访问类型都打出来。找到之后,正常做法是优雅停掉这些服务(systemctl stop xxx),而不是直接 kill。
真到了必须强杀的地步,可以用:
fuser -km /data但我要强调,-k会直接给进程发信号,-m指的是挂载点。这里面如果有数据库进程还没把数据刷到盘上,强杀可能造成数据不一致。所以我一般只在确认是 shell 会话卡在目录里、或者某个无关紧要的进程时才这么干。
另外几个常见的"卡住"来源:
- 你自己或者别人的 SSH 会话
cd到了/data下面。退出去就行。 - 有挂载点嵌套,比如
/data下面还挂着别的分区。先卸载内层的。 - NFS 服务正在导出这个目录。先
exportfs -u取消导出。 - 有 loop 设备或者 bind mount 关联到它。
卸载成功后,用mount | grep data确认它真的不在了。这一步别省。
3.2 mkfs.xfs 参数怎么选,我把计算过程写出来
格式化命令本身很简单,但参数选错会让性能差一大截,甚至埋下隐患。先给一个通用模板:
mkfs.xfs -f -i size=512 -n ftype=1 -l size=512m -L data_vol /dev/sdb1逐条解释。
-f是 force,强制覆盖已有的文件系统签名。不加它,如果设备上检测到 ext4 的超级块,mkfs 会拒绝执行。这个参数是必需的,但也意味着敲下去就回不去了,动手前再确认一遍设备名。
-i size=512把 inode 大小设成 512 字节,默认是 256。为什么调大?因为 inode 里要存扩展属性(xattr),跑容器、跑 SELinux 的机器上 xattr 用得很凶,256 字节的 inode 经常塞不下,只能写到额外的数据块里,一来一回性能就掉了。现在磁盘便宜,多花这点空间换性能是值的。如果是纯静态文件存储,默认 256 也够用。
-n ftype=1这个参数是重中之重。它让目录项里记录文件类型信息,是 overlayfs / Docker overlay2 驱动的硬性要求。CentOS 7 手动执行mkfs.xfs时默认生成的是 ftype=0 的文件系统,装了 Docker 之后守护进程会报错,提示 backing filesystem 不支持 d_type。这个坑非常隐蔽,很多人格式化完一切正常,直到装容器才炸。所以只要这台机器未来可能跑容器,这个参数一定要加。
-l size=512m指定日志区大小,默认值在小分区上通常是 10MB 到 64MB。日志区用来记录元数据变更,写日志是串行的,日志太小会成为瓶颈。我一般按分区容量来给:小于 100GB 给 128MB,100GB 到 1TB 给 512MB,1TB 以上给 1GB 到 2GB。给太大也没意义,日志区的写入是顺序的,512MB 到 1GB 对绝大多数场景够用。
-L data_vol给文件系统打个标签,后面挂载时可以用LABEL=data_vol代替 UUID。标签的好处是可读性好,坏处是如果机器上有多块盘的标签重名,会挂错。所以我一般只在单机明确场景下用标签,多盘环境还是老老实实用 UUID。
如果是 RAID 阵列,还要考虑条带对齐。这个参数计算稍微绕一点,我把它写清楚。假设底层是 RAID 5,5 块盘,条带大小(chunk size)64KB。那么:
su(stripe unit)就是条带大小,但 mkfs.xfs 的-d su单位是 512 字节扇区,所以su = 64KB / 512 = 128。sw(stripe width)是数据盘数量。RAID 5 有 1 块盘的容量用于校验,所以sw = 5 - 1 = 4。RAID 6 是盘数 - 2,RAID 10 是盘数 / 2。
完整命令:
mkfs.xfs -f -i size=512 -n ftype=1 -l size=512m -d su=128,sw=4 -L data_vol /dev/sdb1这里要特别注意单位差异,这是新手最容易搞错的地方:mkfs 阶段su的单位是 512 字节块,挂载阶段su的单位是字节。也就是说 mkfs 里写su=128,对应挂载时写su=64k。搞反了不会报错,但对齐就错了,性能白优化。
顺带说一句,现在很多新机器用的是 SSD 或者带大缓存的 RAID 卡,这些条带参数的意义比机械盘时代小了很多。如果是单块 SSD,完全不用管 su/sw,直接默认即可。
3.3 挂载、改 fstab、验证,顺序不能乱
格式化完成后,先手动挂载测试,别急着改 fstab:
mkdir -p /mnt/newdata mount /dev/sdb1 /mnt/newdata df -Th | grep newdata确认挂载成功、类型是 xfs,再拿 UUID:
blkid /dev/sdb1输出类似UUID="a1b2c3d4-..." TYPE="xfs"。把这段 UUID 抄进/etc/fstab,原来是 ext4 那一行改成:
UUID=a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx /data xfs defaults,noatime,nodiratime 0 0几个细节。挂载参数里noatime关掉访问时间更新,能显著减少元数据写入,尤其是 Web 目录、日志目录这种读多写多的场景,收益很明显。nodiratime是只关目录的 atime,配合 noatime 一起用更干脆。日志位的0 0,第一个 0 是 dump 标志,基本没人用 dump 备份了,写 0;第二个 0 是 fsck 顺序,xfs 必须写 0。原因在于 xfs 不像 ext4 那样支持开机自动 fsck,如果写了非 0 的值,systemd 启动时会尝试调用 fsck 工具,可能触发 xfs_repair,然后卡在启动阶段等很久甚至失败。
改完 fstab,先别重启,用这条命令空跑一遍验证语法:
mount -amount -a会尝试挂载 fstab 里所有未挂载的条目。如果语法有错、UUID 不存在、设备有问题,这里立刻就会报错,而不是等到重启后才发现。没报错,再确认一下:
findmnt /data看到类型是 xfs、来源 UUID 正确、选项包含 noatime,心里就有底了。
提示:改 fstab 之前先备份一份
cp /etc/fstab /etc/fstab.bak。这个文件写错是导致服务器起不来的头号原因,备份成本几乎为零。
3.4 数据回迁:权限、时间戳、SELinux 一个都不能漏
数据回迁是整个流程里最耗时的部分,也是最容易出问题的地方。用 rsync 的话,参数要和备份阶段保持一致,保证属性完整:
rsync -aHAX --numeric-ids --info=progress2 /mnt/backup/data/ /data/注意源路径末尾的斜杠,/mnt/backup/data/带斜杠表示"同步这个目录里的内容",不带斜杠表示"同步这个目录本身"。这个区别能让你多一层 or 少一层目录,非常容易搞错。我的习惯是统一带斜杠,语义更清晰。
数据量大的话,加上--bwlimit限制带宽,避免把生产网络的带宽吃光:
rsync -aHAX --numeric-ids --bwlimit=100000 /mnt/backup/data/ /data/单位是 KB/s,所以 100000 约等于 100MB/s。
迁移完成后,做几件事收尾。
第一,比对文件数量和大小:
find /mnt/backup/data -type f | wc -l find /data -type f | wc -l du -sh /mnt/backup/data /data两边的文件数要一致,大小允许有细微差异(比如稀疏文件),但量级必须对得上。
第二,如果之前用了-X保留 SELinux 上下文,检查一下有没有异常。如果没用-X,那就得上restorecon,按系统的策略重新打标签:
restorecon -Rv /data-R递归,-v显示处理过程。这个命令在处理 Web 目录、容器挂载目录时尤其重要,少了它,SELinux 处于 enforcing 模式下服务会起不来。
第三,重新启动原来依赖这个目录的服务,逐个验证:
systemctl status nginx systemctl status mysqld别一次全启,一个个来,出问题好定位。
第四,一切正常后,把备份数据保留至少一周再清理。我知道有人急着腾空间,当天就删了备份,结果两天后发现某个隐藏文件没迁过来,追悔莫及。
4. 特殊场景:根分区、LVM 和云主机怎么处理
4.1 根分区不能在跑着的系统上重建
前面讲的都是独立数据分区,卸载了就行。但如果是根分区/是 ext4,想换成 xfs,情况完全不同——根分区不可能在系统运行的时候卸载自己。这时候有两条路。
第一条路是救援模式。用 CentOS 安装镜像启动,选择 Troubleshooting → Rescue a CentOS system,进入救援 shell。此时系统的根分区会被挂载到/mnt/sysimage(CentOS 7 的救援环境),你可以把里面的数据 rsync 到外置盘或者另一块盘上,然后对原根分区重塑。这条路操作繁琐,而且很难做完美回滚,我不太推荐用在实际生产机上。
第二条路是换盘。如果机器有多余的盘位,直接把新盘装上,格式化 xfs,把原根分区的数据同步过去,然后改引导、改 fstab。这条路的好处是原盘完整保留,切过去之后跑几天稳定了,再决定旧盘怎么处理。麻烦点在于引导部分要重做:grub2-install、grub2-mkconfig、重新生成 initramfs,一步都不能少。
我个人的判断是:生产环境的根分区,能不重建就不重建。真要换,理想做法是重新装一套系统,把数据迁过去,而不是在现有系统上动刀。装新系统的时候安装器默认就是 xfs,一步到位,风险低得多。
4.2 LVM 场景:这才是最舒服的玩法
如果你的分区在 LVM 上,比如/dev/vg0/data,那事情会变得非常舒服,因为 LVM 支持在线新建、删除逻辑卷。方案是这样:
先确认卷组里有空闲空间:
vgs看VFree那一列。如果有几个 GB 以上的空闲,就可以新建一个 LV,格式化 xfs,挂到临时目录,把旧数据 rsync 过去:
lvcreate -L 500G -n data_xfs vg0 mkfs.xfs -f -i size=512 -n ftype=1 -L data_vol /dev/vg0/data_xfs mkdir -p /mnt/newdata mount /dev/vg0/data_xfs /mnt/newdata rsync -aHAX --numeric-ids /data/ /mnt/newdata/数据同步完、验证无误后,把 fstab 里/data指向新 LV 的 UUID,umount /data,再把新 LV 挂到/data:
umount /data mount /dev/vg0/data_xfs /data重启验证一遍。一切正常后,再删掉旧的 LV 收回空间:
lvremove /dev/vg0/data这个方案的精髓在于停机时间极短:rsync 全量可以在业务在线时做,真正需要停的只有最后切挂载点那几分钟。而且旧 LV 保留着,随时能切回去。
如果卷组没有空闲空间怎么办?可以在同一卷组里先做一次lvextend给旧 LV 扩点空间?不行,旧 LV 上跑着 ext4,扩了也解决不了"要新建一个"的问题。这时候只能先删掉或者缩小一部分,或者加一块物理盘进卷组:
pvcreate /dev/sdc vgextend vg0 /dev/sdc新盘进卷组之后,就有空间建新 LV 了。
还有个细节:扩容 xfs 的时候,xfs_growfs的参数是挂载点,不是设备。这个反直觉的设计坑过很多人:
lvextend -L +100G /dev/vg0/data_xfs xfs_growfs /data # 正确,参数是挂载点 # xfs_growfs /dev/vg0/data_xfs # 错误,会报 not a mounted XFS filesystemXFS 的扩容是纯在线的,业务不用停,扩完立即生效。这也是 xfs 比 ext4 省心的地方之一。
4.3 云主机和虚拟机上的额外注意点
云主机上处理这件事,思路和物理机一样,但有几个平台特性要留意。
第一,云盘通常支持在线挂载和卸载,这给了你很大的便利。可以先把新云盘挂上去,格式化 xfs,数据迁完,再卸载旧盘。旧盘其实可以先不释放,观察一段时间再决定。这样连"数据备份到别处"这一步都省了,因为旧盘本身就是最好的备份。
第二,云主机的系统盘一般是不能卸下来挂到别的机器上的(部分平台支持,部分不支持)。所以前面说的"根分区换盘"方案在云上要打个折扣。稳妥做法还是新开一台实例,装好系统,把数据迁过去,再切换流量。
第三,虚拟机的话,操作空间更大。VMware 或者 KVM 上可以直接给虚拟机加一块虚拟磁盘,格式化、迁移、改配置,全程不影响宿主机上的其他内容。真出问题了,还能用快照回滚。所以在虚拟化环境里做这件事,我强烈建议先打个快照。快照成本很低,价值极高,恢复的时候一键搞定,比任何手动回滚都快。
第四,注意云平台的磁盘设备名。云主机上常见的是/dev/vdb、/dev/nvme1n1这类命名,和物理机的/dev/sdb不同。做操作前用lsblk确认清楚,别照搬别人的命令。
5. 踩过的坑都在这儿:问题排查速查
5.1 卸载报 target is busy 怎么破
这是最高频的问题,前面提过一部分,这里给一个完整的排查顺序。
先看是谁在用:
lsof +f -- /data fuser -mv /data如果有输出,看进程名和 PID。正常的服务进程用systemctl stop停掉;如果是 shell 会话,看看是不是自己或者同事的终端cd进去了,让人退出来就行。
如果lsof什么都没输出但就是卸不掉,检查这几个方向:是不是有进程的当前工作目录在这个挂载点里(这种情况 lsof 可能看不到),是不是有 bind mount 指向它,是不是 NFS 还在导出,是不是有 swap 文件在这个分区上。
最后手段,如果确认没有任何数据写入风险,可以用 lazy umount:
umount -l /data-l是 lazy,它会立刻把这个挂载点从命名空间里摘掉,等所有引用都释放后再真正卸载。但这个操作有个副作用:新进程看不到这个目录了,老进程还能继续访问,容易造成"文件到底写哪去了"的混乱。我把它当成万不得已的选项,平时不用。
5.2 fstab 写错导致进不了系统,怎么救回来
这是很多人最怕的场景:改完 fstab,重启,系统卡在emergency mode或者一直等某个设备。
救援步骤(CentOS 7 及以上):重启,在 GRUB 菜单出现时按e进入编辑模式,找到以linux16或linux开头那一行,在行尾加一个参数:
systemd.unit=emergency.target然后按Ctrl+X启动,会进入紧急 shell,此时文件系统是只读挂载的。先重新挂成可写:
mount -o remount,rw /然后编辑 fstab,把错的那行改回来或者注释掉:
vi /etc/fstab保存后,如果系统开了 SELinux,还要打一个标记让下次启动重打标签:
touch /.autorelabel这条命令会让下次启动时对整个文件系统做一次 SELinux 上下文重标,耗时可能比较长,但能避免权限类问题。如果确认 SELinux 是关闭的,可以跳过。
最后重启:
reboot顺便说一句,在 GRUB 里加rd.break也能进类似的环境,区别是rd.break停在 initramfs 阶段,根是在/sysroot下,需要chroot /sysroot才能操作;systemd.unit=emergency.target是已经切到真实根了,直接改文件即可。我一般优先用后面这个,少一层 chroot,少一分出错概率。
5.3 xfs 不能缩小,这个坑要在规划阶段避开
前面反复强调 xfs 不能缩小,这里说说它具体会带来什么麻烦。
假设你有一块 2TB 的盘,全部分给了一个 xfs 分区。跑了半年发现需要单独划 200GB 出来给日志用。在 ext4 上,你卸载分区,resize2fs到 1.8TB,然后拿 200GB 建新分区,虽然麻烦但能做。在 xfs 上,这条路直接堵死——xfs_growfs只有扩大没有缩小,没有任何官方工具能缩。
所以用 xfs 的时候,容量规划的原则是"先小后大,宁可拆细"。具体做法:如果底层是 LVM,就别把一个卷组的所有空间都给一个 LV,留一部分空闲,或者按业务拆成多个 LV(data、log、backup 各一个),每个 LV 上是独立的 xfs 文件系统。将来某个不够用了,从空闲空间扩它就行;某个用不上了,整个 LV 删掉回收空间。
如果底层没有 LVM,是裸分区,那就更要在分区阶段就把粒度分好。宁可分成 4 个 500GB 的分区,也别合并成一个 2TB 的。这个建议听起来有点反直觉,但确实是 xfs 用户的经验之谈。
5.4 权限和标签丢失,服务起不来
数据迁完、服务启动失败,日志里提示 permission denied 或者 SELinux denied,这是非常典型的一类问题。原因基本是两个:要么 rsync 时没带-A/-X/--numeric-ids,要么 SELinux 上下文没恢复。
先看是不是普通权限问题:
ls -lZ /data-Z会显示 SELinux 上下文。对比一下正常目录的上下文,比如/var/www/html应该是httpd_sys_content_t类型。如果/data下面是default_t或者unlabeled_t,那就是标签丢了。
恢复方法:
restorecon -Rv /data如果目录不在默认策略覆盖的路径里(比如你自己建的/data),restorecon可能不知道该给它打什么标签,这时候要么用semanage fcontext显式定义规则,要么临时把 SELinux 设成 permissive 验证一下是不是这个原因:
setenforce 0 # 测试服务是否恢复 setenforce 1setenforce 0只是临时生效,重启就恢复,用来快速定位问题非常方便。但如果确认是 SELinux 的问题,正确做法还是要加上下文规则,而不是永久关掉 SELinux。
普通权限丢失的话,用chown和chmod按原样恢复。这也是为什么我强调 rsync 一定要带-aHAX --numeric-ids,这几个参数带上,权限、ACL、SELinux 标签就全过来了,省掉一堆事后修补。
5.5 常见问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| umount 提示 target is busy | 进程占用、shell 卡目录、NFS 导出 | lsof/fuser 查占用,停服务,laizy umount 兜底 |
| mkfs.xfs 报 not a block special device | 设备路径写错或设备不存在 | lsblk 确认设备名 |
| 重启进 emergency mode | fstab UUID 还是旧的 | GRUB 加 emergency.target 进入修 fstab |
| Docker 启动报不支持 d_type | 文件系统 ftype=0 | 重新 mkfs 并加-n ftype=1 |
| 服务报 SELinux denied | 上下文标签丢失 | restorecon -Rv,或用 semanage 定义规则 |
| 扩容后容量没变 | 忘了跑 xfs_growfs,或参数给了设备名 | xfs_growfs /挂载点 |
| 数据同步后文件数对不上 | rsync 源路径斜杠写错 | 检查末尾斜杠语义,重新同步 |
| 文件系统想缩小 | xfs 不支持 | 无法缩小,重新规划分区或换实现方式 |
6. 换完之后:挂载调优和日常维护
6.1 挂载参数怎么调才合适
换成 xfs 只是开始,挂载参数合不合适,直接决定它能不能发挥出应有的水平。我给几套按场景分好的配置。
通用业务分区,读写混合:
UUID=xxx /data xfs defaults,noatime,nodiratime 0 0Web 站点、日志目录,读多写多、大量小文件:
UUID=xxx /data xfs defaults,noatime,nodiratime,logbufs=8,logbsize=256k 0 0logbufs是内存里日志缓冲区的数量,默认 8,可以调到 8 到 16。logbsize是每个缓冲区的大小,最大 256k(v5 文件系统)。这两个参数提高元数据写入的吞吐,在小文件密集的场景下有效果。
数据库、大文件顺序读写:
UUID=xxx /data xfs defaults,noatime,allocsize=1m,largeio,inode64 0 0allocsize影响预分配的块大小,默认值较小,对顺序写大文件不利。largeio让系统优先做大的 I/O 请求。inode64让 inode 可以分布在磁盘的任意位置,而不是集中在开头,对大文件系统有帮助。注意,如果跑的是 MySQL 这类有自己的缓存和 I/O 调度的程序,参数不要乱堆,调错了反而添乱,改之前最好压测对比一下。
有一点要注意,这些参数不是越多越好。每加一个都要清楚它在干什么,不清楚的就别加,用默认值最稳。
6.2 备份、检查和监控的日常动作
xfs 自带一套工具,用好了能省不少事。
备份方面,xfsdump和xfsrestore是官方的方案,支持增量备份:
xfsdump -l 0 -f /backup/data-full.dump -L data_full /data xfsdump -l 1 -f /backup/data-incr.dump -L data_incr /data-l 0是全量,-l 1是从上一次 0 级备份以来的增量。这两个工具在做全盘快照备份的时候很有用,但日常我更习惯用 rsync,因为可控性更强,恢复也直观。
检查方面,记住一条:xfs_repair 必须在文件系统未挂载的情况下运行。挂载状态下只能做只读检查:
xfs_repair -n /dev/sdb1-n是 no-modify,只检查不修复。如果确实需要修复,先卸载,再跑xfs_repair /dev/sdb1。如果日志区损坏需要强制清空,才用-L参数,但用-L有丢数据风险,只在确定日志可以丢弃时才用。
碎片整理的话,xfs_fsr可以在线跑,对指定文件或者整个文件系统做整理:
xfs_fsr /data不过说实话,SSD 时代碎片整理的意义已经小了很多,机械盘上偶尔跑一次就够了,跑太勤反而增加写入。
监控方面,xfs_info /data能看到文件系统的 AG 数量、块大小、日志参数等,出问题时先看这个,确认参数和预期一致。xfs_db是更底层的调试工具,日常基本用不上,知道有这个东西就行。
我个人的习惯是,换完文件系统之后头一周多看几次df -h和iostat -x 1,确认空间增长和磁盘利用率在预期范围内。因为 inode 动态分配的缘故,xfs 的df -i显示的数字会随着使用变化,别看到 inode 数量增长就紧张,那是正常的。
最后分享一个小技巧:如果你在同一个项目里要批量处理多台机器的 ext4 转 xfs,别每台都手动敲一遍命令。把整个流程写成一个脚本,参数用变量传进去,关键步骤之间加read -p做人工确认。这样既省时间,又能保证每台机器的操作步骤完全一致,减少"这台漏了 ftype=1,那台忘了改 fstab"这类低级失误。这个脚本我在内部用了两年多,救过的场比省下的时间更值钱。