被上一任运维坑惨了!Linux 下 rm -rf 删库万能补救流程

· 2026-09-11 07:15 · 5 阅读

dbaplus社群 2026-09-11 07:15 广东

rm -rf 不可怕,可怕的是“以为自己有备份所以不担心”……

阅读须知

这篇文章是给一线运维写的“事故复盘 + 可落地手册”,不是科普文。整篇会沿着一条真实的故障时间线展开:从误删发生的第一秒讲起,到工具准备、磁盘保护、文件系统选择、恢复命令、风险判断、验证、复盘,最后落到防误删的工程实践。文中所有命令、配置、参数,都按 RHEL/CentOS 7/8、Ubuntu 20.04/22.04 上的常见环境编写;不同发行版、不同文件系统、不同内核版本在具体行为上可能略有差异,关键步骤我都标了“以实际环境为准”。

文中涉及的所有破坏性操作(包括但不限于:dd、mkfs、umount、fuser -k、lvremove、zfs destroy、kubectl delete、iptables -F、rm -rf 演示等)都已经脱离生产环境,演示前请务必确认在测试机或快照上操作。生产环境操作必须经过变更审批、备份、灰度、回滚预案四步走。

阅读过程中如果只想看“立即能用的命令”,可以跳到第 11 章《应急工具包与一键脚本》;如果想了解“为什么 rm -rf 几乎不可逆”,可以重点看第 3、4 章;如果想搭建防御体系,看第 15、16 章。

一、事故背景:那条 rm -rf 是怎么敲下去的

事情发生在某周二凌晨 02:17,运维小 A 在清理一台老旧的备份服务器。这台机器是公司两年前采购的,用于承接应用层每天 02:00 的 mysqldump 输出,然后通过 rsync 推送到远端异地机房。机器是典型的“用着没出过事、所以半年没巡检”的状态。

起因是一个看似合理的清理任务:上一任运维离职时留下了一段在 /opt/scripts/cleanup.sh 里的脚本,里面有一行核心逻辑是:

    find -L /data/backup -type d -mtime +30 -exec rm -rf {} \;

    find -L 是上一任运维加的,本意是“跟软链,免得漏清”。这条命令的初衷是清理 30 天前的备份目录。但小 A 接手后做了一次目录重构,把 /data/backup 拆分成了:

    • /data/backup/mysql:mysqldump 的 SQL 文件

    • /data/backup/app:应用的归档包

    • /data/backup/logs:操作日志归档

    • /data/backup/tmp:一些临时落盘

    他只是改了 cleanup.sh 里的路径,把 /data/backup 改成了 /data/backup/tmp,因为他觉得临时目录总归要清。但这一步没有经过 Code Review,也没有在预发环境演练过。

    当天凌晨 02:00,cron 触发了清理任务。02:17,小 A 收到远端机房同事的微信:你们的备份没过来。

    他 ssh 上去一看,/data/backup/mysql 和 /data/backup/app 都空了。/data/backup/logs 还在。脚本里的 find -L ... -exec rm -rf {} \; 在两个被软链引用的目录上执行了——而这个软链是上一任运维为了偷懒做的:

      /data/backup/tmp -> /data/backup

      也就是说,/data/backup/tmp 是个指向父目录的软链。find -L 主动跟软链,等于把 /data/backup 当成“临时目录”展开去清理,对 /data/backup 下的所有子目录执行了 rm -rf。/data/backup/logs 之所以还在,是因为上一次 cleanup.sh 在 find 走到 logs 目录时刚好被某个后台进程打开了几个文件,rm -rf 删掉了目录的硬链接但文件 inode 还被进程持有,看起来“还在”,其实只是一个假象

      更糟的是,凌晨 02:30,远端异地机房的 rsync 接收端因为拿不到文件,触发了告警:连续三次推送失败,监控把告警升级到了 P1。

      这就是这次事故的完整背景。下面进入正文,从事故响应的第一秒讲起。

      二、事故响应的第一秒:先停下来,再开始救

      误删发生后,新人最容易犯的错是“马上动手救”。这是非常危险的直觉,因为绝大多数“想当然的恢复操作”反而会扩大事故。正确的第一反应是按顺序做下面五件事。

      1、保持现状,不要重启

      重启会让内存中的 page cache 全部丢弃,原本还活着的 inode 信息(文件被打开时内核仍持有)会随着进程退出而消失。所以:

      • 不要 reboot

      • 不要 init 6 / shutdown -r now

      • 不要 sync(虽然 sync 本身不会丢数据,但会让内核尝试把脏页回写,如果磁盘本来就有压力,反而会触发一些不可预期的写)

      • 不要 kill -9 任何你以为“无关”的进程

      正确的做法是:先观察,不要做任何状态变更。

      2、立即隔离写流量

      如果误删的是关键业务目录,要做的第一件事是停止该机器上一切会继续往这块盘写入的进程。对于我们这个案例,具体动作是:

        # 1. 停掉 cron,避免 cleanup.sh 二次执行
        systemctl stop crond


        # 2. 停掉 mysqldump 的定时任务(如果有 systemd timer)
        systemctl list-timers | grep -i mysql


        # 3. 停掉应用层的归档/上传/打包任务
        #    (以你们实际的进程名为准)
        ps -ef | grep -E "tar|rsync|mysqldump|java|python" | grep -v grep

        如果业务不能停,至少要通过 systemctl mask 把定时任务彻底屏蔽:

          systemctl mask crond

          mask 会创建一个指向 /dev/null 的软链到 service 文件,cron 不再能被 start,直到 unmask。

          3、把机器从负载均衡/调度池摘掉

          如果这台机器在 LVS、Nginx upstream、Kubernetes pod、Service 节点池里,要立刻摘流量。这一步不是技术问题,是组织流程问题:

          • 走 change 系统 / 应急群

          • 同步到位之后再做

          • 不要在没同步的情况下直接改 iptables

          我们这次事故里,这台备份服务器不在业务主链路上,摘流操作比较简单,直接在备份调度系统里把它置为“不可用”即可。

          4、记录现场状态

          把当时能收集到的状态全部记下来,便于后续复盘和评估恢复方案:

            # 1. 当前时间、uptime、负载
            date
            uptime


            # 2. 磁盘使用情况
            df -h
            df -i


            # 3. 内存使用
            free -h


            # 4. 进程列表
            ps auxf > /tmp/ps_auxf_$(date +%Y%m%d_%H%M%S).txt


            # 5. 已挂载文件系统
            mount > /tmp/mount_$(date +%Y%m%d_%H%M%S).txt
            cat /proc/mounts >> /tmp/mount_$(date +%Y%m%d_%H%M%S).txt


            # 6. 打开的文件描述符(重要,可能还有活口)
            lsof > /tmp/lsof_$(date +%Y%m%d_%H%M%S).txt
            lsof /data/backup > /tmp/lsof_data_backup_$(date +%Y%m%d_%H%M%S).txt


            # 7. 磁盘 IO、块设备
            iostat -dx 1 3 > /tmp/iostat_$(date +%Y%m%d_%H%M%S).txt
            lsblk > /tmp/lsblk_$(date +%Y%m%d_%H%M%S).txt


            # 8. 文件系统类型
            blkid > /tmp/blkid_$(date +%Y%m%d_%H%M%S).txt
            file -s /dev/sd* >> /tmp/blkid_$(date +%Y%m%d_%H%M%S).txt


            # 9. fstab 配置
            cp /etc/fstab /tmp/fstab_$(date +%Y%m%d_%H%M%S).bak


            # 10. 内核日志
            dmesg > /tmp/dmesg_$(date +%Y%m%d_%H%M%S).txt
            journalctl -k --since "1 hour ago" > /tmp/journal_kernel_$(date +%Y%m%d_%H%M%S).txt

            这些信息看起来很啰嗦,但每一项都有用。后面判断“还能不能恢复”时,/proc/mounts 可以告诉你挂载方式,lsof 还能告诉你哪些文件被进程持有,blkid 告诉你文件系统类型,iostat 告诉你是否还有大量写 IO。

            5、通知相关方

            误删属于 P1 级别事故,必须在第一时间通知:

            • 直接主管

            • 应用 owner

            • DBA(如果涉及数据库)

            • 信息安全(如果涉及合规数据)

            • 备份系统 owner

            通知内容要包括:时间、影响范围、初步判断、当前正在做什么、预计下一步动作。不要隐瞒、不要美化、不要在群里反复讨论细节。具体的故障复盘可以在事故结束后单独开 review。

            三、为什么 rm -rf 几乎不可逆

            在讲恢复之前,必须先理解“为什么 rm -rf 这么难救”。很多新人以为 rm -rf 跟 Windows 的“Shift + Delete”差不多,实际上差异巨大。

            1、Linux 文件删除的本质

            Linux 下“删除一个文件”并不是把磁盘上的字节清零,而是做三件事:

            • 把目录项(dentry)从父目录的目录树里摘掉

            • 把文件的链接计数(link count)减 1

            • 如果链接计数降到 0,文件变为“unlinked”状态,等待被回收

            只有当文件 unlinked 且没有被任何进程持有(open file count = 0)时,内核才会把这个 inode 释放回 inode 位图。inode 被释放后,对应的磁盘块才会被标记为 free,加入到空闲块池里,等待被新数据覆写。

            所以“删除”只动了 inode 和目录项的元数据,磁盘上的实际数据是完整保留的——直到被新数据覆盖。

            2、ext4 文件系统下 rm -rf 的实际动作

            以 ext4 为例,rm -rf 在 VFS 层依次触发:

              vfs_unlink / vfs_rmdir
                -> ext4_unlink / ext4_rmdir
                   -> ext4_delete_entry(删目录项)
                   -> ext4_dec_count(链接计数 -1
                   -> 如果是文件:调用 ext4_free_inode_after_ordered 之类函数,把 inode 标为 free
                   -> 把对应的 block 标为 free

              关键点:ext4 在删除时只是把 block 标记为空闲,并不会清零 block 内容。所以只要 block 没被新数据覆盖,理论上就能恢复。

              3、xfs 文件系统下的差异

              xfs 的元数据布局跟 ext4 完全不同:

              • xfs 使用 B+ 树管理 inode(AGI)和 block(AGF)

              • xfs 的目录项存放在 dir2 格式中,删除动作比 ext4 更复杂

              • xfs 在删除大文件时,extent 分配器会很快把 block 标记为 free,但不会清零

              xfs 不可逆的另一个原因是:xfs 的日志(xlog)相对激进,元数据修改会先写日志。恢复工具必须正确解析 xfs 的所有 on-disk 结构。

              社区上有一个流传很广的说法:“xfs 删了就没了,ext4 还能救”。这句话不严谨,但方向上是对的:xfs 的恢复工具少、效果差,主流方案是 xfs_undelete(在某些版本上效果也一般);ext4 的恢复工具链成熟(extundelete、debugfs、testdisk)。

              4、为什么不能 100% 恢复

              即使文件系统保留 block 不清零,下列情况也会导致数据无法恢复:

              • 块已经被新数据覆盖:内核把 block 分配给新文件

              • journal 重放:ext4 的日志回放可能修改元数据

              • 文件被 truncate 后再写入:恢复出来的是空洞 + 后续覆盖的数据

              • 文件被碎片化严重:恢复出来的 block 顺序可能错乱

              • 文件名丢失:很多工具只能按 inode 恢复,文件名无法对应

              • 文件系统是 btrfs/zfs 这种 CoW 文件系统:snapshot 存在就能完全回滚,snapshot 不存在就跟 ext4/xfs 一样看运气

              所以“恢复”本质是抢救,不是保证。恢复出来的文件可能:

              • 名字变成 inode_12345.dump

              • 内容有部分损坏(特别是被部分覆盖的)

              • 大文件可能中间缺块

              • 压缩包、归档包可能校验失败

              这些风险必须在动手前就跟业务方讲清楚。

              5、为什么不能依赖“回收站”

              Linux 默认没有回收站。rm 走的是 VFS unlink,没有回收站机制。一些桌面环境(GNOME、KDE)有自带的回收站(~/.local/share/Trash),但服务器上几乎不用。

              很多人会装 trash-cli 来模拟 macOS 的回收站,命令从 rm 改成 trash。这确实是防误删的好习惯,但有几个坑:

              • 跨用户不通用:A 用户的回收站,B 用户看不到

              • 跨服务器不同步:rm 在远程机器上跑,回收站也在那台机器

              • 习惯难迁移:老脚本里全是 rm -rf,改造工作量大

              • 不能解决“rm 回收站目录”这种自残操作

              我们这次事故中,软链 + 旧脚本 + 路径变更,三个独立的小问题叠加才造成。如果你只解决其中任何一个,都不会出问题。

              四、文件系统层原理:删一个文件到底动了什么

              要做专业级的恢复,必须理解文件系统在磁盘上是怎么组织的。这一章会从磁盘分区表一直讲到 ext4 的 inode 结构,目的是让你看懂 extundelete 和 debugfs 输出的每一行。

              1、磁盘到文件系统的层次

                物理磁盘 /dev/sda
                  -> 分区表(MBR / GPT)
                    -> /dev/sda1, /dev/sda2 ...
                      -> LVM PV / 直接文件系统
                        -> mkfs.ext4 / mkfs.xfs ...
                          -> mount 到 /data
                            -> 用户文件 / 目录

                误删发生的位置是“用户文件 / 目录”层,但恢复工具操作的是“文件系统”层。中间任何一个环节错位,都会导致恢复失败。

                2、ext4 的磁盘布局

                ext4 把一个分区划分成多个 block group(块组),默认每个块组大小由 mkfs.ext4 自动算。核心数据结构:

                区域

                用途

                Superblock (超级块)

                文件系统元信息:block size、inode count、magic number 等

                Group Descriptors (组描述符)

                描述每个块组的位置和大小

                Block Bitmap (块位图)

                标记本块组内哪些 block 已被使用

                Inode Bitmap (inode 位图)

                标记本块组内哪些 inode 已被使用

                Inode Table (inode 表)

                存储 inode,每个 inode 默认 256 字节

                Data Blocks (数据块)

                实际文件数据和目录项

                mkfs.ext4 时的关键参数:

                  mkfs.ext4 -b 4096 -I 256 -N 1000000 /dev/sda1
                  #  -b 4096: block size 4KB
                  #  -I 256:  inode size 256 字节
                  #  -N 1000000: 预留 100 万个 inode

                  不同的 -b 和 -I 选择会直接影响恢复工具的解析逻辑。extundelete 默认会自动识别,但遇到 journal 损坏的极端情况会失败。

                  3、inode 是恢复的关键

                  inode 是 Unix “一切皆文件” 的灵魂。每个 inode 包含:

                    struct ext4_inode {
                        __le16  i_mode;        // 文件类型 + 权限
                        __le16  i_uid;         // owner
                        __le32  i_size_lo;     // 文件大小低位
                        __le64  i_blocks_lo;   // 占用的 block 数量
                        __le32  i_atime;       // 访问时间
                        __le32  i_ctime;       // 状态变更时间
                        __le32  i_mtime;       // 修改时间
                        __le32  i_dtime;       // 删除时间(关键!)
                        __le16  i_gid;
                        __le16  i_links_count; // 链接计数
                        __le32  i_flags;
                        __le32  i_osd1;
                        __le32  i_block[15];   // 直接 / 间接块指针
                        __le32  i_generation;
                        __le32  i_file_acl_lo;
                        __le32  i_size_high;
                        __le32  i_obso_faddr;
                        __le16  i_osd2[3];
                        __le16  i_extra_isize;
                        __le16  i_checksum_hi;
                        __le32  i_ctime_extra;
                        __le32  i_mtime_extra;
                        __le32  i_atime_extra;
                        __le32  i_crtime;
                        __le32  i_crtime_extra;
                        __le32  i_version_hi;
                        __le32  i_projid;
                        __le16  i_checksum_lo;
                        __le16  i_reserved;
                    };

                    其中 i_dtime 是删除时间,i_links_count 是链接计数。如果一个 inode 的 i_dtime != 0 但 i_blocks > 0,说明这是一个“unlinked 但 block 还没被回收”的文件,恢复工具可以扫到。

                    i_block[15] 数组:

                    • i_block[0..11]:12 个直接块指针,直接指向数据 block

                    • i_block[12]:1 个间接块指针,指向一个 block,block 里再放指针

                    • i_block[13]:1 个二级间接块指针

                    • i_block[14]:1 个三级间接块指针

                    对于小文件(< 48KB,假设 block size 4KB),所有 block 都在 i_block[0..11] 里,恢复时直接读指针即可。对于大文件,需要解析间接块,这就是为什么“恢复出来的大文件可能有部分缺块”。

                    4、ext4 的日志

                    ext4 默认开启 journal(除非显式指定 -O ^has_journal)。日志的作用是:所有元数据修改先写到日志区,再异步刷到主文件系统。

                    删除文件时:

                    • ext4_journal_start:分配一个 handle

                    • 把 inode 变更、目录项变更等元数据写入 journal

                    • ext4_journal_stop:handle 提交,日志刷盘

                    恢复工具读取时:

                    • 如果日志尚未 replay,恢复工具能读到完整的删除轨迹

                    • 如果日志已经 replay 且被覆盖,则只能通过 inode 扫描找“unlinked inode”

                    5、xfs 的磁盘布局

                    xfs 的核心结构:

                    区域

                    用途

                    Allocation Groups (AG)

                    把整个 FS 拆成多个 AG,每个 AG 独立管理

                    AGF (AG Free Space)

                    空闲块位图

                    AGI (AG Inode)

                    inode 位图

                    AGFL (AG Free List)

                    空闲 inode 链表

                    Inode B+ Tree

                    inode 索引

                    Directory B+ Tree

                    目录索引

                    xfs log

                    元数据日志

                    xfs 的目录项采用 B+ 树,删除时只把对应项标 deleted,目录树结构本身保留。但因为 xfs 的元数据更复杂,恢复工具实现难度大得多。

                    6、btrfs / zfs 的“天然保护”

                    btrfs 和 zfs 是 CoW (Copy-on-Write) 文件系统,天然支持 snapshot:

                    • btrfs:btrfs subvolume snapshot /data /data/bak_20260609

                    • zfs:zfs snapshot data/mysql@20260609

                    如果误删发生在 CoW 文件系统上,且有近期 snapshot,恢复成功率接近 100%。这也是为什么云厂商(AWS EBS snapshot、阿里云快照、腾讯云 CBS 快照)会用 CoW 风格的快照。

                    我们这次事故是 ext4,没有 snapshot 兜底,只能走传统恢复工具链。

                    五、立即止血:避免“恢复行动”本身造成二次伤害

                    讲原理讲完了,下面进入“怎么做”的环节。但要再次强调:在你做任何恢复操作之前,必须先做“止血”,否则恢复过程中产生的中间文件、临时挂载、工具日志,可能会把还在空闲块池里的原始数据覆盖掉。

                    1、把误删的分区设为只读

                    最有效的一招:把误删的分区 remount 成 read-only。

                      # 1. 找到误删数据所在的挂载点对应的设备
                      df /data/backup
                      # 假设输出:/dev/sdb1   50G   30G   18G  63% /data
                      # 2. 重新挂载为只读
                      mount -o remount,ro /data
                      # 3. 验证
                      mount | grep /data
                      # 应该是:/dev/sdb1 on /data type ext4 (ro,...)

                      注意:

                      • mount -o remount,ro 要求该文件系统没有任何正在写的文件描述符。如果有进程在写,会报 EBUSY

                      • 如果有进程持有写句柄,可以先 fuser -v /data 找到进程,再判断是否能让它退出

                      • 在备份服务器这种场景下,进程不多,可以直接定位

                      另一种更安全的方式:把整个块设备设为只读(块设备层 readonly):

                        # 块设备层 readonly,会让该设备上所有文件系统都进入只读
                        blockdev --setro /dev/sdb1

                        blockdev --setro 不会触发写回,也不会要求卸载,是最稳妥的“冻结”手段。

                        2、如果无法 remount,做 LVM 快照(最推荐的工业级方案)

                        如果你用的是 LVM,那 lvcreate -s 是救命的银弹:

                          # 1. 查看 VG 剩余空间
                          vgdisplay | grep -E "VG Name|Free"
                          # 2. 给误删的 LV 创建快照
                          #    -s: snapshot
                          #    -L: 快照大小(按需给,至少能覆盖 LV 写放大)
                          #    -n: 快照名
                          lvcreate -s -L 10G -n data_snap_20260609 /dev/vg0/data
                          # 3. 挂载快照
                          mkdir -p /mnt/snap
                          mount -o ro /dev/vg0/data_snap_20260609 /mnt/snap
                          # 4. 在快照上做恢复实验
                          ls -la /mnt/snap/data/backup/mysql/

                          LVM 快照的原理是 COW(Copy-on-Write):快照创建后,对原 LV 的写操作会把原数据复制到快照空间;快照空间内的数据保持创建那一刻的视图。也就是说,快照创建瞬间,原 LV 上所有未分配 block 的内容都被“冻结”了,恢复操作可以在快照上安全进行。

                          如果你的环境是云服务器(ECS、CVM、EC2),云厂商一般也提供磁盘快照,原理类似:

                            # 阿里云示例:通过 OpenAPI 创建快照
                            # aws ec2 create-snapshot --volume-id vol-xxx --description "pre-recovery-$(date +%F)"
                            # 
                            腾讯云 / 阿里云控制台有 GUI 操作

                            3、如果既不能 remount 又没有 LVM,做磁盘镜像

                            最差情况:物理机 / 虚拟机 / 文件系统不支持快照。这种情况下,做一个 dd 镜像到另一块盘:

                              # 1. 找一块至少同样大的目标盘
                              lsblk


                              # 2. 用 dd 做全盘镜像(注意:这一步会读整个源盘,耗时长)
                              #    status=progress 显示进度
                              #    conv=noerror,sync 出错不停止,缺失块补零
                              dd if=/dev/sdb of=/dev/sdc bs=4M status=progress conv=noerror,sync


                              # 3. 验证镜像
                              md5sum /dev/sdb
                              md5sum /dev/sdc

                              风险提示:

                              • dd 误把 of 写错是真实存在的灾难。一定要 double check 三遍。

                              • 建议加 oflag=direct 绕过 page cache

                              • 镜像会读整个磁盘,如果磁盘有坏道,conv=noerror 防止中断

                              • 整个过程磁盘 IO 会打满,建议停业务后做

                              如果机器是云上 VM,可以直接对云盘做“磁盘快照”功能。云盘快照底层就是基于 COW 的,对原盘的影响极小。

                              4、一个常被忽略的细节:进程持有文件

                              lsof 是这一环节的另一个关键工具。rm -rf 删除了目录项,但只要有进程还 open 着这个文件,内核就不会真的释放 inode 和 block。我们可以走 

                                /proc/$PID/fd/$N 路径把文件复制出来:
                                # 1. 找出所有还在 /data/backup 下的打开文件
                                lsof +L1 /data/backup
                                # +L1 表示 link count 已经 <= 1(典型的 unlinked 状态)
                                # 2. 也可以直接找被删但还在跑的
                                lsof | grep deleted
                                # 输出形如:
                                # mysqld  1234  mysql  8u  REG  253,1  1048576  12345  /data/backup/mysql/dump_20260608.sql (deleted)

                                注意 deleted 标志。这表示文件已经被 unlinked 但仍在被进程持有。

                                复制方法:

                                  # 1. 找到 PID 和 FD
                                  ls -la /proc/1234/fd/8
                                  # lrwx------. 1 mysql mysql 64 Jun  9 02:18 8 -> /data/backup/mysql/dump_20260608.sql (deleted)


                                  # 2. 直接 cp
                                  cp /proc/1234/fd/8 /tmp/recovered_dump_20260608.sql

                                  这是恢复成本最低的途径,必须第一时间执行,因为进程一旦退出,文件就真没了。

                                  5、临时禁止 syslog 写入

                                  很多机器的 syslog 会持续写 /var/log/messages 或 /var/log/syslog,如果这块盘恰好是误删的盘,syslog 的写入会持续覆盖空闲块。

                                  应对:

                                    # 方法 1:临时停 rsyslog
                                    systemctl stop rsyslog


                                    # 方法 2:让 rsyslog 写到内存(tmpfs)
                                    #   /etc/fstab 加一行:
                                    #   tmpfs /var/log tmpfs defaults,noatime,size=512M 0 0
                                    #   然后 mount -a

                                    六、评估能否恢复:环境调查清单

                                    正式进入恢复前,需要把环境摸清楚。下面是一个 7 步调查清单,列在前面是因为它能直接决定恢复方案。

                                    第一步:确认文件系统类型

                                      # 方法 1:blkid
                                      blkid /dev/sdb1
                                      # /dev/sdb1: UUID="..." TYPE="ext4"


                                      # 方法 2:mount
                                      mount | grep /data
                                      # /dev/sdb1 on /data type ext4 (rw,relatime,seclabel)


                                      # 方法 3:dumpe2fs(只对 ext 系列)
                                      dumpe2fs -h /dev/sdb1 | head -30

                                      第二步:确认挂载点和挂载选项

                                        cat /proc/mounts | grep /data
                                        findmnt /data
                                        # 输出示例:
                                        # TARGET SOURCE    FSTYPE OPTIONS
                                        # /data  /dev/sdb1 ext4   rw,relatime,seclabel

                                        特别注意 relatime/strictatime/noatime:如果是 noatime,inode 上的 i_atime 不会被更新,恢复时能看到更准确的访问时间。

                                        第三步:确认磁盘是否还有大量写入

                                          iostat -dx 1 5
                                          # 看 %util 和 w/s。如果 w/s 持续 > 0,说明有进程在写,越早止血越重要。

                                          第四步:磁盘健康度

                                            # smartctl(需要安装 smartmontools)
                                            # CentOS / RHEL
                                            yum install -y smartmontools
                                            # Ubuntu / Debian
                                            apt-get install -y smartmontools


                                            smartctl -H /dev/sdb
                                            smartctl -a /dev/sdb

                                            如果磁盘本身有坏道,恢复的成功率会显著下降。

                                            第五步:磁盘空间

                                              df -h /data
                                              df -i /data
                                              # 重点看 inode 使用率。ext4 上即使磁盘空间够,inode 满了也无法创建文件。

                                              第六步:文件系统是否已经损坏

                                                # ext 系列
                                                fsck -n /dev/sdb1
                                                # -n 表示只读检查,不修


                                                # xfs
                                                xfs_repair -n /dev/sdb1

                                                fsck -n 可以列出错误,但不会自动修复。这步是“诊断”,不是“治疗”。

                                                第七步:内核日志

                                                  dmesg | tail -100
                                                  journalctl -k -p err --since "1 hour ago"

                                                  如果看到 EXT4-fs error、I/O error、Buffer I/O error 等,说明磁盘或 FS 本身有健康问题。

                                                  七、方案 A:ext4 + extundelete(首选)

                                                  extundelete 是 ext 系列文件系统的“第一恢复工具”,作者是 ext2fsprogs 维护者之一。它能:

                                                  • 恢复指定文件

                                                  • 恢复整个目录

                                                  • 按 inode 扫描恢复

                                                  • 恢复指定时间之前的所有文件

                                                  重要约定:下面所有 extundelete 命令的路径参数都是相对文件系统根(FS root),不是相对 OS 挂载点。如果 /dev/sdb1 挂在 /data,文件在 OS 里是 /data/backup/mysql/dump.sql,那么 extundelete 接收的路径必须是 /backup/mysql/dump.sql,不是/data/backup/mysql/dump.sql。这是新人最容易踩的坑,路径写错不会报错,但恢复结果为空。

                                                  1、安装 extundelete

                                                    # CentOS / RHEL(需要 EPEL)
                                                    yum install -y epel-release
                                                    yum install -y extundelete


                                                    # Ubuntu / Debian
                                                    apt-get install -y extundelete


                                                    # 源码编译(如果包仓库里没有对应版本)
                                                    wget https://sourceforge.net/projects/extundelete/files/extundelete/0.2.4/extundelete-0.2.4.tar.bz2
                                                    tar xf extundelete-0.2.4.tar.bz2
                                                    cd extundelete-0.2.4
                                                    ./configure
                                                    make
                                                    make install

                                                    2、准备工作

                                                      # 1. 先把被删的 FS 设为只读(前面讲过)
                                                      mount -o remount,ro /data
                                                      # 或者
                                                      umount /data


                                                      # 2. 把恢复结果输出到独立的磁盘(不能写回原盘)
                                                      mkdir -p /mnt/recovery
                                                      mount /dev/sdc1 /mnt/recovery  # 假设 /dev/sdc1 是新盘

                                                      3、恢复指定文件

                                                        # 假设 /dev/sdb1 挂在 /data,文件 OS 路径是 /data/backup/mysql/dump_20260608.sql
                                                        # 那么 extundelete 接收的是 FS 根相对路径:
                                                        extundelete /dev/sdb1 --restore-file /backup/mysql/dump_20260608.sql
                                                        # 输出在当前目录的 RECOVERED_FILES/ 下

                                                        注意:

                                                        • 路径是从 FS 根开始的相对路径(去掉挂载点前缀)

                                                        • 如果你不知道完整路径,可以先看 journal(见 7.5)或用 --list-all

                                                        • 路径分隔符在 extundelete 命令行里用 /

                                                        4、恢复整个目录

                                                          extundelete /dev/sdb1 --restore-directory /backup/mysql

                                                          5、按时间窗口恢复

                                                            # 恢复到 2026-06-09 00:00:00 之前的所有文件
                                                            extundelete /dev/sdb1 --restore-all --before "2026-06-09 00:00:00"

                                                            --before 接受的时间格式默认按 strptime 解析,常用写法是 "%Y-%m-%d %H:%M:%S",对应 "2026-06-09 00:00:00"。不同版本的 extundelete 对日期字符串的容忍度不同,建议先在小 FS 上验证。

                                                            类似地,--after 表示“恢复这个时间点之后”的文件,组合使用可缩小范围。

                                                            6、扫描所有可恢复文件

                                                              # 只列出,不恢复
                                                              extundelete /dev/sdb1 --list-all


                                                              # 真正恢复所有可恢复文件
                                                              extundelete /dev/sdb1 --restore-all

                                                              --list-all 输出非常多,建议重定向到文件:

                                                                extundelete /dev/sdb1 --list-all > /tmp/extundelete_list.txt 2>&1

                                                                7、实际使用中的几个坑

                                                                坑 1:路径写错

                                                                  # 错误:传了 OS 路径(含挂载点前缀)
                                                                  extundelete /dev/sdb1 --restore-file /data/backup/mysql/dump_20260608.sql
                                                                  # 命令不会报错,但 RECOVERED_FILES/ 为空


                                                                  # 正确:去掉挂载点前缀,传 FS 根相对路径
                                                                  extundelete /dev/sdb1 --restore-file /backup/mysql/dump_20260608.sql

                                                                  正确做法:先 extundelete --list-all 看完整目录树,再针对性恢复。--list-all 的输出本身就是 FS 相对路径,可以直接复制。

                                                                  坑 2:extundelete 找不到 journal

                                                                    # 报错:can't find ext3 journal

                                                                    这通常是因为 extundelete 版本和内核版本不兼容。CentOS 7 默认 yum 装的 extundelete 0.2.4 在 4.x 内核上正常,在 5.x 内核的 RHEL 8 上可能有问题。出现这种情况,建议源码编译 0.2.4 加上 patch。

                                                                    坑 3:恢复出来的文件 size = 0

                                                                      # 看到的 recovered 文件是 0 字节

                                                                      原因:inode 的 i_size 在某些情况下被覆写了。但 block 还在。可以用 dd 按 block 范围读出来:

                                                                        # 找到 inode 号
                                                                        extundelete /dev/sdb1 --list-all | grep "dump_20260608"
                                                                        # 输出形如:12345  ... /data/backup/mysql/dump_20260608.sql


                                                                        # debugfs 读取(见方案 B)

                                                                        坑 4:恢复出来的文件名变成 inode_NNNN

                                                                        这是正常的,因为目录项丢了。只能按 inode 来对应原文件名。

                                                                        8、验证恢复结果

                                                                          # 1. 数量对比
                                                                          find RECOVERED_FILES/ -type f | wc -l
                                                                          # 跟原始文件数对比


                                                                          # 2. 大文件检查
                                                                          ls -lS RECOVERED_FILES/ | head -20


                                                                          # 3. 文件类型识别
                                                                          file RECOVERED_FILES/*


                                                                          # 4. 抽样校验
                                                                          md5sum RECOVERED_FILES/mysql/dump_20260608.sql
                                                                          # 跟应用方确认的预期值对比

                                                                          八、方案 B:ext4 + debugfs(精细化恢复)

                                                                          debugfs 是 e2fsprogs 自带的 ext 文件系统调试器,比 extundelete 更底层。适用于:

                                                                          • extundelete 恢复失败

                                                                          • 已知 inode 号,按 inode 提取

                                                                          • 文件名丢失,按 inode 找回

                                                                          • 目录结构严重破坏

                                                                          1、进入 debugfs

                                                                            debugfs /dev/sdb1
                                                                            # 进入交互模式

                                                                            注意:默认 debugfs 会以 read-write 模式打开 FS,可能在某些命令上造成修改。务必加 -c 参数以 cat-like 模式打开(只读):

                                                                              debugfs -c /dev/sdb1
                                                                              # 或者显式指定只读
                                                                              debugfs -R "stats" /dev/sdb1 -c

                                                                              -c 模式下,所有写命令都会被拒绝。

                                                                              2、 常用 debugfs 命令

                                                                                # 1. 列出指定 inode 的内容
                                                                                debugfs: stat <12345>


                                                                                # 2. 把 inode 12345 的内容 dump 到 /tmp/recovered_inode_12345
                                                                                debugfs: dump <12345> /tmp/recovered_inode_12345


                                                                                # 3. 列出目录下的所有项(包括 deleted)
                                                                                debugfs: ls -l /data/backup/mysql
                                                                                # 输出形如:
                                                                                #   12345  40700  1000  1000   4096  9-Jun-2026 02:17  .
                                                                                #   12344  40755  1000  1000   4096  9-Jun-2026 02:17  ..
                                                                                #   <12346>  100644  1000  1000   52428800  9-Jun-2026 01:55  dump_20260608.sql
                                                                                #  注意 <12346>,尖括号表示已删除


                                                                                # 4. 列出所有 unlinked 的 inode
                                                                                debugfs: lsdel
                                                                                # 输出每个 unlinked inode 的 owner、size、dtime


                                                                                # 5. 列出所有 inode(很慢)
                                                                                debugfs: ncheck 12346
                                                                                # 把 inode 号映射回路径

                                                                                3、实战:按 inode 恢复

                                                                                  # 1. 先找到对应 inode
                                                                                  ls -i /data/backup/mysql  # 这一步在已经删除的目录上跑不了,只能从备份的元数据推断


                                                                                  # 2. 如果有上一周的 ls 输出,可以从历史里拿 inode
                                                                                  #    假设从聊天记录里找到了:
                                                                                  #    12346 dump_20260608.sql
                                                                                  #    12347 dump_20260607.sql


                                                                                  # 3. 恢复
                                                                                  debugfs -c /dev/sdb1 -R "dump <12346> /mnt/recovery/dump_20260608.sql"


                                                                                  # 4. 退出
                                                                                  debugfs -c /dev/sdb1 -R "quit"

                                                                                  4、 实战:恢复被删目录下的所有文件

                                                                                    # 1. 找到被删目录的 inode
                                                                                    #    注意:此时目录已经被删,ls -id 跑不了
                                                                                    #    只能从 lsdel 输出、备份的元数据、或人工记忆里拿
                                                                                    #    假设我们从某次巡检记录里知道目录 inode 是 10001
                                                                                    echo"假设目录 inode = 10001"


                                                                                    # 2. 用 lsdel + ncheck 映射路径
                                                                                    #    注意:debugfs 的 ncheck 输出的是 FS 根相对路径,
                                                                                    #    不是 OS 挂载点路径,所以输出里没有 /data 前缀
                                                                                    debugfs -c /dev/sdb1 -R "lsdel" | tee /tmp/lsdel.txt
                                                                                    debugfs -c /dev/sdb1 -R "ncheck 12346 12347 12348"
                                                                                    # 输出形如:
                                                                                    # 12346 /backup/mysql/dump_20260608.sql
                                                                                    # 12347 /backup/mysql/dump_20260607.sql


                                                                                    # 3. 批量 dump
                                                                                    for ino in 12346 12347 12348; do
                                                                                        debugfs -c /dev/sdb1 -R "dump <$ino> /mnt/recovery/ino_$ino"
                                                                                    done

                                                                                    5、debugfs 常见问题

                                                                                    Q:lsdel 跑得特别慢?

                                                                                    A:因为要遍历所有 inode。如果 FS 很大(>1T),可能要几十分钟。可以提前用 dumpe2fs -h /dev/sdb1 看 inode 总数:

                                                                                    Q:dump 出来的文件大小不对?

                                                                                    A:可能 i_size 被覆写,但 block 还在。可以用 dd 按 block 范围读:

                                                                                      # 找出文件占用的 block
                                                                                      debugfs -c /dev/sdb1 -R "stat <12346>"
                                                                                      # 看 i_block[0..14]
                                                                                      # 假设直接块是 1000000~1000124
                                                                                      dd if=/dev/sdb1 of=/mnt/recovery/raw_12346 bs=4096 skip=1000000 count=125

                                                                                      Q:文件类型识别错?

                                                                                      A:file 命令识别错的常见原因是前几个 block 被覆写。可以尝试从后向前读。先确认文件占用的 block 范围:

                                                                                        # 假设 inode 12346 的文件占用 block 1000000 ~ 1000255(共 256 块 = 1MB)
                                                                                        # 但只希望读最后 256 块(1MB)作识别用
                                                                                        start=$((1000255 - 256 + 1))   # 1000000
                                                                                        dd if=/dev/sdb1 of=/mnt/recovery/tail_12346 bs=4096 skip=$start count=256

                                                                                        九、方案 C:xfs 文件系统恢复

                                                                                        xfs 不可逆性比 ext4 强,但还是有几条路。

                                                                                        1、 xfs_undelete 工具

                                                                                          # CentOS / RHEL
                                                                                          yum install -y xfs_undelete
                                                                                          # Ubuntu
                                                                                          apt-get install -y xfs_undelete


                                                                                          # 恢复指定目录下的所有文件
                                                                                          xfs_undelete -t /data/backup/mysql /dev/sdb1


                                                                                          # 恢复到指定目录
                                                                                          xfs_undelete -t /data/backup/mysql -o /mnt/recovery /dev/sdb1

                                                                                          注意:

                                                                                          • xfs_undelete 是对目录树扫描,不是从 journal 恢复

                                                                                          • 效果取决于 xfs 内部数据是否被覆盖

                                                                                          • 建议先在快照上跑

                                                                                          2、xfs_db(debugfs 的 xfs 版)

                                                                                            xfs 自带 xfs_db,跟 debugfs 类似但语义不同:


                                                                                            xfs_db /dev/sdb1


                                                                                            # 进入交互模式
                                                                                            xfs_db> help
                                                                                            xfs_db> blockget
                                                                                            xfs_db> blockuse
                                                                                            xfs_db> quit

                                                                                            xfs_db 主要是诊断和修复,恢复功能有限。生产中更推荐用 xfs_undelete。

                                                                                            3、实在救不回来怎么办

                                                                                            xfs 救不回来的时候,最佳选择是:

                                                                                            • 立刻做一次全盘 dd 镜像

                                                                                            • 联系数据恢复公司(专业级服务)

                                                                                            • 评估业务影响,启动备线

                                                                                            • 复盘时考虑切到 ext4 + LVM + 快照

                                                                                            很多公司的备份服务器就是用 ext4 + LVM,每天一个 snapshot 的方式跑,比 xfs 安全得多。

                                                                                            十、方案 D:testdisk + photorec(无差别按块扫)

                                                                                            testdisk 和 photorec 是 sleuthkit 套件里的工具,是“无差别按块扫”的终极手段。优点:

                                                                                            • 不依赖文件系统元数据,纯按 block 内容特征识别

                                                                                            • 支持几乎所有常见文件系统(ext、xfs、ntfs、fat、btrfs、zfs、hfs+ 等)

                                                                                            • 对小文件、文本、图片恢复效果好

                                                                                            缺点:

                                                                                            • 速度慢

                                                                                            • 文件名几乎全部丢失

                                                                                            • 目录结构丢失

                                                                                            • 适合“抢救性扫描”,不适合精确恢复

                                                                                            1、安装

                                                                                              yum install -y testdisk
                                                                                              # 或者
                                                                                              apt-get install -y testdisk

                                                                                              2、使用 testdisk

                                                                                                # 启动(交互式)
                                                                                                testdisk /dev/sdb1


                                                                                                # 一般流程:
                                                                                                # 1. 选择 [Create] 创建日志
                                                                                                # 2. 选择磁盘
                                                                                                # 3. 选择分区表类型(一般 Intel / GPT)
                                                                                                # 4. 选择 [Advanced] 高级
                                                                                                # 5. 选择分区
                                                                                                # 6. 选择 [Undelete] 恢复文件
                                                                                                # 7. 选择目标目录

                                                                                                3、使用 photorec

                                                                                                  # 启动(交互式)
                                                                                                  photorec /dev/sdb1


                                                                                                  # 一般流程:
                                                                                                  # 1. 选择磁盘
                                                                                                  # 2. 选择分区
                                                                                                  # 3. 选择文件系统类型(一般选 Other)
                                                                                                  # 4. 选择恢复目录

                                                                                                  photorec 的恢复结果默认会生成几千个 f0001.jpg、f0002.txt 这样的文件,需要人工归类。

                                                                                                  4、适合 photorec 的场景

                                                                                                  • 文件类型多样(图片、文档、视频)

                                                                                                  • 文件数量少(几十到几百个)

                                                                                                  • FS 元数据完全损坏

                                                                                                  • 最后兜底

                                                                                                  对于备份服务器里几百 GB 的 mysqldump SQL 文件,photorec 不太合适——它会把每个 block 切碎然后按特征匹配,SQL 文件内部有大量重复模式,容易被切碎。

                                                                                                  十一、方案 E:lsof 抢救未关闭文件(成本最低的恢复)

                                                                                                  前面 5.4 节已经提过,这里展开讲。这是最容易成功、风险最低、速度最快的恢复方式。

                                                                                                  1、找到 deleted but still open 的文件

                                                                                                    # 列出所有 deleted 文件
                                                                                                    lsof | grep deleted

                                                                                                    输出示例(注意:不同 lsof 版本会显示或不显示 TID 列):

                                                                                                      mysqld  1234  1234  mysql  8u  REG  253,1  104857600  12345  /data/backup/mysql/dump_20260608.sql (deleted)
                                                                                                      rsync   5678  5678  root   3r  REG  253,1   52428800  12346  /data/backup/app/app-20260608.tar.gz (deleted)

                                                                                                      其中:

                                                                                                      • 第 5 列:FD(含访问模式字母,如 8u 表示 FD=8 的 u=read+write)

                                                                                                      • 第 7 列:文件大小(字节)

                                                                                                      • 第 8 列:inode 号

                                                                                                      • 第 9 列:原始路径 + (deleted) 标记

                                                                                                      为什么推荐 lsof -F 解析:手工数列数(awk $2、awk $4)在 TID 列存在与否的两种 lsof 输出下表现不同,新人最容易在这里翻车。下面 11.3 的脚本用 -F 规避这个问题。

                                                                                                      2、恢复单个文件

                                                                                                        # 把 1234 进程的第 8 个 fd 复制出来
                                                                                                        cp /proc/1234/fd/8 /tmp/recovered_dump_20260608.sql


                                                                                                        # 验证大小
                                                                                                        ls -la /tmp/recovered_dump_20260608.sql
                                                                                                        # 应该跟 deleted 文件的大小一致

                                                                                                        3、批量恢复脚本

                                                                                                          #!/bin/bash
                                                                                                          # recover_deleted.sh
                                                                                                          # 恢复所有 deleted 但被进程持有的文件
                                                                                                          # 用法:./recover_deleted.sh /mnt/recovery
                                                                                                          #
                                                                                                          # 说明:使用 lsof -F 输出(每行一个键值对)规避 awk 字段数随版本
                                                                                                          #      变化(TID 列有时存在有时缺失)的问题。
                                                                                                          #      p=<PID>  f=<FD>  n=<NAME> 是我们关心的三种字段
                                                                                                          OUT_DIR="${1:-/tmp/recovered}"
                                                                                                          mkdir -p "$OUT_DIR"
                                                                                                          # 1. 抓出所有 deleted 文件
                                                                                                          #    -F pfn:  仅输出 p/f/n 三个字段
                                                                                                          #    2>/dev/null: 忽略 lsof 因权限不足输出的部分 warning
                                                                                                          lsof -F pfn 2>/dev/null > /tmp/lsof_f.txt
                                                                                                          # 2. 解析:把 p/f/n 三行组合成一条记录
                                                                                                          awk -v OUT_DIR="$OUT_DIR"'
                                                                                                          /^p/ {
                                                                                                              pid = substr($0, 2)
                                                                                                              fd = ""
                                                                                                              name = ""
                                                                                                              deleted = 0
                                                                                                              next
                                                                                                          }
                                                                                                          /^f/ {
                                                                                                              fd = substr($0, 2)
                                                                                                              next
                                                                                                          }
                                                                                                          /^n/ {
                                                                                                              name = substr($0, 2)
                                                                                                              if (name ~ / \(deleted\)$/) {
                                                                                                                  deleted = 1
                                                                                                                  # 去掉 " (deleted)" 标记
                                                                                                                  name = substr(name, 1, length(name) - 10)
                                                                                                              }
                                                                                                          }
                                                                                                          {
                                                                                                              if (deleted && pid != "" && fd != "" && name != "") {
                                                                                                                  # 构造目标文件名:pid_fd_原路径
                                                                                                                  safe = name
                                                                                                                  gsub(/\//, "_", safe)
                                                                                                                  target = OUT_DIR "/" pid "_" fd "_" safe
                                                                                                                  # 调用 cp 复制
                                                                                                                  cmd = "cp -a /proc/" pid "/fd/" fd " \"" target "\" 2>/dev/null"
                                                                                                                  if (system(cmd) == 0) {
                                                                                                                      print "RECOVERED: " name " -> " target
                                                                                                                  }
                                                                                                                  deleted = 0
                                                                                                              }
                                                                                                          }
                                                                                                          ' /tmp/lsof_f.txt
                                                                                                          rm -f /tmp/lsof_f.txt

                                                                                                          使用:

                                                                                                            chmod +x recover_deleted.sh
                                                                                                            ./recover_deleted.sh /mnt/recovery

                                                                                                            风险提示:

                                                                                                            • 这个脚本会触发对 /proc/$pid/fd/$fd 的访问,可能短暂占用 fd

                                                                                                            • 对每个进程有权限要求,root 才能访问其他用户的 fd

                                                                                                            • 不要在生产业务机上跑,先演练

                                                                                                            • lsof -F 的输出里 NAME 可能包含空格,脚本里的 cp 已加引号兜底

                                                                                                            • 如果 lsof 版本过老不支持 -F,可改用 lsof +c 0 -P -n 加手工解析

                                                                                                            4、实战中的几个问题

                                                                                                            Q:进程是 root 启动的,但文件实际属于 mysql 用户,能恢复吗?

                                                                                                            A:能。内核只校验调用进程的权限,root 可以访问任何 fd。

                                                                                                            Q:lsof 输出里有 deleted 文件,但 /proc/$pid/fd 路径不存在?

                                                                                                            A:可能进程已经退出了。lsof 是当时的状态,进程退出后 /proc/$pid 整个消失。

                                                                                                            Q:lsof 输出里有 N 个 deleted,但我用上面的脚本只恢复了 M 个(M < N)?

                                                                                                            A:可能某些 fd 已经被进程关闭但还没被内核回收。增加睡眠重试,或者直接按 inode 扫。

                                                                                                            十二、方案 F:LVM / ZFS / btrfs 快照回滚(成功率最高)

                                                                                                            如果误删发生在支持快照的文件系统上,且近期有 snapshot,恢复成功率是 100%。这一节讲三类系统的快照操作。

                                                                                                            1、LVM 快照

                                                                                                              # 1. 创建快照
                                                                                                              lvcreate -s -L 20G -n data_snap_recovery /dev/vg0/data


                                                                                                              # 2. 挂载(ext4 快照直接挂载即可;xfs 在多 LV 同 VG 场景下可能要加 nouuid)
                                                                                                              mount /dev/vg0/data_snap_recovery /mnt/snap
                                                                                                              # 如果快照里是 xfs 且 VG 内出现 UUID 冲突,可加 -o nouuid:
                                                                                                              # mount -o ro,nouuid /dev/vg0/data_snap_recovery /mnt/snap


                                                                                                              # 3. 复制数据
                                                                                                              cp -a /mnt/snap/data/backup/mysql/dump_20260608.sql /mnt/recovery/


                                                                                                              # 4. 验证
                                                                                                              diff /mnt/recovery/dump_20260608.sql /data/backup/mysql/dump_20260608.sql


                                                                                                              # 5. 卸载 + 删除快照
                                                                                                              umount /mnt/snap
                                                                                                              lvremove -f /dev/vg0/data_snap_recovery

                                                                                                              LVM 快照的局限:

                                                                                                              • 快照空间耗尽后会失效

                                                                                                              • 频繁写操作的 LV 不建议做大量快照

                                                                                                              • 快照本身影响写性能

                                                                                                              2、btrfs 快照

                                                                                                                # 1. 列出已有快照
                                                                                                                btrfs subvolume list /data


                                                                                                                # 2. 创建新快照
                                                                                                                btrfs subvolume snapshot /data /data/.snapshots/$(date +%F_%H%M%S)


                                                                                                                # 3. 恢复:把快照里的文件直接 cp 出来
                                                                                                                #    btrfs 快照是“可写快照”,可以挂载后操作
                                                                                                                mkdir -p /mnt/snap
                                                                                                                mount -o subvol=.snapshots/2026-06-09_021800 /dev/sdb1 /mnt/snap
                                                                                                                ls /mnt/snap/data/backup/mysql/


                                                                                                                # 4. 也可以用 btrfs restore 把整个 subvolume 恢复到另一个位置
                                                                                                                btrfs restore /dev/sdb1 /mnt/recovery_btrfs

                                                                                                                btrfs 的优势:

                                                                                                                • 快照成本极低(COW)

                                                                                                                • 快照嵌套快照

                                                                                                                • 可以做增量备份(btrfs send/receive)

                                                                                                                3、zfs 快照

                                                                                                                  # 1. 列出已有快照
                                                                                                                  zfs list -t snapshot | grep data


                                                                                                                  # 2. 创建新快照
                                                                                                                  zfs snapshot data@recover_$(date +%F_%H%M%S)


                                                                                                                  # 3. 访问快照
                                                                                                                  ls /data/.zfs/snapshot/recover_2026-06-09_021800/data/backup/mysql/


                                                                                                                  # 4. 复制数据
                                                                                                                  cp -a /data/.zfs/snapshot/recover_2026-06-09_021800/data/backup/mysql/* /mnt/recovery/


                                                                                                                  # 5. 删除快照
                                                                                                                  zfs destroy data@recover_2026-06-09_021800

                                                                                                                  zfs 的优势:

                                                                                                                  • 快照是文件系统层的一等公民

                                                                                                                  • zfs rollback 可以把整个 FS 回到某个时刻

                                                                                                                  • 跨主机 zfs send/receive 是工业级备份

                                                                                                                  4、云盘快照

                                                                                                                  云上的块存储(EBS、CBS、Disk)都支持快照:

                                                                                                                    # AWS CLI
                                                                                                                    aws ec2 create-snapshot \
                                                                                                                        --volume-id vol-0abc1234 \
                                                                                                                        --description "pre-recovery-$(date +%F)"


                                                                                                                    # 阿里云 CLI
                                                                                                                    aliyun ecs CreateSnapshot \
                                                                                                                        --DiskId d-abc1234 \
                                                                                                                        --Description "pre-recovery-$(date +%F)"


                                                                                                                    # 腾讯云 CLI
                                                                                                                    tccli cbs CreateSnapshot \
                                                                                                                        --DiskId disk-abc1234

                                                                                                                    云盘快照的特点:

                                                                                                                    • 底层是 COW,对原盘 IO 影响小

                                                                                                                    • 快照创建通常秒级

                                                                                                                    • 跨可用区复制功能可以做异地容灾

                                                                                                                    • 收费按快照大小 + 保留时间

                                                                                                                    我们的生产环境在事件后第 3 周统一做了改造:所有 ext4 卷都迁到 LVM,每天一个 snapshot,保留 14 天。

                                                                                                                    十三、实战时间线:一次完整的误删恢复全过程

                                                                                                                    把前面讲的工具串起来,按真实事故的时间线给一个完整流程。假设场景:

                                                                                                                    • 时间:2026-06-09 凌晨 02:17

                                                                                                                    • 主机:backup-01.example.com,CentOS 7.9

                                                                                                                    • 误删目录:/data/backup/mysql

                                                                                                                    • 文件系统:ext4

                                                                                                                    • 误删原因:脚本里 find ... -exec rm -rf {} \; 沿软链展开

                                                                                                                    • 误删对象:约 200 个 mysqldump 文件,共 380GB

                                                                                                                    • 备份:本地 LVM snapshot(昨天 02:00 整)

                                                                                                                    1、02:17 - 02:25:响应与止血

                                                                                                                      # 1. ssh 上去
                                                                                                                      ssh backup-01


                                                                                                                      # 2. 立刻停 cron
                                                                                                                      sudo systemctl stop crond
                                                                                                                      sudo systemctl mask crond


                                                                                                                      # 3. 停 mysqldump 任务(如果还在跑)
                                                                                                                      ps -ef | grep -E "mysqldump|cleanup" | grep -v grep
                                                                                                                      # 假设 PID 5678 是 cleanup.sh
                                                                                                                      sudo kill -STOP 5678  # 暂停进程,先不杀
                                                                                                                      # 也可以直接 kill,但要先把后面 lsof 信息抓了


                                                                                                                      # 4. 抓现场
                                                                                                                      sudo sh -c 'date; uptime; df -h; df -i; free -h; mount > /tmp/mount.txt; lsof > /tmp/lsof.txt; lsblk > /tmp/lsblk.txt; blkid > /tmp/blkid.txt' > /tmp/initial_state.txt 2>&1


                                                                                                                      # 5. 立即把盘设为只读
                                                                                                                      sudo mount -o remount,ro /data
                                                                                                                      # 报 EBUSY,看下谁在写
                                                                                                                      sudo fuser -vm /data
                                                                                                                      # 假设是 mysqldump 进程
                                                                                                                      sudo kill -STOP $(pidof mysqldump)
                                                                                                                      sudo mount -o remount,ro /data
                                                                                                                      # 这次成功了


                                                                                                                      # 6. 验证
                                                                                                                      mount | grep /data

                                                                                                                      2、02:25 - 02:30:LVM 快照

                                                                                                                        # 1. 查看 VG 空间
                                                                                                                        sudo vgdisplay vg0 | grep -E "VG Name|Free"
                                                                                                                        # 假设 Free: 50G


                                                                                                                        # 2. 创建快照
                                                                                                                        sudo lvcreate -s -L 30G -n data_snap_recovery /dev/vg0/data


                                                                                                                        # 3. 挂载快照
                                                                                                                        sudo mkdir -p /mnt/snap
                                                                                                                        sudo mount -o ro /dev/vg0/data_snap_recovery /mnt/snap


                                                                                                                        # 4. 验证快照可读
                                                                                                                        sudo ls -la /mnt/snap/data/backup/mysql/ | head -20
                                                                                                                        # 看到 380GB 的文件都在

                                                                                                                        3、02:30 - 02:50:lsof 抢救

                                                                                                                          # 1. 抓 deleted
                                                                                                                          sudo lsof | grep deleted | grep backup > /tmp/deleted_files.txt
                                                                                                                          # 看到约 30 个文件被 mysqldump 进程持有


                                                                                                                          # 2. 准备恢复目录
                                                                                                                          sudo mkdir -p /mnt/recovery


                                                                                                                          # 3. 批量恢复
                                                                                                                          sudo /opt/scripts/recover_deleted.sh /mnt/recovery
                                                                                                                          # 恢复出 30 个文件,约 80GB


                                                                                                                          # 4. 验证
                                                                                                                          sudo ls -la /mnt/recovery/ | head -20

                                                                                                                          4、 02:50 - 04:30:extundelete 全量恢复

                                                                                                                            # 1. 准备恢复目标盘
                                                                                                                            sudo mkdir -p /mnt/recovery2
                                                                                                                            sudo mount /dev/sdc1 /mnt/recovery2  # 假设 sdc1 是 1TB 独立盘


                                                                                                                            # 2. 卸载原盘(避免误操作)
                                                                                                                            sudo umount /mnt/snap
                                                                                                                            # 保留 snapshot lv 不动


                                                                                                                            # 3. 跑 extundelete
                                                                                                                            cd /mnt/recovery2
                                                                                                                            sudo extundelete /dev/sdb1 --restore-all --before "2026-06-09 02:00:00"
                                                                                                                            # 这一步大约 1 小时


                                                                                                                            # 4. 验证
                                                                                                                            ls -la /mnt/recovery2/RECOVERED_FILES/ | head
                                                                                                                            ls -la /mnt/recovery2/RECOVERED_FILES/data/backup/mysql/ | head

                                                                                                                            5、 04:30 - 06:00:业务校验

                                                                                                                              # 1. 把恢复出来的 mysqldump 文件加载到测试库
                                                                                                                              for f in /mnt/recovery2/RECOVERED_FILES/data/backup/mysql/dump_*.sql; do
                                                                                                                                  # 抽样前 100 行
                                                                                                                                  head -100 "$f" | mysql -u root -p <db_test>
                                                                                                                                  if [ $? -ne 0 ]; then
                                                                                                                                      echo"BROKEN: $f"
                                                                                                                                  fi
                                                                                                                              done


                                                                                                                              # 2. 校验关键文件大小
                                                                                                                              find /mnt/recovery2/RECOVERED_FILES/data/backup/mysql/ -type f -size -100k -ls
                                                                                                                              # 找出 0 字节和异常小的文件


                                                                                                                              # 3. 对比 mysqldump 的预期行数
                                                                                                                              mysql -u root -p -e "SELECT * FROM <db>.tables" | wc -l
                                                                                                                              # 跟某个 dump 文件的 INSERT 行数对比

                                                                                                                              6、06:00 - 08:00:补传 + 业务验证

                                                                                                                                # 1. 把恢复出来的文件推回业务机
                                                                                                                                rsync -avz /mnt/recovery2/RECOVERED_FILES/data/backup/mysql/ \
                                                                                                                                    backup-target:/data/backup/mysql/


                                                                                                                                # 2. 让应用方做端到端校验
                                                                                                                                #    业务方反馈:恢复出来的文件能正常恢复
                                                                                                                                #    缺失的 12 个文件从异地机房拉


                                                                                                                                # 3. 通知变更完成

                                                                                                                                7、复盘

                                                                                                                                • 完整复盘会安排在第二天上午

                                                                                                                                • 主因:旧脚本没做 Code Review

                                                                                                                                • 根因:缺监控 + 缺审计 + 缺流程

                                                                                                                                • 改进:所有清理脚本必须 Code Review、必须 dry-run

                                                                                                                                十四、风险点与不可恢复场景

                                                                                                                                下面这些场景,恢复工具都救不回来。提前认清边界。

                                                                                                                                1、块已被覆写

                                                                                                                                  # 查看空闲块水位
                                                                                                                                  dumpe2fs /dev/sdb1 | grep -E "Free blocks|Free inodes"

                                                                                                                                  Free blocks 越小,说明可用空间越紧张,覆写风险越高。如果空闲块数已经很少,新数据写入会很快把误删文件覆盖。

                                                                                                                                  2、文件系统已重格式化

                                                                                                                                    # 如果有人在修复过程中 mkfs 了
                                                                                                                                    mkfs.ext4 /dev/sdb1
                                                                                                                                    # 那么元数据被重置,恢复工具扫到的是全新的 FS 结构

                                                                                                                                    遇到这种情况,extundelete 几乎无解,唯一的希望是 photorec 按块扫。

                                                                                                                                    3、 磁盘出现坏道

                                                                                                                                      # smartctl 报告坏道
                                                                                                                                      smartctl -a /dev/sdb | grep -E "Reallocated|Pending|Uncorrectable"

                                                                                                                                      坏道上的数据物理上读不出来,恢复出来的文件会有零字节块或随机内容。

                                                                                                                                      4、文件被部分覆写

                                                                                                                                        # 假设 dump_20260608.sql 被覆写了头部 1MB
                                                                                                                                        # 恢复出来的文件从原 1MB 位置开始,前面 1MB 是新数据

                                                                                                                                        这种文件通常校验失败,需要从其他渠道(远端备份、其它机器)补全。

                                                                                                                                        5、 文件名彻底丢失

                                                                                                                                        extundelete 恢复出来的文件名是 inode_12345.dump 这种,需要靠 inode 推回去。如果连 inode 都没记(应用层没打 tag),就只能按文件大小、修改时间、文件类型人工归类。

                                                                                                                                        6、加密文件系统

                                                                                                                                        LUKS 加密的 FS,恢复时需要解锁。如果密钥丢了,数据不可救。生产环境建议:

                                                                                                                                        • 密钥用 key escrow(Key Custodian)机制

                                                                                                                                        • 不要只放在一个人的密码本里

                                                                                                                                        • LUKS 头要做异地备份

                                                                                                                                        7、写入放大

                                                                                                                                        如果误删的是数据库文件,且 DB 仍在跑,DB 的 checkpoint、redolog 写入会持续覆写空闲块。这种情况下,越早停 DB 越好。

                                                                                                                                        十五、防误删:制度、命令、备份、监控

                                                                                                                                        讲完恢复,必须讲防御。下面是我们在事件后落地的一套防御体系。

                                                                                                                                        1、制度层

                                                                                                                                        1)清理脚本 Code Review 制度

                                                                                                                                        任何 find ... -exec rm、rm -rf、shred 等破坏性命令,必须:

                                                                                                                                        • 在 git 仓库里

                                                                                                                                        • 经过至少 1 人 review

                                                                                                                                        • 必须有 dry-run 选项

                                                                                                                                        • 必须有过期时间判断(不能只判断目录名)

                                                                                                                                        2)清理脚本必须有 dry-run

                                                                                                                                          #!/bin/bash
                                                                                                                                          # cleanup.sh
                                                                                                                                          # 清理 30 天前的旧备份
                                                                                                                                          # 必须支持 DRY_RUN 环境变量
                                                                                                                                          DRY_RUN=${DRY_RUN:-1}# 默认 dry-run
                                                                                                                                          LOG_FILE=${LOG_FILE:-/var/log/cleanup.log}
                                                                                                                                          log() {
                                                                                                                                              echo"$(date '+%F %T') $*" | tee -a "$LOG_FILE"
                                                                                                                                          }
                                                                                                                                          if [ "$DRY_RUN" = "1" ]; then
                                                                                                                                              log"DRY-RUN: would remove the following:"
                                                                                                                                              find /data/backup/mysql -type f -mtime +30 -print
                                                                                                                                              exit 0
                                                                                                                                          fi
                                                                                                                                          # 真正的清理逻辑
                                                                                                                                          log"REAL-CLEAN: starting"
                                                                                                                                          find /data/backup/mysql -type f -mtime +30 -print -delete
                                                                                                                                          log"REAL-CLEAN: done"

                                                                                                                                          使用方式:

                                                                                                                                            # 1. 演练
                                                                                                                                            DRY_RUN=1 LOG_FILE=/var/log/cleanup_test.log /opt/scripts/cleanup.sh


                                                                                                                                            # 2. 确认无误后真实运行
                                                                                                                                            DRY_RUN=0 LOG_FILE=/var/log/cleanup_real.log /opt/scripts/cleanup.sh

                                                                                                                                            3) 强制二次确认

                                                                                                                                              #!/bin/bash
                                                                                                                                              # rm_with_confirm.sh
                                                                                                                                              # 包装 rm,强制二次确认
                                                                                                                                              TARGET="$1"
                                                                                                                                              if [ -z "$TARGET" ]; then
                                                                                                                                                  echo"Usage: $0 <path>"
                                                                                                                                                  exit 1
                                                                                                                                              fi


                                                                                                                                              # 1. 提示
                                                                                                                                              echo"==== WARNING ===="
                                                                                                                                              echo"About to remove: $TARGET"
                                                                                                                                              echo"Resolved path: $(readlink -f "$TARGET")"
                                                                                                                                              echo"Disk usage: $(du -sh "$TARGET" 2>/dev/null | awk '{print $1}')"
                                                                                                                                              echo"==== END ===="


                                                                                                                                              # 2. 强制输入 YES
                                                                                                                                              read -p "Type 'YES' to continue: " confirm
                                                                                                                                              if [ "$confirm" != "YES" ]; then
                                                                                                                                                  echo"Aborted."
                                                                                                                                                  exit 1
                                                                                                                                              fi


                                                                                                                                              # 3. 删除
                                                                                                                                              rm -rf -- "$TARGET"
                                                                                                                                              echo"Removed."

                                                                                                                                              4) 审计日志

                                                                                                                                                # 在 /etc/profile 里加 alias
                                                                                                                                                alias rm='/usr/local/bin/audit_rm.sh'

                                                                                                                                                  # /usr/local/bin/audit_rm.sh
                                                                                                                                                  #!/bin/bash
                                                                                                                                                  LOG_FILE=/var/log/rm_audit.log
                                                                                                                                                  USER=$(whoami)
                                                                                                                                                  PWD_PATH=$(pwd)
                                                                                                                                                  TIMESTAMP=$(date '+%F %T')


                                                                                                                                                  # 记录命令
                                                                                                                                                  echo "$TIMESTAMP user=$USER pwd=$PWD_PATH cmd=rm args=$*" >> "$LOG_FILE"


                                                                                                                                                  # 调用真实 rm
                                                                                                                                                  exec /bin/rm "$@"

                                                                                                                                                  2、命令层

                                                                                                                                                  1)替换 rm

                                                                                                                                                  trash-cli 是一个跨平台的回收站替代品:

                                                                                                                                                    # Fedora / RHEL 8 + EPEL
                                                                                                                                                    yum install -y trash-cli


                                                                                                                                                    # Ubuntu / Debian
                                                                                                                                                    apt-get install -y trash-cli


                                                                                                                                                    # 仓库里没有时,pip 兜底
                                                                                                                                                    pip3 install trash-cli


                                                                                                                                                    # 使用
                                                                                                                                                    trash-put foo.txt           # 移动到回收站
                                                                                                                                                    trash-list                  # 列出回收站
                                                                                                                                                    trash-restore foo.txt       # 恢复
                                                                                                                                                    trash-empty                 # 清空回收站


                                                                                                                                                    # 替换 alias
                                                                                                                                                    alias rm='trash-put'

                                                                                                                                                    trash-cli 的坑:

                                                                                                                                                    • 跨主机不通用

                                                                                                                                                    • 跨用户不通用

                                                                                                                                                    • 习惯 rm 的同学要过渡期

                                                                                                                                                    2) safe-rm

                                                                                                                                                      # 安装
                                                                                                                                                      yum install -y safe-rm


                                                                                                                                                      # 配置黑名单
                                                                                                                                                      cat /etc/safe-rm.conf
                                                                                                                                                      /
                                                                                                                                                      /etc
                                                                                                                                                      /usr
                                                                                                                                                      /var
                                                                                                                                                      /data/backup  # 把重要目录加进去

                                                                                                                                                      safe-rm 是一个 rm 的 wrapper,会拦截对黑名单路径的删除。

                                                                                                                                                      3)慎用 find -exec rm

                                                                                                                                                      find ... -exec rm -rf {} \; 配合软链非常危险。find 默认不跟软链,但 -L 会跟。

                                                                                                                                                        # 安全做法:先 print 看一下
                                                                                                                                                        find /data/backup/mysql -type f -mtime +30 -print


                                                                                                                                                        # 确认无误后 -delete
                                                                                                                                                        find /data/backup/mysql -type f -mtime +30 -delete

                                                                                                                                                        find -delete 跟 find -exec rm 的区别:

                                                                                                                                                        • -delete 是 find 内置的,更安全(不会执行任意命令)

                                                                                                                                                        • -exec rm 是执行外部命令,软链攻击面更大

                                                                                                                                                        4) 通配符小心

                                                                                                                                                          # 危险写法
                                                                                                                                                          rm -rf /data/backup/*


                                                                                                                                                          # 如果 /data/backup 是空目录,而当前 shell 把 * 展开成别的,就出事了
                                                                                                                                                          # 安全写法
                                                                                                                                                          rm -rf /data/backup/*.sql  # 明确匹配


                                                                                                                                                          # 更安全:先 ls
                                                                                                                                                          ls /data/backup/*.sql
                                                                                                                                                          rm -rf /data/backup/*.sql

                                                                                                                                                          3、备份层

                                                                                                                                                          1) 3-2-1 备份策略

                                                                                                                                                          • 3 份副本

                                                                                                                                                          • 2 种介质

                                                                                                                                                          • 1 份异地

                                                                                                                                                          对于我们这种备份服务器,3 份副本的实现:

                                                                                                                                                          • 本地 LVM snapshot

                                                                                                                                                          • 异地 rsync

                                                                                                                                                          • 对象存储(S3/OSS/COS)

                                                                                                                                                          2)自动 snapshot 脚本

                                                                                                                                                            #!/bin/bash
                                                                                                                                                            # daily_snapshot.sh
                                                                                                                                                            # 每天凌晨 2 点创建 LVM snapshot,保留 7 天
                                                                                                                                                            VG_NAME=vg0
                                                                                                                                                            LV_NAME=data
                                                                                                                                                            SNAP_SIZE=20G
                                                                                                                                                            KEEP_DAYS=7
                                                                                                                                                            SNAP_PREFIX=data_daily
                                                                                                                                                            # 1. 创建快照
                                                                                                                                                            DATE=$(date +%Y%m%d)
                                                                                                                                                            SNAP_NAME="${SNAP_PREFIX}_${DATE}"
                                                                                                                                                            lvcreate -s -L ${SNAP_SIZE} -n ${SNAP_NAME} /dev/${VG_NAME}/${LV_NAME} 2>&1 | logger -t snapshot
                                                                                                                                                            if [ $? -ne 0 ]; then
                                                                                                                                                                logger -t snapshot "ERROR: failed to create snapshot ${SNAP_NAME}"
                                                                                                                                                                exit 1
                                                                                                                                                            fi
                                                                                                                                                            # 2. 删除过期快照
                                                                                                                                                            for old_snap in $(lvs --noheadings -o lv_name ${VG_NAME} | grep "${SNAP_PREFIX}_"); do
                                                                                                                                                                snap_date=$(echo$old_snap | sed "s/${SNAP_PREFIX}_//")
                                                                                                                                                                if [ -n "$snap_date" ]; then
                                                                                                                                                                    snap_ts=$(date -d "$snap_date" +%s 2>/dev/null)
                                                                                                                                                                    if [ $? -eq 0 ]; then
                                                                                                                                                                        age_days=$(( ($(date +%s) - snap_ts) / 86400 ))
                                                                                                                                                                        if [ $age_days -gt $KEEP_DAYS ]; then
                                                                                                                                                                            lvremove -f /dev/${VG_NAME}/${old_snap} 2>&1 | logger -t snapshot
                                                                                                                                                                        fi
                                                                                                                                                                    fi
                                                                                                                                                                fi
                                                                                                                                                            done

                                                                                                                                                            3)异地备份

                                                                                                                                                              # rsync over ssh
                                                                                                                                                              rsync -avz --delete \
                                                                                                                                                                  /data/backup/mysql/ \
                                                                                                                                                                  backup@backup-dr.example.com:/data/backup/mysql/


                                                                                                                                                              # 用对象存储
                                                                                                                                                              aws s3 sync /data/backup/mysql/ s3://my-bucket/mysql/ --delete

                                                                                                                                                              4、 监控层

                                                                                                                                                              1)监控重要目录的存在性

                                                                                                                                                                #!/bin/bash
                                                                                                                                                                # /opt/mon/check_backup.sh
                                                                                                                                                                # 检查关键目录是否存在


                                                                                                                                                                CRITICAL_DIRS=(
                                                                                                                                                                    "/data/backup/mysql"
                                                                                                                                                                    "/data/backup/app"
                                                                                                                                                                    "/data/backup/logs"
                                                                                                                                                                )


                                                                                                                                                                for dir in"${CRITICAL_DIRS[@]}"do
                                                                                                                                                                    if [ ! -d "$dir" ]; then
                                                                                                                                                                        # 触发告警
                                                                                                                                                                        curl -X POST "https://alert.example.com/alert" \
                                                                                                                                                                            -d "host=$(hostname)&dir=$dir&msg=directory missing"
                                                                                                                                                                    fi
                                                                                                                                                                done

                                                                                                                                                                2)监控文件数量

                                                                                                                                                                  # 写一个 prometheus textfile collector
                                                                                                                                                                  CRITICAL_DIRS=(
                                                                                                                                                                      "/data/backup/mysql"
                                                                                                                                                                  )


                                                                                                                                                                  for dir in "${CRITICAL_DIRS[@]}"do
                                                                                                                                                                      count=$(find "$dir" -type f 2>/dev/null | wc -l)
                                                                                                                                                                      echo "backup_file_count{dir=\"$dir\"} $count" >> /var/lib/node_exporter/textfile/backup.prom
                                                                                                                                                                  done

                                                                                                                                                                  3)监控清理脚本的运行

                                                                                                                                                                    # 在 cleanup.sh 里发送心跳
                                                                                                                                                                    logger -t cleanup "started with DRY_RUN=$DRY_RUN"
                                                                                                                                                                    # 同时把日志发到集中日志系统(ELK / Loki)
                                                                                                                                                                    curl -X POST "https://logs.example.com/collect" \
                                                                                                                                                                        -"{\"job\":\"cleanup\",\"status\":\"started\",\"host\":\"$(hostname)\"}"

                                                                                                                                                                    4)告警:删除事件

                                                                                                                                                                      #!/bin/bash
                                                                                                                                                                      # /opt/mon/audit_rm_watch.sh
                                                                                                                                                                      # 实时监控 /var/log/rm_audit.log,发现危险操作立即告警


                                                                                                                                                                      tail -F /var/log/rm_audit.log | while read -r line; do
                                                                                                                                                                          # 检测 -rf、/、* 这类危险模式
                                                                                                                                                                          if echo "$line" | grep -qE "(rm -rf|/ | rm -rf)"then
                                                                                                                                                                              curl -X POST "https://alert.example.com/alert" \
                                                                                                                                                                                  -d "host=$(hostname)&line=$line&severity=high"
                                                                                                                                                                          fi
                                                                                                                                                                      done

                                                                                                                                                                      5、配置层

                                                                                                                                                                      1)/etc/skel/.bashrc 加 alias

                                                                                                                                                                        # /etc/skel/.bashrc
                                                                                                                                                                        alias rm='echo "Use trash-put or /opt/scripts/safe_rm.sh"; false'
                                                                                                                                                                        alias mv='mv -i'
                                                                                                                                                                        alias cp='cp -i'

                                                                                                                                                                        新建用户会自动继承。生产环境谨慎给 root 用。

                                                                                                                                                                        2) /etc/profile.d/rm_alias.sh

                                                                                                                                                                          # 强制所有用户加载
                                                                                                                                                                          cat > /etc/profile.d/rm_alias.sh << 'EOF'
                                                                                                                                                                          alias rm='/usr/local/bin/audit_rm.sh'
                                                                                                                                                                          alias cp='cp -i'
                                                                                                                                                                          alias mv='mv -i'
                                                                                                                                                                          EOF
                                                                                                                                                                          chmod +x /etc/profile.d/rm_alias.sh

                                                                                                                                                                          十六、替代 rm 的安全删除方案

                                                                                                                                                                          如果你的团队能接受“换个命令”,下面是几个更安全的替代品。

                                                                                                                                                                          1、 trash-cli

                                                                                                                                                                          前面讲过,跨平台,使用简单。缺点是不解决软链问题。

                                                                                                                                                                          2、 rmtrash

                                                                                                                                                                            # macOS 用户熟悉的
                                                                                                                                                                            brew install rmtrash

                                                                                                                                                                            3、 移动到隔离目录

                                                                                                                                                                              #!/bin/bash
                                                                                                                                                                              # /opt/bin/saferm
                                                                                                                                                                              # 移动到隔离目录,30 天后自动清理


                                                                                                                                                                              QUARANTINE=/var/spool/quarantine
                                                                                                                                                                              RETENTION_DAYS=30


                                                                                                                                                                              mkdir -p "$QUARANTINE"


                                                                                                                                                                              for target in"$@"do
                                                                                                                                                                                  real=$(readlink -f "$target")
                                                                                                                                                                                  ts=$(date +%Y%m%d_%H%M%S)
                                                                                                                                                                                  safe=$(echo"$real" | tr '/''_')
                                                                                                                                                                                  mv "$target""$QUARANTINE/${ts}_${safe}"
                                                                                                                                                                              done


                                                                                                                                                                              # 清理过期
                                                                                                                                                                              find "$QUARANTINE" -mtime +$RETENTION_DAYS -delete

                                                                                                                                                                              4、 用 Git / Mercurial 做版本控制

                                                                                                                                                                              对于配置文件、关键脚本:

                                                                                                                                                                                # 初始化
                                                                                                                                                                                cd /opt/scripts
                                                                                                                                                                                git init
                                                                                                                                                                                git add .
                                                                                                                                                                                git commit -m "initial"


                                                                                                                                                                                # 每次改之前
                                                                                                                                                                                git commit -am "before change rm logic"


                                                                                                                                                                                # 改错了
                                                                                                                                                                                git checkout HEAD -- cleanup.sh

                                                                                                                                                                                5、用 Git LFS / DVC 做大数据版本控制

                                                                                                                                                                                对于数据文件,可以用 DVC(Data Version Control)做轻量级版本管理。

                                                                                                                                                                                十七、复盘总结与给初中级运维的建议

                                                                                                                                                                                事故复盘是一线运维的“软基建”,但很多团队不做或者走形式。我们这次复盘真正落地的有几条:

                                                                                                                                                                                1、 复盘要点

                                                                                                                                                                                • 主因:清理脚本没有 Code Review,没有 dry-run,配合软链造成扩删

                                                                                                                                                                                • 根因:

                                                                                                                                                                                • 缺流程:清理脚本被当作“杂事”

                                                                                                                                                                                • 缺监控:误删到告警之间隔了 17 分钟

                                                                                                                                                                                • 缺审计:rm 操作没有二次确认

                                                                                                                                                                                • 缺演练:脚本变更没在预发演练

                                                                                                                                                                                • 影响:约 380GB 数据不可见,异地备份还有,但延迟 1 天恢复

                                                                                                                                                                                2、行动项

                                                                                                                                                                                • 所有清理脚本纳入 Git 管理 + Code Review

                                                                                                                                                                                • 部署 audit_rm 全局包装

                                                                                                                                                                                • 关键目录加文件数量监控 + 异常告警

                                                                                                                                                                                • 备份服务器从 ext4 迁到 ext4 + LVM,启用 daily snapshot

                                                                                                                                                                                • 异地备份加密传输 + 完整性校验

                                                                                                                                                                                3、给初中级运维的几条建议

                                                                                                                                                                                • 任何 rm -rf 之前先 ls 一遍,哪怕你自己写的脚本

                                                                                                                                                                                • find -delete 优于 find -exec rm,是更安全的写法

                                                                                                                                                                                • 生产环境的清理脚本必须支持 dry-run,上线前演练

                                                                                                                                                                                • 软链是 rm -rf 的最大帮凶,清理脚本要显式 -type d 或 -type l 区分

                                                                                                                                                                                • 重要目录加监控,目录存在性 + 文件数量 + 文件总大小

                                                                                                                                                                                • 定期做恢复演练,每年至少一次真实数据恢复测试

                                                                                                                                                                                • 永远不要在生产环境做新工具的“第一次使用”,先在测试机

                                                                                                                                                                                • 培养“误删后第一反应是拍照不是动手”的习惯

                                                                                                                                                                                4、给团队 Leader 的建议

                                                                                                                                                                                • 清理任务走变更流程,跟代码发布一样严格

                                                                                                                                                                                • 强制 Code Review,把 Git 仓库的权限收紧

                                                                                                                                                                                • 建立“防误删日”演练,每季度一次

                                                                                                                                                                                • 配置 review 检查清单,把软链、绝对路径、rm 包装列入

                                                                                                                                                                                • 监控告警必须有“删除事件”分类

                                                                                                                                                                                十八、附录 A:常用命令速查表

                                                                                                                                                                                1、状态检查

                                                                                                                                                                                命令

                                                                                                                                                                                用途

                                                                                                                                                                                df -h

                                                                                                                                                                                查看磁盘空间

                                                                                                                                                                                df -i

                                                                                                                                                                                查看 inode 使用

                                                                                                                                                                                mount

                                                                                                                                                                                查看挂载点

                                                                                                                                                                                cat /proc/mounts

                                                                                                                                                                                内核视角的挂载信息

                                                                                                                                                                                blkid

                                                                                                                                                                                查看块设备文件系统类型

                                                                                                                                                                                lsblk

                                                                                                                                                                                树形查看块设备

                                                                                                                                                                                iostat -dx 1 5

                                                                                                                                                                                磁盘 IO 监控

                                                                                                                                                                                `dmesg

                                                                                                                                                                                tail`

                                                                                                                                                                                smartctl -H /dev/sdX

                                                                                                                                                                                磁盘健康度

                                                                                                                                                                                2、恢复工具

                                                                                                                                                                                工具

                                                                                                                                                                                适用 FS

                                                                                                                                                                                关键参数

                                                                                                                                                                                extundelete

                                                                                                                                                                                ext2/3/4

                                                                                                                                                                                --restore-all --before "时间"
                                                                                                                                                                                debugfs

                                                                                                                                                                                ext2/3/4

                                                                                                                                                                                lsdel

                                                                                                                                                                                dump <inode>

                                                                                                                                                                                xfs_undelete

                                                                                                                                                                                xfs

                                                                                                                                                                                -t <dir>
                                                                                                                                                                                xfs_db

                                                                                                                                                                                xfs

                                                                                                                                                                                诊断

                                                                                                                                                                                testdisk

                                                                                                                                                                                多 FS

                                                                                                                                                                                交互式

                                                                                                                                                                                photorec

                                                                                                                                                                                多 FS

                                                                                                                                                                                按块扫

                                                                                                                                                                                btrfs restore

                                                                                                                                                                                btrfs

                                                                                                                                                                                整 FS 恢复

                                                                                                                                                                                `lsof

                                                                                                                                                                                grep deleted`

                                                                                                                                                                                任何 FS

                                                                                                                                                                                zfs rollback

                                                                                                                                                                                zfs

                                                                                                                                                                                回滚到 snapshot

                                                                                                                                                                                3、LVM 操作

                                                                                                                                                                                命令

                                                                                                                                                                                用途

                                                                                                                                                                                vgdisplay

                                                                                                                                                                                查看 VG

                                                                                                                                                                                lvdisplay

                                                                                                                                                                                查看 LV

                                                                                                                                                                                lvcreate -s -L 20G -n snap

                                                                                                                                                                                创建快照

                                                                                                                                                                                lvremove

                                                                                                                                                                                删除快照

                                                                                                                                                                                mount -o ro /dev/vg/lv

                                                                                                                                                                                挂载快照

                                                                                                                                                                                4、 btrfs 操作

                                                                                                                                                                                命令

                                                                                                                                                                                用途

                                                                                                                                                                                btrfs subvolume list

                                                                                                                                                                                列出 subvolume

                                                                                                                                                                                btrfs subvolume snapshot

                                                                                                                                                                                创建快照

                                                                                                                                                                                btrfs restore

                                                                                                                                                                                恢复整个 FS

                                                                                                                                                                                btrfs send/receive

                                                                                                                                                                                增量备份

                                                                                                                                                                                5、zfs 操作

                                                                                                                                                                                命令

                                                                                                                                                                                用途

                                                                                                                                                                                zfs list -t snapshot

                                                                                                                                                                                列出快照

                                                                                                                                                                                zfs snapshot

                                                                                                                                                                                创建快照

                                                                                                                                                                                zfs rollback

                                                                                                                                                                                回滚

                                                                                                                                                                                zfs send/receive

                                                                                                                                                                                增量备份

                                                                                                                                                                                十九、附录 B:常见错误码与排查

                                                                                                                                                                                错误码

                                                                                                                                                                                含义

                                                                                                                                                                                应对

                                                                                                                                                                                EBUSY

                                                                                                                                                                                 mount

                                                                                                                                                                                FS 有进程在写

                                                                                                                                                                                fuser

                                                                                                                                                                                 找进程,停止或卸载

                                                                                                                                                                                EACCES

                                                                                                                                                                                 debugfs

                                                                                                                                                                                权限不足

                                                                                                                                                                                用 root

                                                                                                                                                                                ENOSPC

                                                                                                                                                                                 extundelete

                                                                                                                                                                                输出盘空间不足

                                                                                                                                                                                换大点的目标盘

                                                                                                                                                                                EIO

                                                                                                                                                                                 dd

                                                                                                                                                                                磁盘读错误

                                                                                                                                                                                加 conv=noerror,sync

                                                                                                                                                                                EINVAL

                                                                                                                                                                                 mkfs

                                                                                                                                                                                文件系统不识别

                                                                                                                                                                                检查 blkid

                                                                                                                                                                                ENOMEM

                                                                                                                                                                                 debugfs

                                                                                                                                                                                内存不足

                                                                                                                                                                                加大内存或加 swap

                                                                                                                                                                                二十、附录 C:常见误区澄清

                                                                                                                                                                                误区 1:rm 之后立刻 sync 还能救

                                                                                                                                                                                错。sync 只把内存里的脏页刷到磁盘。rm 已经把目录项和 inode 元数据改了,sync 不能“撤销”这个改动。

                                                                                                                                                                                误区 2:磁盘格式化后立刻重启就找不到原数据

                                                                                                                                                                                不一定。元数据被重置,但 block 数据还在。photorec 还能扫到一部分。但成功率和 FS 类型、覆写率强相关。

                                                                                                                                                                                误区 3:rm -rf / 一定能把系统搞坏

                                                                                                                                                                                取决于根分区的类型。如果根分区是单独 mount 的,rm -rf / 不会真的删根目录(Linux 内核会拒绝)。但如果根目录是用 bind mount 把 /data 映射到 / 的,就会真的全删。

                                                                                                                                                                                误区 4:xfs 不能恢复

                                                                                                                                                                                不严谨。xfs_undelete 多数情况下能恢复部分文件,photorec 也能扫一部分。成功率比 ext4 低,但并不是 0。

                                                                                                                                                                                误区 5:固态硬盘恢复成功率低

                                                                                                                                                                                不严谨。SSD 的 TRIM 指令会主动清零空闲块。如果 SSD 开启了 TRIM 且运行了足够时间,恢复率确实接近 0。但很多企业级 SSD 默认关闭 TRIM,或延迟 TRIM,恢复率跟 HDD 接近。hdparm -I /dev/sdX | grep TRIM 可以看是否支持。

                                                                                                                                                                                误区 6:恢复后文件能 100% 还原

                                                                                                                                                                                不严谨。恢复工具只保证 block 层面拼回去,元数据(创建时间、权限、扩展属性、ACL)可能丢失。

                                                                                                                                                                                误区 7:dd 镜像比 rsync 安全

                                                                                                                                                                                各有各的用法。dd 适合整盘镜像、无法 mount 的磁盘、底层读取场景。rsync 适合文件系统级别的复制。

                                                                                                                                                                                误区 8:rm -rf 跟 rm 等价

                                                                                                                                                                                错。-r 是 recursive,-f 是 force(不提示、忽略不存在的文件)。rm -rf 在交互场景下完全不会等你。

                                                                                                                                                                                二十一、附录 D:极端场景下的兜底方案

                                                                                                                                                                                下面几个方案是“实在救不回来”才考虑。

                                                                                                                                                                                1、联系数据恢复公司

                                                                                                                                                                                国内主流公司:

                                                                                                                                                                                • 苏州某知名厂商(涉密)

                                                                                                                                                                                • 各类“专业数据恢复”服务

                                                                                                                                                                                服务特点:

                                                                                                                                                                                • 价格贵(万到几十万)

                                                                                                                                                                                • 周期长(几天到几周)

                                                                                                                                                                                • 需要把磁盘邮寄过去

                                                                                                                                                                                • 有保密协议

                                                                                                                                                                                适用于:

                                                                                                                                                                                • 关键业务数据

                                                                                                                                                                                • 涉及合规审计

                                                                                                                                                                                • 内部团队已无能力

                                                                                                                                                                                2、从备份恢复

                                                                                                                                                                                如果误删的文件是“备份数据”本身,恢复路径就是从“备份的备份”恢复:

                                                                                                                                                                                • 异地 rsync 同步

                                                                                                                                                                                • 对象存储快照

                                                                                                                                                                                • 磁带归档(LTO)

                                                                                                                                                                                磁带是“最后的最后”的手段:

                                                                                                                                                                                • 成本低

                                                                                                                                                                                • 容量大

                                                                                                                                                                                • 恢复需要特定设备

                                                                                                                                                                                • 多数公司已经不再用

                                                                                                                                                                                3、业务层降级

                                                                                                                                                                                实在救不回来,业务层要启动降级:

                                                                                                                                                                                • 部分功能关闭

                                                                                                                                                                                • 数据不完整的状态先跑

                                                                                                                                                                                • 业务方接受降级方案

                                                                                                                                                                                • 后续逐步补全

                                                                                                                                                                                二十二、附录 E:磁盘镜像与远程恢复

                                                                                                                                                                                当恢复工具无法直接操作原盘时,需要做磁盘镜像。

                                                                                                                                                                                1、dd 镜像

                                                                                                                                                                                  # 本地镜像
                                                                                                                                                                                  dd if=/dev/sdb of=/mnt/recovery/sdb.img bs=4M status=progress conv=noerror,sync


                                                                                                                                                                                  # 远程镜像
                                                                                                                                                                                  dd if=/dev/sdb bs=4M conv=noerror,sync | gzip | ssh user@backup "cat > /mnt/recovery/sdb.img.gz"


                                                                                                                                                                                  # 还原
                                                                                                                                                                                  dd if=/mnt/recovery/sdb.img of=/dev/sdb bs=4M status=progress

                                                                                                                                                                                  2、ddrescue 增量恢复

                                                                                                                                                                                    # 安装
                                                                                                                                                                                    yum install -y ddrescue


                                                                                                                                                                                    # 第一次:全量
                                                                                                                                                                                    ddrescue /dev/sdb /mnt/recovery/sdb.img /mnt/recovery/sdb.rescue.log


                                                                                                                                                                                    # 第二次:跳过已读
                                                                                                                                                                                    ddrescue -d -r3 /dev/sdb /mnt/recovery/sdb.img /mnt/recovery/sdb.rescue.log
                                                                                                                                                                                    ddrescue 比 dd 智能,能跳过坏道并多次尝试。

                                                                                                                                                                                    3、镜像后操作

                                                                                                                                                                                      # 把镜像文件当磁盘用
                                                                                                                                                                                      losetup -f /mnt/recovery/sdb.img
                                                                                                                                                                                      losetup -a


                                                                                                                                                                                      # 在 loop 设备上跑 extundelete
                                                                                                                                                                                      extundelete /dev/loop0 --restore-all

                                                                                                                                                                                      二十三、附录 F:演练剧本(生产环境慎用)

                                                                                                                                                                                      1、演练环境准备

                                                                                                                                                                                        # 准备一台测试机
                                                                                                                                                                                        # 创建一个小 FS
                                                                                                                                                                                        dd if=/dev/zero of=/tmp/test.img bs=1M count=1024
                                                                                                                                                                                        mkfs.ext4 /tmp/test.img
                                                                                                                                                                                        mkdir -p /mnt/test
                                                                                                                                                                                        mount -o loop /tmp/test.img /mnt/test


                                                                                                                                                                                        # 准备测试数据
                                                                                                                                                                                        mkdir -p /mnt/test/{mysql,app,logs}
                                                                                                                                                                                        echo"test data" > /mnt/test/mysql/dump.sql
                                                                                                                                                                                        dd if=/dev/urandom of=/mnt/test/mysql/large_file bs=1M count=10


                                                                                                                                                                                        # 记录元数据
                                                                                                                                                                                        ls -la /mnt/test/mysql > /tmp/before_state.txt
                                                                                                                                                                                        ls -i /mnt/test/mysql > /tmp/before_inodes.txt

                                                                                                                                                                                        2、模拟误删

                                                                                                                                                                                          # 模拟误删
                                                                                                                                                                                          rm -rf /mnt/test/mysql

                                                                                                                                                                                          3、恢复演练

                                                                                                                                                                                            # 1. 立即 remount ro
                                                                                                                                                                                            mount -o remount,ro /mnt/test


                                                                                                                                                                                            # 2. extundelete
                                                                                                                                                                                            mkdir -p /mnt/recovery
                                                                                                                                                                                            cd /mnt/recovery
                                                                                                                                                                                            extundelete /tmp/test.img --restore-all


                                                                                                                                                                                            # 3. 验证
                                                                                                                                                                                            diff -r /mnt/recovery/RECOVERED_FILES/mysql /tmp/before_state.txt

                                                                                                                                                                                            4、演练评估

                                                                                                                                                                                            • 成功标准:恢复出来的文件能正常打开

                                                                                                                                                                                            • 失败标准:文件 0 字节、内容损坏

                                                                                                                                                                                            • 演练报告:记录耗时、命令、结果,写入 SOP

                                                                                                                                                                                            二十四、附录 G:监控指标建议

                                                                                                                                                                                            对于备份目录的健康度,建议监控以下指标:

                                                                                                                                                                                              # 1. 关键目录文件数量
                                                                                                                                                                                              backup_file_count{dir="/data/backup/mysql"}


                                                                                                                                                                                              # 2. 关键目录总大小
                                                                                                                                                                                              backup_dir_size_bytes{dir="/data/backup/mysql"}


                                                                                                                                                                                              # 3. 关键目录最近一次修改时间
                                                                                                                                                                                              backup_last_modified_timestamp{dir="/data/backup/mysql"}


                                                                                                                                                                                              # 4. 磁盘使用率
                                                                                                                                                                                              node_filesystem_avail_bytes{mountpoint="/data"}


                                                                                                                                                                                              # 5. inode 使用率
                                                                                                                                                                                              node_filesystem_files_free{mountpoint="/data"}


                                                                                                                                                                                              # 6. LVM 快照数量
                                                                                                                                                                                              lvm_snapshot_count{vg="vg0"}


                                                                                                                                                                                              # 7. 异地备份最后一次同步时间
                                                                                                                                                                                              remote_backup_last_sync_timestamp

                                                                                                                                                                                              告警规则示例:

                                                                                                                                                                                                groups:
                                                                                                                                                                                                  -name:backup_alerts
                                                                                                                                                                                                    rules:
                                                                                                                                                                                                      -alert:BackupDirectoryMissing
                                                                                                                                                                                                        expr:backup_file_count{dir="/data/backup/mysql"}==0
                                                                                                                                                                                                        for:5m
                                                                                                                                                                                                        labels:
                                                                                                                                                                                                          severity:critical
                                                                                                                                                                                                        annotations:
                                                                                                                                                                                                          summary:"备份目录文件数为 0"
                                                                                                                                                                                                      -alert:BackupDirectoryLowFileCount
                                                                                                                                                                                                        expr:backup_file_count{dir="/data/backup/mysql"}<100
                                                                                                                                                                                                        for:30m
                                                                                                                                                                                                        labels:
                                                                                                                                                                                                          severity:warning
                                                                                                                                                                                                        annotations:
                                                                                                                                                                                                          summary:"备份目录文件数低于阈值"
                                                                                                                                                                                                      -alert:BackupSyncFailed
                                                                                                                                                                                                        expr:time()-remote_backup_last_sync_timestamp>86400
                                                                                                                                                                                                        for:1h
                                                                                                                                                                                                        labels:
                                                                                                                                                                                                          severity:critical
                                                                                                                                                                                                        annotations:
                                                                                                                                                                                                          summary:"异地备份超过 24 小时未同步"

                                                                                                                                                                                                二十五、附录 H:工具对照表

                                                                                                                                                                                                工具

                                                                                                                                                                                                适用场景

                                                                                                                                                                                                难度

                                                                                                                                                                                                成功率

                                                                                                                                                                                                备注

                                                                                                                                                                                                extundelete

                                                                                                                                                                                                ext4 整目录恢复

                                                                                                                                                                                                首选

                                                                                                                                                                                                debugfs

                                                                                                                                                                                                按 inode 精细恢复

                                                                                                                                                                                                配合元数据

                                                                                                                                                                                                xfs_undelete

                                                                                                                                                                                                xfs 目录恢复

                                                                                                                                                                                                工具较新

                                                                                                                                                                                                testdisk

                                                                                                                                                                                                全 FS 扫描

                                                                                                                                                                                                交互式

                                                                                                                                                                                                photorec

                                                                                                                                                                                                按块特征扫描

                                                                                                                                                                                                中低

                                                                                                                                                                                                文件名丢失

                                                                                                                                                                                                lsof

                                                                                                                                                                                                进程持有文件

                                                                                                                                                                                                必查

                                                                                                                                                                                                LVM snapshot

                                                                                                                                                                                                整盘回滚

                                                                                                                                                                                                100%

                                                                                                                                                                                                依赖 snapshot

                                                                                                                                                                                                btrfs restore

                                                                                                                                                                                                btrfs 整盘恢复

                                                                                                                                                                                                zfs rollback

                                                                                                                                                                                                zfs 整盘回滚

                                                                                                                                                                                                100%

                                                                                                                                                                                                依赖 snapshot

                                                                                                                                                                                                二十六、附录 I:SOP 模板

                                                                                                                                                                                                  # 数据误删应急响应 SOP
                                                                                                                                                                                                  ## 触发条件
                                                                                                                                                                                                  - 收到删除事件告警
                                                                                                                                                                                                  - 用户报告文件丢失
                                                                                                                                                                                                  - 监控显示目录文件数突降
                                                                                                                                                                                                  ## 响应步骤
                                                                                                                                                                                                  1. 确认事故(10 分钟内)
                                                                                                                                                                                                     - 联系报告人确认现象
                                                                                                                                                                                                     - ssh 到目标主机初步确认
                                                                                                                                                                                                  2. 立即止血(5 分钟内)
                                                                                                                                                                                                     - 停 cron、停相关进程
                                                                                                                                                                                                     - remount ro 或做 LVM 快照
                                                                                                                                                                                                  3. 现场记录(15 分钟内)
                                                                                                                                                                                                     - 抓 mount、lsof、ps、dmesg
                                                                                                                                                                                                  4. 评估恢复方案(30 分钟内)
                                                                                                                                                                                                     - 确认 FS 类型
                                                                                                                                                                                                     - 选择恢复工具
                                                                                                                                                                                                  5. 执行恢复(视情况)
                                                                                                                                                                                                     - extundelete / debugfs / lsof
                                                                                                                                                                                                     - 写入独立磁盘
                                                                                                                                                                                                  6. 业务校验
                                                                                                                                                                                                     - 文件大小、类型、内容
                                                                                                                                                                                                     - 应用方确认
                                                                                                                                                                                                  7. 复盘(事故后 24 小时内)
                                                                                                                                                                                                     - 写复盘文档
                                                                                                                                                                                                     - 落地行动项

                                                                                                                                                                                                  二十七、附录 J:推荐阅读与工具

                                                                                                                                                                                                  • ext2fsprogs 文档:debugfs、e2fsck、mke2fs 的官方手册

                                                                                                                                                                                                  • e2fsprogs 源码:理解 ext4 内部实现

                                                                                                                                                                                                  • LVM 官方文档:理解 snapshot 实现

                                                                                                                                                                                                  • BTRFS Wiki:理解 CoW 文件系统

                                                                                                                                                                                                  • ZFS 文档:理解 zfs send/receive

                                                                                                                                                                                                  工具:

                                                                                                                                                                                                  • extundelete (sf.net)

                                                                                                                                                                                                  • testdisk / photorec (cgsecurity.org)

                                                                                                                                                                                                  • sleuthkit (sleuthkit.org)

                                                                                                                                                                                                  • ddrescue (gnu.org)

                                                                                                                                                                                                  • trash-cli (github.com/andreafrancia/trash-cli)

                                                                                                                                                                                                  二十八、结语

                                                                                                                                                                                                  rm -rf 不可怕,可怕的是“以为自己有备份所以不担心”。做运维越久,越会敬畏“删除”这个动作:它不像写,写错了能 git revert;它更像 SQL 的 DROP TABLE,跑完就没了。

                                                                                                                                                                                                  本文的真正意义不是教你用 extundelete 救命,而是希望你:

                                                                                                                                                                                                  • 理解文件系统原理,对“删除”有敬畏

                                                                                                                                                                                                  • 在动手前先想清楚影响面

                                                                                                                                                                                                  • 永远有 Plan B(备份、快照、Code Review)

                                                                                                                                                                                                  • 把防误删做成制度、做成工具、做成肌肉记忆

                                                                                                                                                                                                  技术会变,ext4 会变成 btrfs,centos 会变成 rocky,rm 会被各种 wrapper 包装。但“删除”这件事的本质不会变:它永远是不可逆的、永远需要审批的、永远需要备份的。

                                                                                                                                                                                                  希望这篇文章能让你下次面对 rm -rf 时,多一份从容,多一份底气。

                                                                                                                                                                                                  二十九、引用与版本说明

                                                                                                                                                                                                  • 本文涉及的内核版本以 3.10、4.18、5.4 为例

                                                                                                                                                                                                  • ext4 格式参考 kernel.org Documentation/filesystems/ext4

                                                                                                                                                                                                  • LVM 来自 Red Hat 官方手册

                                                                                                                                                                                                  • btrfs 来自 btrfs.wiki.kernel.org

                                                                                                                                                                                                  • zfs 来自 OpenZFS 文档

                                                                                                                                                                                                  不同版本字段可能略有差异,实际操作请以目标环境的工具版本手册为准。

                                                                                                                                                                                                  来源丨公众号:马哥Linux运维(ID:magedu-Linux)

                                                                                                                                                                                                  dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

                                                                                                                                                                                                  跳转微信打开