
1. 先看清存储堆栈的全貌从硬盘到挂载点之间发生了什么“凡人修云传”这个系列写到第九篇终于轮到存储了。我之所以把存储放在这个位置而不是更早是因为在真实的云环境中网络、计算、身份这些基础能力往往决定了服务能不能跑起来而存储则决定了它能跑多久、跑得多稳、数据会不会丢。一句话总结就是前面几篇在教你盖楼这一篇开始教你打地基里最容易被忽略的那部分——存储堆栈。很多刚接触云主机的朋友会有个直觉存储不就是一块硬盘吗云主机创建的时候选了系统盘数据盘也挂上了格式化之后mount一下能读能写不就行了这种想法在单机个人电脑时代勉强够用但在云环境里尤其是涉及多节点、高可用、容器化调度、数据库落盘这类场景时存储是一个从物理设备到应用可见路径之间叠了好几层抽象的结构。这一层一层的抽象就是所谓的“存储堆栈”。我做个拆解。一台普通的Linux云主机从底层到应用层要经过的存储路径大概是这样物理存储设备可能是云平台给你分配的云硬盘对应到虚拟机里是/dev/vda、/dev/vdb这样的设备节点。存储控制器/驱动层云平台上IO请求从虚拟机经过虚拟化层到达底层分布式存储这一步对云主机内的管理员基本是黑盒但往往也决定了单盘性能上限。设备映射层在系统里/dev/vdb可能不是直接拿来用的它可能被卷管理、软RAID、设备映射device mapper再包一层。分区层用fdisk或parted在整块盘上划出分区形成/dev/vdb1。文件系统层在分区之上格式化出ext4或xfs形成用户可读写的目录结构。挂载层把文件系统挂载到某个目录挂载点应用才能真正往里面写文件。注意我上面列的这六层前五层任何一个环节出了问题应用层都会表现出“磁盘不可用”或“IO异常”但报错信息往往只停留在最表层。这也是为什么很多人排查存储问题时一头雾水——因为你对整个堆栈没有完整认知根本不知道报错来自哪一层。用一个生活化的类比来说存储堆栈就像一栋楼里的快递分拣流程。快递数据从快递员物理磁盘送到楼下收发室设备映射收发室按楼层分类分区每一层楼有自己的储物间文件系统最后住户应用到储物间取件读写数据。如果楼下的收发室临时关闭了住户只会觉得快递收不到了但问题到底出在哪一层得逐层去查。所以这一篇的核心任务就是带你以“管理存储堆栈”的视角把每一层的职责、操作方式和常见坑位都过一遍这样以后再遇到存储问题至少能先判断出大概方位而不是整个下午都在瞎试。2. 新盘接入后的标准操作顺序从/dev/vdb到可用目录我们直接从最常见的场景开始你在云控制台上给一台Linux云主机创建了一块新的数据盘重启之后进入系统现在要把它变成某个应用可以读写数据的目录。整个生命周期里的标准操作可以分成五步。2.1 识别磁盘设备名别把盘认错了登录服务器后第一件事确认系统认到了这块新盘。命令是lsblk它比fdisk -l更直观能展示出设备层级结构[rootcloud ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 253:0 0 40G 0 disk └─vda1 253:1 0 40G 0 part / vdb 253:16 0 100G 0 disk这里vda是40G系统盘已经有了分区vda1并且挂载为根目录vdb是一个100G的空盘没有分区没有文件系统也没有挂载点。这就是我们要处理的目标。这一步最容易犯的错是设备名拼错或者认错盘。如果你在云控制台同时挂载了多块数据盘注意不要把数据盘名称比如disk-xxxx和系统内设备名搞混也别想当然认为vdb一定对应控制台上的第二块盘。保险做法是用lsblk确认容量大小和挂载状态再结合云控制台信息交叉核对。2.2 分区还是不分区这是个判断题接下来要做一个很多人没仔细想过的决定这块盘是直接格式化用还是先分区再用两种方式都能用差别在于运维弹性。我个人的建议是需要后续扩容的盘建议分区一次性固定的盘可以直接格式化。但更准确地说这要看你的文件系统和后续管理计划。直接格式化整块盘mkfs.ext4 /dev/vdb好处是命令少、路径清晰后续用resize2fs扩容也支持坏处是如果你想在同一个盘上切出不同用途的区域比如一部分给日志、一部分给数据只能靠目录区分或者事后被迫做迁移。先分区再用fdisk或parted创建分区再格式化/dev/vdb1。好处是结构清晰后续可以在同一块盘上加分区坏处是多一层分区表如果分区表损坏恢复更麻烦一点。在云环境里由于底层块存储本身就是虚拟化的传统的分区对齐问题比如起始扇区对齐到2048基本已经由现代分区工具自动处理不需要太操心。不过如果你手上有老资料建议分区起始扇区从2048开始现在parted默认就是对齐到1MiB不需要额外指定。我自己的操作习惯是如果是给数据库用的高性能盘整块盘直接格式化不分区减少一层间接如果是通用数据盘按用途分两到三个区比如/data/log和/data/service分开。这样日志写满的时候不会把业务数据目录也堵死故障隔离效果更好。2.3 分区实操一段fdisk对话记录选择分区方案后实际操作其实很简单。下面我用fdisk走一遍创建单分区的过程[rootcloud ~]# fdisk /dev/vdb Welcome to fdisk (util-linux 2.37.4). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Command (m for help): n Partition number (1-4, default 1): 1 First sector (2048-209715199, default 2048): 直接回车 Last sector, /-sectors or /-size (2048-209715199, default 209715199): 直接回车 Created a new partition 1 of type Linux filesystem. Command (m for help): p Disk /dev/vdb: 100 GiB, 107374182400 bytes, 209715200 sectors Device Boot Start End Sectors Size Id Type /dev/vdb1 2048 209715199 209713152 100G 83 Linux Command (m for help): w The partition table has been altered. Syncing disks.如果你用的是超过2T的盘建议直接用parted加GPT分区表fdisk默认MBR只支持到2T虽然新版fdisk也能操作GPT但为了少踩坑大容量盘用parted更顺手parted /dev/vdc mklabel gpt parted /dev/vdc mkpart primary ext4 1MiB 100% partprobe /dev/vdc这里有个细节值得注意fdisk的交互过程里按w写入分区表之后内核不一定立刻刷新分区信息。老一些的系统可能提示“内核仍然使用旧分区表”这时候需要运行partprobe或重启系统让内核重新读取分区表。新版系统一般会自动同步但养成执行partprobe的习惯能避免很多诡异问题。2.4 格式化与挂载最后两步不能忽略的细节分区创建好后格式化文件系统mkfs.ext4 /dev/vdb1如果这是一块业务数据盘我建议加上-L参数给文件系统设置一个卷标label比如-L data。因为在系统里设备名可能漂移但卷标是稳定的标识后续排查或自动化脚本里用卷标定位会比用设备名可靠得多。格式化的时间取决于盘的大小。100G的盘ext4格式化一般几十秒到几分钟如果格式化时间异常长可能需要检查底层存储的IO性能。然后创建挂载点并临时挂载验证mkdir -p /data mount /dev/vdb1 /data df -h /data能正确显示容量和使用率就说明整个路径通了。但到这里还不够——重启之后这个挂载是不会自动恢复的必须写入/etc/fstab。这一步踩坑的人最多我在下一节单独讲。2.5 这里有一个新手容易犯的错误很多新手在fstab里直接写设备名比如/dev/vdb1 /data ext4 defaults 0 0。如果这台机器是KVM虚拟机或物理机一般问题不大但在云环境里设备名可能在每次开机时发生变化。比如之前是vdb下一次开机变成vdcfstab里还是写vdb那数据盘就没挂上系统启动时反而会因为找不到设备而报错进入紧急模式。所以我强烈建议fstab里用UUID或卷标来标识设备而不是设备名。获取UUID的方式blkid /dev/vdb1然后fstab里这样写UUIDxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /data ext4 defaults,nofail 0 0注意我加了nofail选项。这个选项的含义是如果这块盘因为某种原因没有正常挂载系统不会卡在启动流程里而是跳过继续启动。对云主机来说这个选项几乎必加。否则一旦云平台调度导致设备名变了或者盘暂时没就绪你连SSH登录都进不去还得去控制台折腾救援模式那是非常痛苦的一件事。3. 文件系统选型ext4不是唯一答案但大多数时候是最稳的选择很多人格式化的时候习惯性打mkfs.ext4从来没想过为什么用ext4也没想过其他选项。存储堆栈里文件系统这一层是最贴近应用也是最容易影响性能的一层值得花点时间想清楚。3.1 常见几种文件系统的真实差异我列一个尽量简单的对比表基于我在实际生产环境里的观察而不是纯理论文件系统成熟度适合场景主要优势主要顾虑ext4极高通用业务盘、OS盘、虚拟化镜像存储全Linux发行版默认支持故障修复工具成熟稳定单文件最大16T单盘扩展能力有限不支持块级校验xfs高大数据量、视频文件、Kafka日志目录高吞吐、大文件性能好支持在线扩大文件系统在线收缩文件系统困难基本不可收缩内核崩溃恢复稍慢btrfs中个人开发环境、需要快照和压缩的轻量场景内置快照、子卷、压缩、校验和重负荷运维场景下稳定性和工具链成熟度仍需评估zfs中高存储服务器、备份服务器需要极致数据完整性校验和、快照、压缩、RAID-Z一体化Linux下默认未集成需要额外模块对内存要求高从这个表你可以看出不同的文件系统不是简单的“谁比谁快”而是“谁更适合哪种工作负载”。3.2 我实际使用时怎么选以我自己的项目经验来说几个典型场景的选择是这样的数据库数据目录如果没有专门存储设备优先用xfs或ext4这两个在单机场景下的随机读写性能没有本质差别真正的影响因素是磁盘本身类型SSD还是HDD、IO调度器、文件系统挂载参数而不仅仅是文件系统选择。在InnoDB这类直接绕过文件系统缓存O_DIRECT的场景下文件系统的存在感其实不高。日志队列目录比如Kafka、ES的data目录强烈建议xfs。Kafka官方文档里明确推荐xfs因为它在顺序写入和大文件连续分配方面比ext4表现更稳定文件系统碎片对吞吐影响更小。个人开发机或实验环境btrfs可以玩一玩快照功能在折腾系统配置时非常有用回滚就是一条命令的事。备份服务器zfs值得考虑校验和机制能发现静默数据损坏快照和复制功能也能配合远程备份策略。说到挂载参数这里还有个小细节ext4在挂载时有个datawriteback选项可以降低写屏障的开销提升性能但代价是断电时可能丢最近几秒的文件系统元数据一致性。数据库和核心业务数据不要用日志型、可重建的数据可以用。xfs默认就在较小程度上权衡了这类问题不需要特别配置。3.3 在线扩容一个必需掌握的技能没有哪个系统是一开始就规划得刚刚好的数据涨起来之后扩容是几乎必然遇到的需求。ext4的在线扩容流程是“先扩块设备再扩文件系统”。假设云控制台把vdb从100G扩到了200G# 1. 刷新内核识别到的块设备大小 echo 1 /sys/class/block/vdb/device/rescan # 部分环境用 growpart 或直接重启 # 2. 扩分区如果是分区表方式 growpart /dev/vdb 1 # 3. 扩文件系统 resize2fs /dev/vdb1xfs对应的步骤是growpart /dev/vdb 1 xfs_growfs /data注意xfs扩容之后不能缩缩了数据就没了。所以规划xfs分区时初始分区分得小一点完全没问题后面大方向扩容就行但一定要想清楚“只增不减”这个特性。我之前就遇到过一个真实项目有人把Kafka的日志目录用xfs挂在了一个30G的盘上后来数据量涨得厉害把盘扩到200G倒是很顺利但到月底想缩一部分容量给另一套业务才发现xfs没法在线缩。最后只能停机迁移数据到新盘然后重新挂载。如果当初第一版就做好了容量规划根本不会有后面的折腾。4. 存储堆栈的常见故障现象与排查路径我踩过的三个坑存储是运维里最“难”排障的部分因为它的问题不会只在某一层爆发而是从底层向上传导。我把自己在实践里遇到过的三类典型问题写出来每一类都包含完整的排查思路不一定能覆盖你所有场景但至少能帮你建立“逐层排查”的方法论。4.1 故障现象一挂载后写入报Read-only file system这是一个看起来简单但很容易误判的问题。第一次遇到时我以为是自己目录权限没配对反复chmod和chown都没用最后用mount命令一看才发现根因[rootcloud ~]# touch /data/test touch: cannot touch /data/test: Read-only file system [rootcloud ~]# mount | grep /data /dev/vdb1 on /data type ext4 (ro,relatime)挂载状态里明确写着ro。为什么文件系统会变成只读根因绝大多数是文件系统内部检测到不一致inconsistency为了避免进一步损坏内核主动把文件系统切换为只读。这个机制叫“文件系统错误处理策略”ext4默认的错误行为就是remount-ro。排查方法先看dmesg尾部确认是不是IO错误dmesg | tail -50如果能看到类似“Buffer I/O error on device vdb1”“EXT4-fs error (device vdb1)”的日志那基本确认是文件系统层面发现了IO问题或内部损坏。这时候不要强行mount -o remount,rw去改回可写那是跟内核的自我保护机制对着干会让脏数据继续写入造成更严重损坏。正确做法是若盘未卸载先umount如果卸载不了说明有进程占用用lsof或fuser确认占用进程。执行文件系统检查和修复e2fsck -f /dev/vdb1xfs文件系统则执行xfs_repair -L /dev/vdb1-L表示清空日志谨慎使用。修复完成后重新mount再写入测试。这类问题在云盘上还经常伴随底层存储节点抖动。如果你确认本机操作没问题报告发给了云厂商客服但对方查了很久也没定位到那有可能是云平台底层迁移或存储节点维护导致短暂的IO异常属于基础设施层面的偶发问题。此时记录好时间点优先保证数据恢复不用太纠结根因。4.2 故障现象二df正常但写大文件时卡死或超时另一个高频问题是应用正常跑着跑着突然变慢写文件时卡住df看磁盘空间和inode都还正常iostat看使用率也不高。这种情况的元凶经常藏在存储堆栈的中间层——设备映射。如果系统里用到了LVM逻辑卷管理整条链路变成了物理盘 - PV - VG - LV - 文件系统。任何一层有故障都会导致行为诡异。我遇到过一个非常典型的案例数据盘是LVM管理的某天写入突然变得极其缓慢但iostat显示物理盘使用率只有10%左右。后来排查发现LVM底层对应的物理卷所在的物理盘有坏块IO重试机制导致每次写入都要等很长时间的超时然后才切换路径。这个“等待”在应用层看起来就是卡顿。排查链路是# 1. 看整个存储堆栈结构 lsblk pvs # 查看物理卷状态 vgs # 查看卷组状态 lvs # 查看逻辑卷状态 # 2. 看IO等待情况 iostat -x 1 # 重点看await、svctm、utilutil低但await高就有可能是硬件或底层在重试 # 3. 查看内核IO错误日志 dmesg | grep -i error找到根因后如果是坏块导致的能修复的用fsck或smartctl进一步确认不能修复的就得从备份恢复或者导出数据了。这个案例的通用价值在于存储故障不是一个平面而是一条链路。你只有把lsblk输出的每一层都看明白才能确认问题卡在哪个环节。4.3 故障现象三fstab写错导致系统启动进入紧急模式这个坑可以说是每个Linux运维都至少踩过一次的。原因往往是fstab里新增了一行挂载配置但设备名不对、UUID写错、或者文件系统类型填错开机时系统尝试挂载发现失败默认策略下就会放弃继续启动进入emergency模式连SSH都连不上。如果遇到这种情况别慌用云控制台的VNC登录或者救援模式进入系统把fstab里的错误行注释掉重启即可。但这个过程非常耽误时间尤其是在Notebook上没有VNC权限的环境里只能反复提工单。更好的做法是养成一个习惯每次修改fstab后先执行mount -a测试。mount -a会尝试重新挂载fstab里的所有条目如果配置有误会立刻报错而不会等到重启才暴露[rootcloud ~]# mount -a mount: /data: wrong fs type, bad option, bad superblock on /dev/vdb1, missing codepage or helper program, or other error.看到这样的错误马上回头检查fstab那一行把问题消灭在重启之前。5. 存储管理的日常习惯几个让我少吃苦头的检查要点说了这么多具体操作最后再分享一些我在实际运维中沉淀下来的习惯。这些东西不在任何官方文档里写得特别详细但对长期维护多台云主机的场景非常有用。5.1 定期做空间与inode的双重体检很多人监控磁盘只看df -h看容量用完了就清理文件却忽略了inode数量。inode是文件系统里记录文件元数据的结构每个文件包括目录都要占用一个inode。如果文件系统里的inode耗尽了即使磁盘还有大量空间也无法创建任何新文件。判断方法df -i /data如果IUse%接近100%说明inode耗尽。常见的罪魁祸首是某些程序疯狂产生小文件比如未清理的日志文件、邮件队列临时文件、消息中间件消费失败产生的死信等。我自己的习惯是写一个简单的巡检脚本每天cron跑一次检查各挂载点的容量和inode使用率超过阈值就告警到企业微信或Telegram Bot。这样可以在故障发生前就介入处理而不是等到业务写不进文件才被动响应。5.2 大目录与文件分布画像如果磁盘空间确实满了下一步要快速找出空间都被谁吃了。du和find是这时候最趁手的工具# 找出根目录下最大的几个目录 du -h --max-depth1 / 2/dev/null | sort -hr | head -20 # 找出某个目录下最大的文件 find /data -type f -size 1G -exec ls -lh {} \; 2/dev/null | awk {print $5, $9} | sort -k1 -rh注意du在超大目录上可能跑得很慢建议用--max-depth控制扫描深度或者用ncdu这个交互式工具快速定位。5.3 备份意识存储堆栈里滚烫的一课说到备份我在这里想再多说一句。云平台通常会承诺底层磁盘的持久性和副本机制但那是针对物理硬件的并不能保护你免受误删文件、软件bug覆盖数据、或恶意脚本删库的伤害。真正能兜底的只有你自己的备份策略。针对基本存储场景一个足够的基础备份方案是用rsync做异地文件同步配合存储快照做时间点回滚。云平台一般都有磁盘快照功能在控制台里点一下就能创建成本很低。数据库类应用则应该做逻辑备份如mysqldump、pg_dump或基于binlog的持续备份。我见过太多人觉得“云盘本身就双副本了不需要额外备份”直到某一天一个rm -rf误操作执行在不该执行的地方或者一次变更脚本里的bug把数据目录清掉才追悔莫及。存储分层再健壮也挡不住逻辑层的失误。5.4 每次变更留痕文档比记忆可靠最后一条是关于流程管理的每一次对存储的变更都要记录下来。包括初始设备名、UUID、分区布局、文件系统、挂载参数、扩容时间点、备份策略。云平台上的虚拟机有可能被销毁重建如果只有系统里存在再重建时就只能靠经验和猜了。我自己是用一个简单的Markdown文档每台服务器一页把存储相关的元信息整理清楚。看起来笨但真正派上用场的时候能省几小时的排查时间。存储堆栈的修炼核心不在于背命令而在于建立这种“从底层到应用一层层看问题”的思维。当你能在脑海里把一块云盘从物理设备到挂载点的整个路径勾勒出来遇到问题不再慌才算真正入了这一门的门。凡人修云修的不只是工具熟练度更是这种全局的视角。